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
JavaScript
/**
* 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