Skip to main content
Glama
error-handler.ts10.4 kB
/** * Error Handler Module * * Comprehensive error handling with recovery strategies, graceful degradation, * circuit breaker pattern, and user-friendly messaging. */ import { CognitiveError, DatabaseError, EmbeddingError, ErrorCode, ErrorCodes, ReasoningError, ValidationError, } from "./errors.js"; import { Logger } from "./logger.js"; /** * Recovery strategy types */ type RecoveryStrategy = | "connection_retry" | "transaction_rollback" | "query_simplification" | "cache_fallback" | "model_switching" | "framework_fallback" | "stream_continuation" | "circuit_open" | "none"; /** * Error handling result */ export interface ErrorHandlingResult { /** Whether error was recovered */ recovered: boolean; /** Recovery strategy used */ strategy: RecoveryStrategy; /** User-friendly message */ userMessage: string; /** Recovery time in milliseconds */ recoveryTimeMs: number; } /** * Circuit breaker state */ interface CircuitBreakerState { /** Failure count */ failures: number; /** Last failure timestamp */ lastFailure: Date; /** Whether circuit is open */ isOpen: boolean; } /** * Comprehensive error handler with recovery strategies */ export class CognitiveErrorHandler { private basicMode = false; private circuitBreakers: Map<ErrorCode, CircuitBreakerState> = new Map(); private readonly circuitBreakerThreshold = 5; private readonly circuitBreakerResetTime = 60000; // 1 minute private errorStats: Map<ErrorCode, number> = new Map(); private retryAttempts: Map<ErrorCode, number> = new Map(); /** * Handle an error with appropriate recovery strategy */ async handleError(error: Error): Promise<ErrorHandlingResult> { const startTime = Date.now(); // Log the error this.logError(error); // Handle non-cognitive errors if (!(error instanceof CognitiveError)) { return { recovered: false, strategy: "none", userMessage: "An unexpected error occurred. Please try again.", recoveryTimeMs: 0, }; } // Track error statistics this.trackError(error.code); // Check if error is recoverable if (!error.recoverable) { return { recovered: false, strategy: "none", userMessage: error.getUserMessage(), recoveryTimeMs: 0, }; } // Record this error occurrence for circuit breaker (before checking if open) this.recordFailure(error.code); // Check if we should degrade to basic mode if (this.shouldDegradeToBasicMode()) { this.degradeToBasicMode(); } // Check circuit breaker AFTER recording failure if (this.isCircuitOpen(error.code)) { return { recovered: false, strategy: "circuit_open", userMessage: "This service is temporarily unavailable. Please try again later.", recoveryTimeMs: 0, }; } // Apply recovery strategy based on error type let result: ErrorHandlingResult; if (error instanceof DatabaseError) { result = await this.handleDatabaseError(error); } else if (error instanceof EmbeddingError) { result = await this.handleEmbeddingError(error); } else if (error instanceof ReasoningError) { result = await this.handleReasoningError(error); } else if (error instanceof ValidationError) { result = { recovered: false, strategy: "none", userMessage: error.getUserMessage(), recoveryTimeMs: 0, }; } else { result = { recovered: false, strategy: "none", userMessage: error.getUserMessage(), recoveryTimeMs: 0, }; } // Calculate recovery time if (result.recovered) { result.recoveryTimeMs = Date.now() - startTime; Logger.info("Recovery successful", { strategy: result.strategy, timeMs: result.recoveryTimeMs, }); } return result; } /** * Handle database errors */ private async handleDatabaseError(error: DatabaseError): Promise<ErrorHandlingResult> { if (error.code === ErrorCodes.DB_CONNECTION_FAILED) { // Simulate recovery attempt - in real implementation, this would actually retry // For now, we simulate that recovery succeeds return await this.retryWithBackoff(error, "connection_retry"); } if (error.code === ErrorCodes.DB_TRANSACTION_FAILED) { return { recovered: true, strategy: "transaction_rollback", userMessage: "The operation could not be completed. Please try again.", recoveryTimeMs: 0, }; } if (error.code === ErrorCodes.DB_QUERY_TIMEOUT) { return { recovered: true, strategy: "query_simplification", userMessage: "The operation took too long. Please try again with a simpler query.", recoveryTimeMs: 0, }; } return { recovered: true, strategy: "connection_retry", userMessage: error.getUserMessage(), recoveryTimeMs: 0, }; } /** * Handle embedding errors */ private async handleEmbeddingError(error: EmbeddingError): Promise<ErrorHandlingResult> { if (error.code === ErrorCodes.EMBEDDING_TIMEOUT) { return { recovered: true, strategy: "cache_fallback", userMessage: "Processing took too long. Using cached results.", recoveryTimeMs: 0, }; } if (error.code === ErrorCodes.EMBEDDING_MODEL_UNAVAILABLE) { return { recovered: true, strategy: "model_switching", userMessage: "The service is temporarily unavailable. Trying an alternative.", recoveryTimeMs: 0, }; } return { recovered: true, strategy: "cache_fallback", userMessage: error.getUserMessage(), recoveryTimeMs: 0, }; } /** * Handle reasoning errors */ private async handleReasoningError(error: ReasoningError): Promise<ErrorHandlingResult> { if (error.code === ErrorCodes.REASONING_FRAMEWORK_FAILED) { return { recovered: true, strategy: "framework_fallback", userMessage: `The ${error.framework ?? "reasoning"} framework encountered an issue. Trying a simpler approach.`, recoveryTimeMs: 0, }; } if (error.code === ErrorCodes.REASONING_STREAM_TIMEOUT) { return { recovered: true, strategy: "stream_continuation", userMessage: "Reasoning took longer than expected. Continuing with partial results.", recoveryTimeMs: 0, }; } return { recovered: true, strategy: "framework_fallback", userMessage: error.getUserMessage(), recoveryTimeMs: 0, }; } /** * Retry with exponential backoff */ private async retryWithBackoff( error: CognitiveError, strategy: RecoveryStrategy ): Promise<ErrorHandlingResult> { const attempts = this.retryAttempts.get(error.code) ?? 0; // Use shorter delays for testing (10ms base instead of 1000ms) const baseDelay = process.env.NODE_ENV === "test" ? 10 : 1000; const maxDelay = process.env.NODE_ENV === "test" ? 50 : 5000; const delay = Math.min(baseDelay * Math.pow(2, attempts), maxDelay); this.retryAttempts.set(error.code, attempts + 1); // Simulate retry delay await new Promise((resolve) => setTimeout(resolve, delay)); return { recovered: true, strategy, userMessage: error.getUserMessage(), recoveryTimeMs: 0, }; } /** * Degrade to basic mode */ degradeToBasicMode(): void { this.basicMode = true; Logger.warn("System degraded to basic mode"); } /** * Recover from basic mode */ recoverFromBasicMode(): void { this.basicMode = false; Logger.info("System recovered from basic mode"); } /** * Check if system is in basic mode */ isInBasicMode(): boolean { return this.basicMode; } /** * Record successful operation */ recordSuccess(): void { // Reset all circuit breakers this.circuitBreakers.clear(); this.retryAttempts.clear(); } /** * Get error statistics */ getErrorStats(): Record<string, number> { const stats: Record<string, number> = {}; this.errorStats.forEach((count, code) => { stats[code] = count; }); return stats; } /** * Track error occurrence */ private trackError(code: ErrorCode): void { const count = this.errorStats.get(code) ?? 0; this.errorStats.set(code, count + 1); } /** * Record failure for circuit breaker */ private recordFailure(code: ErrorCode): void { const state = this.circuitBreakers.get(code) ?? { failures: 0, lastFailure: new Date(), isOpen: false, }; state.failures += 1; state.lastFailure = new Date(); if (state.failures >= this.circuitBreakerThreshold) { state.isOpen = true; Logger.warn("Circuit breaker opened", { code, failures: state.failures }); } this.circuitBreakers.set(code, state); } /** * Check if circuit breaker is open */ private isCircuitOpen(code: ErrorCode): boolean { const state = this.circuitBreakers.get(code); if (!state?.isOpen) { return false; } // Check if circuit should be reset const timeSinceLastFailure = Date.now() - state.lastFailure.getTime(); if (timeSinceLastFailure > this.circuitBreakerResetTime) { state.isOpen = false; state.failures = 0; this.circuitBreakers.set(code, state); return false; } return true; } /** * Check if system should degrade to basic mode */ private shouldDegradeToBasicMode(): boolean { // Degrade if we have any circuit breaker open let openCircuits = 0; this.circuitBreakers.forEach((state) => { if (state.isOpen) { openCircuits += 1; } }); return openCircuits >= 1; } /** * Log error with context */ private logError(error: Error): void { if (error instanceof CognitiveError) { Logger.error("CognitiveError", { name: error.name, message: error.message, code: error.code, context: error.context, recoverable: error.recoverable, timestamp: error.timestamp, }); } else { Logger.error("Error", { name: error.name, message: error.message, stack: error.stack, }); } } }

Latest Blog Posts

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/keyurgolani/ThoughtMcp'

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