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
JavaScript
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