UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

271 lines (233 loc) 6.46 kB
/** * Production Retry Mechanisms * * Provides retry with: * - Exponential backoff * - Jitter * - Per-error-type configuration * - Circuit breaker integration * * @module @claude-flow/cli/production/retry */ // ============================================================================ // Types // ============================================================================ export interface RetryConfig { // Maximum retry attempts maxAttempts: number; // Initial delay in milliseconds initialDelayMs: number; // Maximum delay in milliseconds maxDelayMs: number; // Exponential backoff multiplier backoffMultiplier: number; // Add random jitter (0-1) jitter: number; // Errors that should not be retried nonRetryableErrors: string[]; // Custom retry decision function shouldRetry?: (error: Error, attempt: number) => boolean; // Called before each retry onRetry?: (error: Error, attempt: number, delayMs: number) => void; } export interface RetryResult<T> { success: boolean; result?: T; error?: Error; attempts: number; totalTimeMs: number; retryHistory: Array<{ attempt: number; error: string; delayMs: number }>; } export type RetryStrategy = 'exponential' | 'linear' | 'constant' | 'fibonacci'; // ============================================================================ // Default Configuration // ============================================================================ const DEFAULT_CONFIG: RetryConfig = { maxAttempts: 3, initialDelayMs: 1000, maxDelayMs: 30000, backoffMultiplier: 2, jitter: 0.1, nonRetryableErrors: [ 'validation', 'authentication', 'authorization', 'not_found', ], }; // ============================================================================ // Retry Implementation // ============================================================================ /** * Calculate delay for a given attempt */ function calculateDelay( attempt: number, config: RetryConfig, strategy: RetryStrategy = 'exponential' ): number { let delay: number; switch (strategy) { case 'linear': delay = config.initialDelayMs * attempt; break; case 'constant': delay = config.initialDelayMs; break; case 'fibonacci': delay = config.initialDelayMs * fibonacci(attempt); break; case 'exponential': default: delay = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt - 1); } // Apply max delay cap delay = Math.min(delay, config.maxDelayMs); // Apply jitter if (config.jitter > 0) { const jitterRange = delay * config.jitter; delay += (Math.random() - 0.5) * 2 * jitterRange; } return Math.round(Math.max(0, delay)); } /** * Fibonacci helper for fibonacci backoff */ function fibonacci(n: number): number { if (n <= 1) return 1; let a = 1, b = 1; for (let i = 2; i <= n; i++) { [a, b] = [b, a + b]; } return b; } /** * Check if an error should be retried */ function shouldRetryError( error: Error, attempt: number, config: RetryConfig ): boolean { // Check custom retry function if (config.shouldRetry) { return config.shouldRetry(error, attempt); } // Check non-retryable error patterns const message = error.message.toLowerCase(); for (const pattern of config.nonRetryableErrors) { if (message.includes(pattern.toLowerCase())) { return false; } } // Default: retry if we haven't exceeded max attempts return attempt < config.maxAttempts; } /** * Sleep for a given number of milliseconds */ function sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Execute a function with retry logic */ export async function withRetry<T>( fn: () => Promise<T>, config: Partial<RetryConfig> = {}, strategy: RetryStrategy = 'exponential' ): Promise<RetryResult<T>> { const finalConfig = { ...DEFAULT_CONFIG, ...config }; const startTime = Date.now(); const retryHistory: Array<{ attempt: number; error: string; delayMs: number }> = []; let lastError: Error | undefined; let attempt = 0; while (attempt < finalConfig.maxAttempts) { attempt++; try { const result = await fn(); return { success: true, result, attempts: attempt, totalTimeMs: Date.now() - startTime, retryHistory, }; } catch (error) { lastError = error as Error; // Check if we should retry if (!shouldRetryError(lastError, attempt, finalConfig)) { return { success: false, error: lastError, attempts: attempt, totalTimeMs: Date.now() - startTime, retryHistory, }; } // Calculate delay const delay = calculateDelay(attempt, finalConfig, strategy); // Record retry retryHistory.push({ attempt, error: lastError.message, delayMs: delay, }); // Call retry callback if (finalConfig.onRetry) { finalConfig.onRetry(lastError, attempt, delay); } // Wait before retrying if (attempt < finalConfig.maxAttempts) { await sleep(delay); } } } return { success: false, error: lastError, attempts: attempt, totalTimeMs: Date.now() - startTime, retryHistory, }; } /** * Create a retryable version of a function */ export function makeRetryable<T extends (...args: unknown[]) => Promise<unknown>>( fn: T, config: Partial<RetryConfig> = {}, strategy: RetryStrategy = 'exponential' ): (...args: Parameters<T>) => Promise<RetryResult<Awaited<ReturnType<T>>>> { return async (...args: Parameters<T>) => { return withRetry(() => fn(...args) as Promise<Awaited<ReturnType<T>>>, config, strategy); }; } /** * Retry decorator for class methods */ export function Retryable( config: Partial<RetryConfig> = {}, strategy: RetryStrategy = 'exponential' ) { return function ( _target: unknown, _propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value; descriptor.value = async function (...args: unknown[]) { const result = await withRetry( () => originalMethod.apply(this, args), config, strategy ); if (result.success) { return result.result; } throw result.error; }; return descriptor; }; } export default withRetry;