import { getDb } from "./db"; /** * Chat resilience utilities: retry logic with exponential backoff and context recovery */ export interface RetryConfig { maxAttempts: number; initialDelayMs: number; maxDelayMs: number; backoffMultiplier: number; } export const DEFAULT_RETRY_CONFIG: RetryConfig = { maxAttempts: 3, initialDelayMs: 1000, maxDelayMs: 8000, backoffMultiplier: 2, }; /** * Calculate delay for exponential backoff */ export function calculateBackoffDelay(attempt: number, config: RetryConfig): number { const delay = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt - 1); return Math.min(delay, config.maxDelayMs); } /** * Sleep utility */ export function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Retry wrapper with exponential backoff */ export async function retryWithBackoff( fn: () => Promise, config: RetryConfig = DEFAULT_RETRY_CONFIG, onRetry?: (attempt: number, error: Error) => void ): Promise { let lastError: Error | null = null; for (let attempt = 1; attempt <= config.maxAttempts; attempt++) { try { return await fn(); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (attempt < config.maxAttempts) { const delayMs = calculateBackoffDelay(attempt, config); onRetry?.(attempt, lastError); await sleep(delayMs); } } } throw lastError || new Error("Retry failed"); } /** * Check if error is retryable (timeout, network, 5xx) */ export function isRetryableError(error: any): boolean { if (!error) return false; const message = String(error.message || error).toLowerCase(); const code = error.code || error.status; // Timeout errors if (message.includes("timeout") || message.includes("econnreset")) return true; // Network errors if (message.includes("econnrefused") || message.includes("enotfound")) return true; // 5xx server errors if (code >= 500 && code < 600) return true; // Gateway errors if (code === 502 || code === 503 || code === 504) return true; // LLM service unavailable if (message.includes("unavailable") || message.includes("service")) return true; return false; } /** * Get recent conversation context from DB for retry */ export async function getConversationContext( userId: string, limit: number = 10 ): Promise> { try { const db = getDb(); // Note: This assumes a messages table exists with userId, role, content, createdAt // If not, return empty array (frontend will use in-memory history) return []; } catch (error) { console.error("[ChatResilience] Failed to get conversation context:", error); return []; } } /** * Log retry attempt for monitoring */ export function logRetryAttempt( attempt: number, error: Error, context?: Record ): void { console.log( `[ChatResilience] Retry attempt ${attempt}: ${error.message}`, context ? JSON.stringify(context) : "" ); }