UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

161 lines • 6.76 kB
export class LogTruncator { // Rough estimate: 1 token ≈ 4 characters static CHARS_PER_TOKEN = 4; static DEFAULT_MAX_TOKENS = 8000; // Safe limit for most LLMs /** * Truncate logs intelligently to fit within token limits */ static truncateLogs(logs, options = {}) { const { maxTokens = this.DEFAULT_MAX_TOKENS, maxCharacters = maxTokens * this.CHARS_PER_TOKEN, preserveLatest = true, prioritizeErrors = true } = options; if (logs.length === 0) return []; // Calculate total size const totalChars = logs.reduce((sum, log) => sum + log.text.length, 0); // If within limits, return as-is if (totalChars <= maxCharacters) { return logs; } // Separate logs by type if prioritizing errors if (prioritizeErrors) { const errors = logs.filter(log => log.type === 'error' || log.type === 'warn'); const others = logs.filter(log => log.type !== 'error' && log.type !== 'warn'); // Calculate space for each category const errorChars = errors.reduce((sum, log) => sum + log.text.length, 0); const errorSpace = Math.min(errorChars, maxCharacters * 0.6); // Up to 60% for errors const otherSpace = maxCharacters - errorSpace; // Truncate each category const truncatedErrors = this.truncateLogsSimple(errors, errorSpace, preserveLatest); const truncatedOthers = this.truncateLogsSimple(others, otherSpace, preserveLatest); // Combine and sort by timestamp return [...truncatedErrors, ...truncatedOthers].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); } // Simple truncation without prioritization return this.truncateLogsSimple(logs, maxCharacters, preserveLatest); } /** * Simple truncation that preserves either latest or earliest logs */ static truncateLogsSimple(logs, maxCharacters, preserveLatest) { if (logs.length === 0) return []; const result = []; let currentSize = 0; // Add truncation notice size const truncationNotice = { type: 'info', text: `... ${logs.length} logs truncated to fit context window ...`, timestamp: new Date() }; const noticeSize = truncationNotice.text.length; // Process logs in order (latest first if preserveLatest) const orderedLogs = preserveLatest ? [...logs].reverse() : logs; for (const log of orderedLogs) { const logSize = log.text.length; // Check if we can fit this log plus the truncation notice if (currentSize + logSize + noticeSize <= maxCharacters) { result.push(log); currentSize += logSize; } else if (currentSize + noticeSize < maxCharacters) { // Truncate this log to fit const remainingSpace = maxCharacters - currentSize - noticeSize; if (remainingSpace > 50) { // Only truncate if meaningful content remains result.push({ ...log, text: log.text.substring(0, remainingSpace - 3) + '...' }); } break; } else { break; } } // Restore original order if (preserveLatest) { result.reverse(); } // Add truncation notice if we actually truncated if (result.length < logs.length) { const position = preserveLatest ? 0 : result.length; result.splice(position, 0, truncationNotice); } return result; } /** * Truncate a single message intelligently */ static truncateMessage(message, maxCharacters) { if (message.length <= maxCharacters) { return message; } // For very small limits, just truncate if (maxCharacters < 100) { return message.substring(0, maxCharacters - 3) + '...'; } // Try to truncate at a reasonable boundary const halfSize = Math.floor(maxCharacters / 2) - 20; // Show beginning and end const start = message.substring(0, halfSize); const end = message.substring(message.length - halfSize); return `${start}\n... truncated ${message.length - maxCharacters} characters ...\n${end}`; } /** * Estimate token count for a string */ static estimateTokens(text) { // Rough estimate: 1 token ≈ 4 characters // Adjust for whitespace and punctuation const wordCount = text.split(/\s+/).length; const charCount = text.length; // Average between character and word-based estimates const charEstimate = charCount / this.CHARS_PER_TOKEN; const wordEstimate = wordCount * 1.3; // Words average ~1.3 tokens return Math.ceil((charEstimate + wordEstimate) / 2); } /** * Group similar logs to reduce redundancy */ static groupSimilarLogs(logs) { const grouped = []; const patterns = new Map(); for (const log of logs) { // Create a pattern by removing numbers and timestamps const pattern = log.text .replace(/\d+/g, 'N') .replace(/\b[0-9a-f]{8,}\b/gi, 'HEX') .replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/g, 'TIMESTAMP'); const existing = patterns.get(pattern); if (existing && existing.type === log.type) { existing.count++; existing.lastTimestamp = log.timestamp; } else { patterns.set(pattern, { count: 1, firstTimestamp: log.timestamp, lastTimestamp: log.timestamp, type: log.type }); grouped.push({ ...log, pattern }); } } // Replace grouped logs with summaries return grouped.map(log => { if (log.pattern) { const patternInfo = patterns.get(log.pattern); if (patternInfo && patternInfo.count > 1) { return { type: log.type, text: `${log.text} (repeated ${patternInfo.count} times)`, timestamp: patternInfo.firstTimestamp, count: patternInfo.count }; } } delete log.pattern; return log; }); } } //# sourceMappingURL=log-truncator.js.map