cross-tool-context.js•17.6 kB
// Enhanced cross-tool communication with time caps and smart caching
import { v4 as uuidv4 } from 'uuid';
// Smart cache with TTL and memory management
class SmartCache {
constructor(maxSize = 1000, defaultTTL = 300000) { // 5 minutes default
this.cache = new Map();
this.maxSize = maxSize;
this.defaultTTL = defaultTTL;
this.accessTimes = new Map();
}
set(key, value, ttl = this.defaultTTL) {
// Clean up if cache is full
if (this.cache.size >= this.maxSize) {
this.cleanup();
}
const expires = Date.now() + ttl;
this.cache.set(key, { value, expires });
this.accessTimes.set(key, Date.now());
}
get(key) {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() > entry.expires) {
this.cache.delete(key);
this.accessTimes.delete(key);
return null;
}
this.accessTimes.set(key, Date.now());
return entry.value;
}
has(key) {
return this.get(key) !== null;
}
delete(key) {
this.cache.delete(key);
this.accessTimes.delete(key);
}
clear() {
this.cache.clear();
this.accessTimes.clear();
}
cleanup() {
const now = Date.now();
const toDelete = [];
// Remove expired entries
for (const [key, entry] of this.cache) {
if (now > entry.expires) {
toDelete.push(key);
}
}
// Remove least recently used if still over size
if (this.cache.size - toDelete.length >= this.maxSize) {
const sorted = Array.from(this.accessTimes.entries())
.sort(([, a], [, b]) => a - b);
const toRemove = sorted.slice(0, this.cache.size - this.maxSize + toDelete.length);
toDelete.push(...toRemove.map(([key]) => key));
}
toDelete.forEach(key => this.delete(key));
}
getStats() {
return {
size: this.cache.size,
maxSize: this.maxSize,
hitRate: this.hitRate || 0
};
}
}
// Delta-based context manager
class DeltaContextManager {
constructor() {
this.baseContext = new Map();
this.deltas = new Map();
this.lastSync = Date.now();
}
updateBaseContext(key, value) {
const oldValue = this.baseContext.get(key);
this.baseContext.set(key, value);
// Store delta
this.deltas.set(`${key}_${Date.now()}`, {
key,
oldValue,
newValue: value,
timestamp: Date.now()
});
// Clean old deltas (keep only last 100)
if (this.deltas.size > 100) {
const sorted = Array.from(this.deltas.entries())
.sort(([, a], [, b]) => a.timestamp - b.timestamp);
const toDelete = sorted.slice(0, this.deltas.size - 100);
toDelete.forEach(([key]) => this.deltas.delete(key));
}
}
getDeltas(since) {
const relevantDeltas = [];
for (const [id, delta] of this.deltas) {
if (delta.timestamp > since) {
relevantDeltas.push(delta);
}
}
return relevantDeltas;
}
getContext(since = null) {
if (!since) {
return Object.fromEntries(this.baseContext);
}
const deltas = this.getDeltas(since);
const context = {};
// Apply deltas in order
deltas.forEach(delta => {
context[delta.key] = delta.newValue;
});
return context;
}
compress() {
// Clean old deltas (older than 5 minutes)
const cutoff = Date.now() - 300000;
for (const [id, delta] of this.deltas) {
if (delta.timestamp < cutoff) {
this.deltas.delete(id);
}
}
}
}
export class CrossToolContext {
constructor() {
this.toolSessions = new Map();
this.currentSession = null;
this.globalContext = new DeltaContextManager();
this.smartCache = new SmartCache(1000, 300000); // 5 minute TTL
this.resultCache = new SmartCache(500, 180000); // 3 minute TTL for results
this.duplicationSuppression = new Map();
this.reportedItems = new Map(); // Track reported items for suppression
this.lastCleanup = Date.now();
}
// Time cap constants
static TIME_CAPS = {
ANALYSIS: 60000, // 1 minute for analysis
LINT: 30000, // 30 seconds for linting
EXECUTION: 120000, // 2 minutes for execution
SEARCH: 45000, // 45 seconds for search
DEFAULT: 30000 // 30 seconds default
};
// Get time cap for tool type
getTimeCap(toolName) {
const name = toolName.toLowerCase();
if (name.includes('ast') || name.includes('analysis')) return this.constructor.TIME_CAPS.ANALYSIS;
if (name.includes('lint')) return this.constructor.TIME_CAPS.LINT;
if (name.includes('exec')) return this.constructor.TIME_CAPS.EXECUTION;
if (name.includes('search')) return this.constructor.TIME_CAPS.SEARCH;
return this.constructor.TIME_CAPS.DEFAULT;
}
// Check for duplicate operations
isDuplicate(toolName, workingDirectory, query, args) {
const signature = this.createOperationSignature(toolName, workingDirectory, query, args);
const lastOperation = this.duplicationSuppression.get(signature);
if (lastOperation && Date.now() - lastOperation < this.getTimeCap(toolName)) {
return true;
}
this.duplicationSuppression.set(signature, Date.now());
// Clean old suppression entries
this.cleanupSuppression();
return false;
}
createOperationSignature(toolName, workingDirectory, query, args) {
const keyData = {
toolName,
workingDirectory,
query: query ? query.substring(0, 100) : '',
argsHash: this.hashArgs(args)
};
return JSON.stringify(keyData);
}
hashArgs(args) {
// Simple hash of relevant args
const relevant = {};
for (const [key, value] of Object.entries(args)) {
if (typeof value === 'string') {
relevant[key] = value.substring(0, 50);
} else if (typeof value === 'number' || typeof value === 'boolean') {
relevant[key] = value;
}
}
return JSON.stringify(relevant);
}
cleanupSuppression() {
const now = Date.now();
if (now - this.lastCleanup > 60000) { // Clean every minute
const toDelete = [];
for (const [signature, timestamp] of this.duplicationSuppression) {
if (now - timestamp > 300000) { // 5 minutes
toDelete.push(signature);
}
}
toDelete.forEach(signature => this.duplicationSuppression.delete(signature));
// Clean old reported items (older than 10 minutes)
const reportedToDelete = [];
for (const [signature, timestamp] of this.reportedItems) {
if (now - timestamp > 600000) { // 10 minutes
reportedToDelete.push(signature);
}
}
reportedToDelete.forEach(signature => this.reportedItems.delete(signature));
this.lastCleanup = now;
}
}
// Check if an item has been reported and should be suppressed
isReported(category, identifier) {
const signature = `${category}:${identifier}`;
const lastReported = this.reportedItems.get(signature);
if (lastReported && Date.now() - lastReported < 600000) { // 10 minutes
return true;
}
this.reportedItems.set(signature, Date.now());
return false;
}
// Generate concise report for cross-tool responses
generateConciseReport(toolName, workingDirectory, result) {
const report = {
tool: toolName,
location: workingDirectory,
timestamp: Date.now(),
summary: this.createSummary(result)
};
// Only include essential information
if (result.filesAccessed && result.filesAccessed.length > 0) {
report.files = result.filesAccessed.slice(0, 3); // Max 3 files
}
if (result.patterns && result.patterns.length > 0) {
report.patterns = result.patterns.slice(0, 2); // Max 2 patterns
}
return report;
}
// Create a concise summary from result data
createSummary(result) {
if (result.error) {
return `Error: ${result.error.message || result.error}`;
}
if (result.content && Array.isArray(result.content)) {
const textContent = result.content.find(c => c.type === 'text')?.text || '';
if (textContent.startsWith('[')) { // JSON array
try {
const parsed = JSON.parse(textContent);
if (Array.isArray(parsed)) {
return `${parsed.length} items found`;
}
} catch {
// Fall through to text summary
}
}
return textContent.substring(0, 100) + (textContent.length > 100 ? '...' : '');
}
if (result.stdout) {
return result.stdout.split('\n')[0].substring(0, 100) + (result.stdout.length > 100 ? '...' : '');
}
return 'Operation completed';
}
// Suppress duplicate reporting with enhanced checks
shouldReport(toolName, workingDirectory, result) {
// Create signature for this report
const signature = this.createReportSignature(toolName, workingDirectory, result);
// Check if we've reported something very similar recently
return !this.isReported('tool_report', signature);
}
createReportSignature(toolName, workingDirectory, result) {
const keyParts = [
toolName,
workingDirectory,
result.error ? 'error' : 'success',
result.content ? 'content' : 'no-content',
result.filesAccessed?.length || 0,
result.patterns?.length || 0
];
return keyParts.join('|');
}
// Smart caching for intermediate results
cacheResult(toolName, workingDirectory, key, result, ttl = null) {
const cacheKey = `${toolName}_${workingDirectory}_${key}`;
const resultTTL = ttl || this.getTimeCap(toolName);
this.resultCache.set(cacheKey, result, resultTTL);
}
getCachedResult(toolName, workingDirectory, key) {
const cacheKey = `${toolName}_${workingDirectory}_${key}`;
return this.resultCache.get(cacheKey);
}
// Cache computation results
cacheComputation(toolName, key, computation, ttl = null) {
if (this.smartCache.has(key)) {
return this.smartCache.get(key);
}
const result = computation();
const cacheTTL = ttl || this.getTimeCap(toolName);
this.smartCache.set(key, result, cacheTTL);
return result;
}
// Start a new tool session with time cap
startToolSession({
id = uuidv4(),
toolName,
workingDirectory,
query,
startTime = Date.now(),
args = {}
} = {}) {
// Check for duplicates - only for expensive operations
if (this.isDuplicate(toolName, workingDirectory, query, args) &&
['searchcode', 'ast_tool'].includes(toolName)) {
// Silently return cached result instead of throwing error
const cachedResult = this.getCachedResult(toolName, workingDirectory, this.createOperationSignature(toolName, workingDirectory, query, args));
if (cachedResult) {
return cachedResult;
}
}
const session = {
id,
toolName,
workingDirectory,
query,
startTime,
timeCap: this.getTimeCap(toolName),
status: 'running',
result: null,
error: null,
relatedSessions: [],
metadata: {}
};
this.toolSessions.set(id, session);
this.currentSession = id;
return session;
}
// Complete current tool session
completeCurrentSession(result) {
if (!this.currentSession) return null;
const session = this.toolSessions.get(this.currentSession);
if (session) {
session.status = 'completed';
session.endTime = Date.now();
session.duration = session.endTime - session.startTime;
session.result = result;
// Cache result if under time cap
if (session.duration < session.timeCap) {
this.cacheResult(
session.toolName,
session.workingDirectory,
session.query || 'default',
result,
session.timeCap
);
}
// Update global context with delta
this.updateGlobalContext(session);
return session;
}
return null;
}
// Get current tool session with time cap check
getCurrentSession() {
if (!this.currentSession) return null;
const session = this.toolSessions.get(this.currentSession);
// Clear current if it's too old (exceeded time cap)
if (session && Date.now() - session.startTime > session.timeCap) {
this.currentSession = null;
return null;
}
return session;
}
// Update global context using delta-based updates
updateGlobalContext(session) {
const key = `${session.toolName}_${session.workingDirectory}`;
const currentContext = this.globalContext.getContext();
let context = currentContext[key] || {
toolName: session.toolName,
workingDirectory: session.workingDirectory,
totalCalls: 0,
successfulCalls: 0,
failedCalls: 0,
averageDuration: 0,
lastUsed: 0,
commonPatterns: new Set(),
recentFiles: new Set()
};
// Create delta
context.totalCalls++;
context.lastUsed = Date.now();
if (session.status === 'completed') {
context.successfulCalls++;
} else if (session.status === 'failed') {
context.failedCalls++;
}
// Update average duration
if (session.duration) {
context.averageDuration = (context.averageDuration * (context.totalCalls - 1) + session.duration) / context.totalCalls;
}
// Store session-specific metadata
if (session.metadata) {
if (session.metadata.patterns) {
session.metadata.patterns.forEach(pattern => context.commonPatterns.add(pattern));
}
if (session.metadata.filesAccessed) {
session.metadata.filesAccessed.forEach(file => context.recentFiles.add(file));
}
}
this.globalContext.updateBaseContext(key, context);
}
// Clean up old sessions and compress context
cleanup() {
const now = Date.now();
const toDelete = [];
// Delete old sessions
for (const [id, session] of this.toolSessions) {
const age = now - session.startTime;
if (age > 300000) { // 5 minutes
toDelete.push(id);
}
}
toDelete.forEach(id => {
this.toolSessions.delete(id);
if (this.currentSession === id) {
this.currentSession = null;
}
});
// Compress context
this.globalContext.compress();
// Clean caches
this.smartCache.cleanup();
this.resultCache.cleanup();
}
// Get cache statistics
getCacheStats() {
return {
smartCache: this.smartCache.getStats(),
resultCache: this.resultCache.getStats(),
sessions: this.toolSessions.size,
duplicatesSuppressed: this.duplicationSuppression.size
};
}
}
// Global instance
export const crossToolContext = new CrossToolContext();
// Enhanced middleware function with smart caching and duplication suppression
export function withCrossToolAwareness(toolHandler, toolName) {
return async (args) => {
const workingDirectory = args.workingDirectory || process.cwd();
const query = args.query || args.pattern || args.code || '';
// Start tool session with time cap and duplication check
const session = crossToolContext.startToolSession({
toolName,
workingDirectory,
query,
args
});
try {
// Check cache first
const cacheKey = JSON.stringify(args);
const cachedResult = crossToolContext.getCachedResult(toolName, workingDirectory, cacheKey);
if (cachedResult) {
// Return cached result with metadata
return {
...cachedResult,
_cached: true,
_cacheTimestamp: Date.now()
};
}
// Execute the tool with smart caching for expensive operations
const result = await crossToolContext.cacheComputation(
toolName,
cacheKey,
() => toolHandler(args)
);
// Complete session and cache result
crossToolContext.completeCurrentSession(result);
// Add session metadata
if (session) {
session.metadata = {
filesAccessed: result._filesAccessed || [],
patterns: result._patterns || [],
insights: result._insights || []
};
}
return result;
} catch (error) {
// Fail session and return error gracefully
crossToolContext.failCurrentSession(error);
return {
content: [{
type: "text",
text: `Error in ${toolName}: ${error.message}${error.stack ? '\n' + error.stack : ''}`
}],
_isError: true
};
}
};
}
// Utility function to add tool-specific metadata with concise reporting
export function addToolMetadata(result, metadata) {
const enhancedResult = {
...result,
...metadata
};
// Add concise report if this should be reported
if (metadata.toolName && metadata.workingDirectory) {
if (crossToolContext.shouldReport(metadata.toolName, metadata.workingDirectory, result)) {
const conciseReport = crossToolContext.generateConciseReport(
metadata.toolName,
metadata.workingDirectory,
result
);
enhancedResult._conciseReport = conciseReport;
}
}
return enhancedResult;
}
// Utility function to generate location-based concise report
export function generateLocationReport(toolName, workingDirectory, files = [], summary = '') {
const report = {
tool: toolName,
location: workingDirectory,
timestamp: Date.now(),
files: files.slice(0, 5), // Max 5 files
summary: summary || `${files.length} files processed`
};
// Only report if not recently reported
const signature = `${toolName}:${workingDirectory}:${files.length}:${summary.substring(0, 50)}`;
if (!crossToolContext.isReported('location_report', signature)) {
return report;
}
return null; // Suppressed
}