claude-hooks/lib/context-monitor.js

156 lines
4.3 KiB
JavaScript

/**
* Context Monitor - Estimates context usage and triggers backups
*/
const fs = require('fs-extra');
const path = require('path');
class ContextMonitor {
constructor() {
this.estimatedTokens = 0;
this.promptCount = 0;
this.toolExecutions = 0;
this.sessionStartTime = Date.now();
// Configuration
this.maxContextTokens = 200000; // Conservative estimate
this.backupThreshold = 0.85; // 85% of max context
this.timeThresholdMinutes = 30; // 30 minutes
this.toolThreshold = 25; // 25 tool executions
this.lastBackupTime = Date.now();
this.lastBackupToolCount = 0;
}
/**
* Update context estimates from user prompt
*/
updateFromPrompt(promptData) {
this.promptCount++;
// Estimate tokens from prompt
const prompt = promptData.prompt || '';
const estimatedPromptTokens = this._estimateTokens(prompt);
this.estimatedTokens += estimatedPromptTokens;
// Add context size if provided
if (promptData.context_size) {
this.estimatedTokens += promptData.context_size;
}
}
/**
* Update context estimates from tool usage
*/
updateFromToolUse(toolData) {
this.toolExecutions++;
// Estimate tokens from tool parameters and output
const parameters = JSON.stringify(toolData.parameters || {});
const output = toolData.output || '';
const error = toolData.error || '';
const toolTokens = this._estimateTokens(parameters + output + error);
this.estimatedTokens += toolTokens;
// File operations add more context
if (toolData.tool === 'Read' || toolData.tool === 'Edit') {
this.estimatedTokens += 2000; // Typical file size estimate
} else if (toolData.tool === 'Bash') {
this.estimatedTokens += 500; // Command output estimate
}
}
/**
* Check if backup should be triggered
*/
checkBackupTriggers(hookType, data) {
const decisions = [];
// Context threshold trigger
const contextRatio = this.getContextUsageRatio();
if (contextRatio > this.backupThreshold) {
decisions.push({
shouldBackup: true,
reason: `Context usage ${(contextRatio * 100).toFixed(1)}%`,
urgency: 'high'
});
}
// Time-based trigger
const sessionMinutes = (Date.now() - this.sessionStartTime) / (1000 * 60);
const timeSinceBackup = (Date.now() - this.lastBackupTime) / (1000 * 60);
if (timeSinceBackup > this.timeThresholdMinutes) {
decisions.push({
shouldBackup: true,
reason: `${this.timeThresholdMinutes} minutes since last backup`,
urgency: 'medium'
});
}
// Tool-based trigger
const toolsSinceBackup = this.toolExecutions - this.lastBackupToolCount;
if (toolsSinceBackup >= this.toolThreshold) {
decisions.push({
shouldBackup: true,
reason: `${this.toolThreshold} tools since last backup`,
urgency: 'medium'
});
}
// Return highest priority decision
if (decisions.length > 0) {
const urgencyOrder = { high: 3, medium: 2, low: 1 };
decisions.sort((a, b) => urgencyOrder[b.urgency] - urgencyOrder[a.urgency]);
return decisions[0];
}
return { shouldBackup: false };
}
/**
* Get current context usage ratio (0.0 to 1.0)
*/
getContextUsageRatio() {
return Math.min(1.0, this.estimatedTokens / this.maxContextTokens);
}
/**
* Mark that a backup was performed
*/
markBackupPerformed() {
this.lastBackupTime = Date.now();
this.lastBackupToolCount = this.toolExecutions;
}
/**
* Estimate tokens from text (rough approximation)
*/
_estimateTokens(text) {
if (!text) return 0;
// Rough estimate: ~4 characters per token for English text
// Add some buffer for formatting and special tokens
return Math.ceil(text.length / 3.5);
}
/**
* Get context usage statistics
*/
getStats() {
const sessionMinutes = (Date.now() - this.sessionStartTime) / (1000 * 60);
return {
estimatedTokens: this.estimatedTokens,
contextUsageRatio: this.getContextUsageRatio(),
promptCount: this.promptCount,
toolExecutions: this.toolExecutions,
sessionMinutes: Math.round(sessionMinutes),
lastBackupMinutesAgo: Math.round((Date.now() - this.lastBackupTime) / (1000 * 60))
};
}
}
module.exports = { ContextMonitor };