Skip to main content
Glama

In Memoria

circuit-breaker.ts7.17 kB
/** * Circuit Breaker pattern implementation for external API calls * Protects against cascading failures and provides fallback mechanisms */ export enum CircuitState { CLOSED = 'CLOSED', // Normal operation OPEN = 'OPEN', // Circuit is open, calls fail fast HALF_OPEN = 'HALF_OPEN' // Testing if service is back } export interface CircuitBreakerOptions { failureThreshold: number; // Number of failures before opening recoveryTimeout: number; // Time to wait before half-open (ms) requestTimeout: number; // Individual request timeout (ms) monitoringWindow: number; // Time window for failure counting (ms) } export interface CircuitBreakerStats { state: CircuitState; failures: number; successes: number; lastFailureTime?: number; totalRequests: number; } export interface ErrorDetails { message: string; state: CircuitState; failures: number; lastFailureTime?: number; successRate: number; timeSinceLastFailure?: number; stats: CircuitBreakerStats; } export class CircuitBreakerError extends Error { public readonly details: ErrorDetails; public readonly options: CircuitBreakerOptions; constructor(message: string, details: any, options: CircuitBreakerOptions) { super(message); this.name = 'CircuitBreakerError'; this.details = details; this.options = options; } toString(): string { return `${this.name}: ${this.message}\nDetails: ${JSON.stringify(this.details, null, 2)}`; } } export class CircuitBreaker { private state: CircuitState = CircuitState.CLOSED; private failures: number = 0; private successes: number = 0; private lastFailureTime?: number; private totalRequests: number = 0; private requestTimeouts: number[] = []; constructor(private options: CircuitBreakerOptions) {} async execute<T>(operation: () => Promise<T>, fallback?: () => Promise<T>): Promise<T> { this.totalRequests++; // Clean old failure records outside monitoring window this.cleanOldFailures(); // Fast fail if circuit is open if (this.state === CircuitState.OPEN) { if (this.canAttemptReset()) { this.state = CircuitState.HALF_OPEN; } else { // Don't mask the real error - provide detailed failure information const errorDetails = this.getDetailedErrorInfo(); if (fallback) { console.warn(`Circuit breaker OPEN: ${errorDetails.message}. Using fallback.`); return await fallback(); } // Throw a proper error with context instead of generic message throw new CircuitBreakerError( 'Circuit breaker is OPEN', errorDetails, this.options ); } } try { // Execute with timeout const result = await this.executeWithTimeout(operation, this.options.requestTimeout); // Success this.onSuccess(); return result; } catch (error) { // Failure - preserve original error details this.onFailure(); // Try fallback if (fallback) { try { console.warn(`Primary operation failed: ${error instanceof Error ? error.message : String(error)}. Using fallback.`); return await fallback(); } catch (fallbackError: unknown) { // Throw the original error with fallback context, not just original error throw new CircuitBreakerError( 'Both primary and fallback operations failed', { primaryError: error instanceof Error ? error.message : String(error), fallbackError: fallbackError instanceof Error ? fallbackError.message : String(fallbackError), state: this.state, failures: this.failures, stats: this.getStats() }, this.options ); } } // No fallback - throw original error with circuit context throw error; } } private async executeWithTimeout<T>( operation: () => Promise<T>, timeoutMs: number ): Promise<T> { return new Promise<T>((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error(`Operation timed out after ${timeoutMs}ms`)); }, timeoutMs); operation() .then(result => { clearTimeout(timeoutId); resolve(result); }) .catch(error => { clearTimeout(timeoutId); reject(error); }); }); } private onSuccess(): void { this.successes++; if (this.state === CircuitState.HALF_OPEN) { // Reset to closed after successful test this.state = CircuitState.CLOSED; this.failures = 0; this.requestTimeouts = []; } } private onFailure(): void { this.failures++; this.lastFailureTime = Date.now(); this.requestTimeouts.push(this.lastFailureTime); // Open circuit if threshold exceeded if (this.failures >= this.options.failureThreshold) { this.state = CircuitState.OPEN; } } private canAttemptReset(): boolean { return this.lastFailureTime !== undefined && Date.now() - this.lastFailureTime >= this.options.recoveryTimeout; } private cleanOldFailures(): void { const cutoff = Date.now() - this.options.monitoringWindow; this.requestTimeouts = this.requestTimeouts.filter(time => time > cutoff); this.failures = this.requestTimeouts.length; } getStats(): CircuitBreakerStats { return { state: this.state, failures: this.failures, successes: this.successes, lastFailureTime: this.lastFailureTime, totalRequests: this.totalRequests }; } reset(): void { this.state = CircuitState.CLOSED; this.failures = 0; this.successes = 0; this.lastFailureTime = undefined; this.totalRequests = 0; this.requestTimeouts = []; } private getDetailedErrorInfo(): ErrorDetails { const successRate = this.totalRequests > 0 ? this.successes / this.totalRequests : 0; const timeSinceLastFailure = this.lastFailureTime ? Date.now() - this.lastFailureTime : undefined; return { message: `Circuit breaker is ${this.state}. Failures: ${this.failures}/${this.options.failureThreshold}`, state: this.state, failures: this.failures, lastFailureTime: this.lastFailureTime, successRate, timeSinceLastFailure, stats: this.getStats() }; } } // Pre-configured circuit breakers for common services export const createOpenAICircuitBreaker = (): CircuitBreaker => { return new CircuitBreaker({ failureThreshold: 5, // 5 failures recoveryTimeout: 30000, // 30 seconds requestTimeout: 30000, // 30 seconds per request monitoringWindow: 300000 // 5 minute window }); }; export const createRustAnalyzerCircuitBreaker = (): CircuitBreaker => { return new CircuitBreaker({ failureThreshold: 3, // 3 failures (Rust should be more reliable) recoveryTimeout: 5000, // 5 seconds requestTimeout: 60000, // 60 seconds for complex analysis monitoringWindow: 120000 // 2 minute window }); };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pi22by7/In-Memoria'

If you have feedback or need assistance with the MCP directory API, please join our Discord server