@mrtkrcm/acp-claude-code
Version:
ACP (Agent Client Protocol) bridge for Claude Code
95 lines • 4.31 kB
JavaScript
export class ContextMonitor {
sessions = new Map();
CONTEXT_LIMIT = 200000;
WARNING_THRESHOLD = 0.8;
CRITICAL_THRESHOLD = 0.95;
cleanupTimer;
constructor(_debugMode) {
// Auto-cleanup every 10 minutes instead of relying on external calls
this.cleanupTimer = setInterval(() => {
this.cleanupInactiveSessions();
}, 10 * 60 * 1000);
}
addMessage(sessionId, content, role) {
// Use more accurate token estimation for better memory planning
const tokens = this.estimateTokens(content);
const stats = this.sessions.get(sessionId) || { usage: 0, estimatedTokens: 0, messages: 0, turnCount: 0, lastActivity: Date.now(), lastUpdate: new Date() };
stats.estimatedTokens += tokens;
stats.usage = Math.min(stats.estimatedTokens / this.CONTEXT_LIMIT, 1);
stats.messages++;
if (role === 'user')
stats.turnCount++;
stats.lastActivity = Date.now();
stats.lastUpdate = new Date();
this.sessions.set(sessionId, stats);
// Proactive cleanup when approaching limits
if (this.sessions.size > 100) {
this.cleanupInactiveSessions(30 * 60 * 1000); // 30 minutes
}
if (stats.usage >= this.CRITICAL_THRESHOLD)
return { level: 'critical', message: `Context usage critical (${(stats.usage * 100).toFixed(1)}%)`, usage: stats.usage };
if (stats.usage >= this.WARNING_THRESHOLD)
return { level: 'warning', message: `High context usage (${(stats.usage * 100).toFixed(1)}%)`, usage: stats.usage };
return null;
}
estimateTokens(content) {
// Simple token estimation - rough approximation for monitoring
// Keep this simple and fast for real-time monitoring and test compatibility
return Math.ceil(content.length / 4);
}
getStats(sessionId) { return this.sessions.get(sessionId) || null; }
getAllStats() { return this.sessions; }
clearSession(sessionId) { this.sessions.delete(sessionId); }
getSessionSummary(sessionId) {
const stats = this.sessions.get(sessionId);
if (!stats)
return `Session ${sessionId}: No data`;
const usageKB = Math.round(stats.estimatedTokens / 1000);
const limitKB = Math.round(this.CONTEXT_LIMIT / 1000);
const percent = Math.round(stats.usage * 100);
const status = stats.usage >= this.CRITICAL_THRESHOLD ? '[!]' : stats.usage >= this.WARNING_THRESHOLD ? '[WARNING]' : '[✓]';
const usageLabel = stats.usage >= this.CRITICAL_THRESHOLD ? 'CRITICAL' : stats.usage >= this.WARNING_THRESHOLD ? 'HIGH' : 'OK';
const turnsLabel = stats.turnCount === 1 ? '1 turn' : `${stats.turnCount} turns`;
return `${status} Context: ${usageKB}K/${limitKB}K (${percent}%) | ${turnsLabel} | Status: ${usageLabel}`;
}
cleanupInactiveSessions(maxInactiveMs = 3600000) {
const now = Date.now();
let removed = 0;
for (const [sessionId, stats] of this.sessions.entries()) {
if (now - stats.lastActivity > maxInactiveMs) {
this.sessions.delete(sessionId);
removed++;
}
}
return removed;
}
resetSession(sessionId) {
const stats = this.sessions.get(sessionId);
if (stats) {
stats.usage = 0;
stats.estimatedTokens = 0;
stats.messages = 0;
stats.turnCount = 0;
stats.lastActivity = Date.now();
stats.lastUpdate = new Date();
}
}
getMemoryStats() {
let totalMessages = 0;
let totalTokens = 0;
for (const stats of this.sessions.values()) {
totalMessages += stats.messages;
totalTokens += stats.estimatedTokens;
}
return { activeSessions: this.sessions.size, totalMessages, totalTokens, averageTokensPerSession: this.sessions.size ? totalTokens / this.sessions.size : 0 };
}
cleanupOldSessions(maxAgeMs) { return this.cleanupInactiveSessions(maxAgeMs); }
destroy() {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = undefined;
}
this.sessions.clear();
}
}
//# sourceMappingURL=context-monitor.js.map