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

314 lines โ€ข 10.8 kB
/** * Network Resilience Manager * Handles connection timeouts, retry logic, circuit breakers, and connection pooling */ import { Agent } from 'http'; import { Agent as HttpsAgent } from 'https'; import { AbortSignalManager } from './abort-signal-manager.js'; export class NetworkResilienceManager { static instance; httpAgent; httpsAgent; circuitBreakers = new Map(); connectionPool = new Map(); stats = { totalRequests: 0, successfulRequests: 0, failedRequests: 0, timeouts: 0, circuitBreakerTrips: 0, averageResponseTime: 0, activeConnections: 0 }; responseTimes = []; maxResponseTimesSamples = 100; constructor() { // Create HTTP agents with connection pooling this.httpAgent = new Agent({ keepAlive: true, maxSockets: 10, maxFreeSockets: 2, timeout: 30000, scheduling: 'fifo' }); this.httpsAgent = new HttpsAgent({ keepAlive: true, maxSockets: 10, maxFreeSockets: 2, timeout: 30000, scheduling: 'fifo' }); } static getInstance() { if (!NetworkResilienceManager.instance) { NetworkResilienceManager.instance = new NetworkResilienceManager(); } return NetworkResilienceManager.instance; } /** * Make a resilient HTTP request with retries and circuit breaker */ async makeRequest(url, options = {}) { const { timeout = 30000, maxRetries = 3, retryDelay = 1000, circuitBreakerThreshold = 5, ...fetchOptions } = options; const domain = this.getDomain(url); const circuitBreaker = this.getCircuitBreaker(domain, circuitBreakerThreshold); // Check circuit breaker if (circuitBreaker.isOpen()) { this.stats.circuitBreakerTrips++; throw new Error(`Circuit breaker open for domain: ${domain}`); } this.stats.totalRequests++; const startTime = Date.now(); let lastError; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { const { controller, timeoutId } = AbortSignalManager.createWithTimeout(timeout); // Add our agent for connection pooling const requestOptions = { ...fetchOptions, signal: controller.signal, // @ts-ignore - Node.js specific agent: url.startsWith('https:') ? this.httpsAgent : this.httpAgent }; try { const response = await fetch(url, requestOptions); clearTimeout(timeoutId); // Proper AbortSignal cleanup via manager AbortSignalManager.cleanupController(controller); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); // Record success metrics const responseTime = Date.now() - startTime; this.recordSuccess(responseTime); circuitBreaker.recordSuccess(); return result; } catch (fetchError) { clearTimeout(timeoutId); // Ensure proper cleanup even on error AbortSignalManager.cleanupController(controller); throw fetchError; } } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); // Record failure metrics this.recordFailure(lastError); circuitBreaker.recordFailure(); if (lastError.name === 'AbortError') { this.stats.timeouts++; console.warn(`โฐ Request timeout for ${url} (${timeout}ms)`); } console.warn(`โš ๏ธ Network request failed (attempt ${attempt + 1}/${maxRetries + 1}): ${lastError.message}`); // Don't retry on certain errors if (this.isNonRetryableError(lastError) || attempt === maxRetries) { break; } // Exponential backoff const backoffDelay = retryDelay * Math.pow(2, attempt); await this.sleep(backoffDelay); } } throw lastError || new Error('Network request failed after retries'); } /** * Make multiple concurrent requests with connection limiting */ async makeConcurrentRequests(requests, concurrencyLimit = 5) { const results = []; const executing = []; for (let i = 0; i < requests.length; i++) { const { url, options } = requests[i]; const task = this.makeRequest(url, options) .then(result => { results[i] = { success: true, result }; }) .catch(error => { results[i] = { success: false, error }; }); executing.push(task); // Limit concurrency if (executing.length >= concurrencyLimit) { await Promise.race(executing); executing.splice(executing.findIndex(p => p === task), 1); } } // Wait for remaining requests await Promise.allSettled(executing); return results; } /** * Test network connectivity to a URL */ async testConnectivity(url, timeoutMs = 5000) { try { const { controller, timeoutId } = AbortSignalManager.createWithTimeout(timeoutMs); try { const response = await fetch(url, { method: 'HEAD', signal: controller.signal, // @ts-ignore agent: url.startsWith('https:') ? this.httpsAgent : this.httpAgent }); clearTimeout(timeoutId); // Proper AbortSignal cleanup via manager AbortSignalManager.cleanupController(controller); return response.ok; } catch (fetchError) { clearTimeout(timeoutId); // Ensure proper cleanup even on error AbortSignalManager.cleanupController(controller); throw fetchError; } } catch (error) { return false; } } /** * Check if error is non-retryable */ isNonRetryableError(error) { const nonRetryablePatterns = [ '400', '401', '403', '404', '405', '422', // Client errors 'invalid json', 'syntax error', 'parse error', 'certificate', 'ssl', 'tls' ]; const errorMessage = error.message.toLowerCase(); return nonRetryablePatterns.some(pattern => errorMessage.includes(pattern)); } /** * Get or create circuit breaker for domain */ getCircuitBreaker(domain, threshold) { if (!this.circuitBreakers.has(domain)) { this.circuitBreakers.set(domain, new NetworkCircuitBreaker(threshold)); } return this.circuitBreakers.get(domain); } /** * Extract domain from URL */ getDomain(url) { try { return new URL(url).hostname; } catch { return 'unknown'; } } /** * Record successful request */ recordSuccess(responseTime) { this.stats.successfulRequests++; this.recordResponseTime(responseTime); } /** * Record failed request */ recordFailure(error) { this.stats.failedRequests++; } /** * Record response time for metrics */ recordResponseTime(responseTime) { this.responseTimes.push(responseTime); // Keep only recent samples if (this.responseTimes.length > this.maxResponseTimesSamples) { this.responseTimes.shift(); } // Update average this.stats.averageResponseTime = this.responseTimes.reduce((sum, time) => sum + time, 0) / this.responseTimes.length; } /** * Get network statistics */ getNetworkStats() { // Update active connections from agents this.stats.activeConnections = Object.keys(this.httpAgent.sockets).length + Object.keys(this.httpsAgent.sockets).length; return { ...this.stats }; } /** * Reset circuit breaker for domain */ resetCircuitBreaker(domain) { const circuitBreaker = this.circuitBreakers.get(domain); if (circuitBreaker) { circuitBreaker.reset(); console.log(`๐Ÿ”„ Reset network circuit breaker for ${domain}`); } } /** * Reset all circuit breakers */ resetAllCircuitBreakers() { for (const [domain, circuitBreaker] of this.circuitBreakers.entries()) { circuitBreaker.reset(); } console.log('๐Ÿ”„ Reset all network circuit breakers'); } /** * Close all connections and cleanup */ cleanup() { this.httpAgent.destroy(); this.httpsAgent.destroy(); this.circuitBreakers.clear(); this.connectionPool.clear(); console.log('๐Ÿงน Network resilience manager cleaned up'); } /** * Sleep utility */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } /** * Network-specific Circuit Breaker */ class NetworkCircuitBreaker { threshold; failures = 0; lastFailureTime = 0; state = 'closed'; cooldownPeriod = 30000; // 30 seconds for network issues constructor(threshold) { this.threshold = threshold; } isOpen() { if (this.state === 'open') { 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(`๐Ÿšจ Network circuit breaker OPENED after ${this.failures} failures`); } } reset() { this.failures = 0; this.state = 'closed'; this.lastFailureTime = 0; } } //# sourceMappingURL=network-resilience-manager.js.map