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

214 lines 7.06 kB
/** * Tool Execution Safety System * Provides isolation, timeouts, and retry mechanisms for tool execution */ export class ToolExecutionSafetyWrapper { static instance; circuitBreakers = new Map(); executionMetrics = new Map(); static getInstance() { if (!ToolExecutionSafetyWrapper.instance) { ToolExecutionSafetyWrapper.instance = new ToolExecutionSafetyWrapper(); } return ToolExecutionSafetyWrapper.instance; } /** * Execute a tool with safety wrapper including timeouts, retries, and circuit breaker */ async executeTool(toolName, handler, options = {}) { const startTime = Date.now(); const { timeout = 30000, // 30 second default timeout maxRetries = 3, retryDelay = 1000, circuitBreakerThreshold = 5 } = options; // Check circuit breaker const circuitBreaker = this.getCircuitBreaker(toolName, circuitBreakerThreshold); if (circuitBreaker.isOpen()) { return { success: false, error: new Error(`Circuit breaker open for tool: ${toolName}`), executionTime: Date.now() - startTime, retryCount: 0, circuitBreakerTriggered: true }; } let lastError; let retryCount = 0; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { // Execute with timeout const result = await Promise.race([ handler(), this.createTimeoutPromise(timeout, toolName) ]); // Success - record metrics and return this.recordSuccess(toolName); circuitBreaker.recordSuccess(); return { success: true, result, executionTime: Date.now() - startTime, retryCount, circuitBreakerTriggered: false }; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); retryCount = attempt; this.recordFailure(toolName, lastError); circuitBreaker.recordFailure(); console.warn(`⚠️ Tool ${toolName} failed (attempt ${attempt + 1}/${maxRetries + 1}): ${lastError.message}`); // Don't retry on timeout or critical errors if (this.isCriticalError(lastError) || attempt === maxRetries) { break; } // Exponential backoff const backoffDelay = retryDelay * Math.pow(2, attempt); await this.sleep(backoffDelay); } } return { success: false, error: lastError, executionTime: Date.now() - startTime, retryCount, circuitBreakerTriggered: false }; } /** * Create timeout promise that rejects after specified time */ createTimeoutPromise(timeout, toolName) { return new Promise((_, reject) => { setTimeout(() => { reject(new Error(`Tool ${toolName} timed out after ${timeout}ms`)); }, timeout); }); } /** * Check if error is critical (shouldn't retry) */ isCriticalError(error) { const criticalPatterns = [ 'timeout', 'ENOTFOUND', 'permission denied', 'file not found', 'invalid argument' ]; const errorMessage = error.message.toLowerCase(); return criticalPatterns.some(pattern => errorMessage.includes(pattern)); } /** * Get or create circuit breaker for tool */ getCircuitBreaker(toolName, threshold) { if (!this.circuitBreakers.has(toolName)) { this.circuitBreakers.set(toolName, new CircuitBreaker(threshold)); } return this.circuitBreakers.get(toolName); } /** * Record successful execution */ recordSuccess(toolName) { const metrics = this.getOrCreateMetrics(toolName); metrics.successCount++; metrics.lastExecutionTime = Date.now(); } /** * Record failed execution */ recordFailure(toolName, error) { const metrics = this.getOrCreateMetrics(toolName); metrics.failureCount++; metrics.lastError = error.message; metrics.lastExecutionTime = Date.now(); } /** * Get or create execution metrics for tool */ getOrCreateMetrics(toolName) { if (!this.executionMetrics.has(toolName)) { this.executionMetrics.set(toolName, { toolName, successCount: 0, failureCount: 0, lastExecutionTime: 0, lastError: null }); } return this.executionMetrics.get(toolName); } /** * Get execution statistics for monitoring */ getExecutionStats() { const tools = {}; for (const [toolName, metrics] of this.executionMetrics.entries()) { tools[toolName] = { ...metrics }; } return { tools, totalTools: this.executionMetrics.size, circuitBreakersOpen: Array.from(this.circuitBreakers.values()) .filter(cb => cb.isOpen()).length }; } /** * Reset circuit breaker for a tool */ resetCircuitBreaker(toolName) { const circuitBreaker = this.circuitBreakers.get(toolName); if (circuitBreaker) { circuitBreaker.reset(); console.log(`🔄 Reset circuit breaker for tool: ${toolName}`); } } /** * Sleep utility for retry delays */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } /** * Simple Circuit Breaker implementation */ class CircuitBreaker { threshold; failures = 0; lastFailureTime = 0; state = 'closed'; cooldownPeriod = 60000; // 1 minute constructor(threshold) { this.threshold = threshold; } isOpen() { if (this.state === 'open') { // Check if cooldown period has passed if (Date.now() - this.lastFailureTime > this.cooldownPeriod) { this.state = 'half-open'; return false; } return true; } return false; } recordSuccess() { this.failures = 0; this.state = 'closed'; } recordFailure() { this.failures++; this.lastFailureTime = Date.now(); if (this.failures >= this.threshold) { this.state = 'open'; console.warn(`🚨 Circuit breaker OPENED after ${this.failures} failures`); } } reset() { this.failures = 0; this.state = 'closed'; this.lastFailureTime = 0; } } //# sourceMappingURL=tool-execution-safety.js.map