UNPKG

claude-gemini-multimodal-bridge

Version:

Enterprise-grade AI integration bridge connecting Claude Code, Gemini CLI, and Google AI Studio with intelligent routing and advanced multimodal processing capabilities

219 lines (218 loc) 8.11 kB
import { CGMBError, LayerError } from '../core/types.js'; import { logger } from './logger.js'; export class ErrorHandler { static defaultRetryOptions = { maxRetries: 3, delay: 1000, backoffMultiplier: 2, maxDelay: 10000, }; static async retry(operation, options = {}) { const opts = { ...this.defaultRetryOptions, ...options, maxRetries: options.maxAttempts || options.maxRetries || this.defaultRetryOptions.maxRetries }; let lastError; for (let attempt = 0; attempt <= opts.maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; if (attempt === opts.maxRetries) { throw new CGMBError(`Operation failed after ${opts.maxRetries} retries: ${lastError.message}`, 'MAX_RETRIES_EXCEEDED', undefined, { originalError: lastError, attempts: attempt + 1 }); } if (this.shouldNotRetry(error)) { throw error; } const delay = Math.min(opts.delay * Math.pow(opts.backoffMultiplier, attempt), opts.maxDelay); logger.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms`, { error: lastError.message, attempt: attempt + 1, maxRetries: opts.maxRetries, }); await this.sleep(delay); } } throw lastError; } static async safeExecute(operation, context) { const startTime = Date.now(); try { logger.debug(`Starting operation: ${context.operationName}`, context); let result; if (context.timeout) { result = await this.executeWithTimeout(operation, context.timeout, context.operationName); } else { result = await operation(); } const duration = Date.now() - startTime; logger.debug(`Operation completed: ${context.operationName}`, { ...context, duration, success: true, }); return result; } catch (error) { const duration = Date.now() - startTime; const enhancedError = this.enhanceError(error, context); logger.error(`Operation failed: ${context.operationName}`, { ...context, duration, error: enhancedError.message, stack: enhancedError.stack, }); throw enhancedError; } } static createCircuitBreaker(operation, options) { let failureCount = 0; let lastFailureTime = 0; let state = 'CLOSED'; return async (...args) => { const now = Date.now(); if (state === 'OPEN' && now - lastFailureTime > options.resetTimeout) { state = 'HALF_OPEN'; failureCount = 0; } if (state === 'OPEN') { throw new CGMBError('Circuit breaker is OPEN', 'CIRCUIT_BREAKER_OPEN', undefined, { failureCount, lastFailureTime }); } try { const result = await operation(...args); if (state === 'HALF_OPEN') { state = 'CLOSED'; failureCount = 0; } return result; } catch (error) { failureCount++; lastFailureTime = now; if (failureCount >= options.failureThreshold) { state = 'OPEN'; logger.warn('Circuit breaker opened', { failureCount, threshold: options.failureThreshold, }); } throw error; } }; } static handleLayerError(error, layer) { if (error instanceof LayerError) { return error; } let code = 'UNKNOWN_ERROR'; const message = error.message; if (error.message.includes('timeout')) { code = 'TIMEOUT_ERROR'; } else if (error.message.includes('rate limit')) { code = 'RATE_LIMIT_ERROR'; } else if (error.message.includes('authentication')) { code = 'AUTH_ERROR'; } else if (error.message.includes('not found')) { code = 'NOT_FOUND_ERROR'; } else if (error.message.includes('permission')) { code = 'PERMISSION_ERROR'; } return new LayerError(message, layer, { originalError: error, code, timestamp: new Date().toISOString(), }); } static async recoverFromError(error, recoveryStrategies) { logger.warn('Attempting error recovery', { error: error.message, strategiesCount: recoveryStrategies.length, }); for (let i = 0; i < recoveryStrategies.length; i++) { try { const result = await recoveryStrategies[i](); logger.info(`Recovery successful using strategy ${i + 1}`); return result; } catch (recoveryError) { logger.debug(`Recovery strategy ${i + 1} failed`, { error: recoveryError.message, }); if (i === recoveryStrategies.length - 1) { throw new CGMBError('All recovery strategies failed', 'RECOVERY_FAILED', undefined, { originalError: error, recoveryErrors: recoveryError }); } } } throw error; } static async sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } static async executeWithTimeout(operation, timeout, operationName) { const abortController = new AbortController(); let timeoutId; try { const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { abortController.abort(); reject(new CGMBError(`Operation '${operationName}' timed out after ${timeout}ms`, 'TIMEOUT_ERROR')); }, timeout); }); const result = await Promise.race([ operation(), timeoutPromise ]); if (timeoutId) { clearTimeout(timeoutId); } return result; } catch (error) { if (timeoutId) { clearTimeout(timeoutId); } throw error; } } static shouldNotRetry(error) { const nonRetryableErrors = [ 'AUTH_ERROR', 'PERMISSION_ERROR', 'VALIDATION_ERROR', 'NOT_FOUND_ERROR', ]; return nonRetryableErrors.some(code => error.message.includes(code) || error.code === code); } static enhanceError(error, context) { if (error instanceof CGMBError) { return error; } const enhancedError = context.layer ? new LayerError(error.message, context.layer, { originalError: error, context, timestamp: new Date().toISOString(), }) : new CGMBError(error.message, 'ENHANCED_ERROR', context.layer, { originalError: error, context, timestamp: new Date().toISOString(), }); if (error.stack) { enhancedError.stack = error.stack; } return enhancedError; } } export const retry = ErrorHandler.retry.bind(ErrorHandler); export const safeExecute = ErrorHandler.safeExecute.bind(ErrorHandler); export const handleLayerError = ErrorHandler.handleLayerError.bind(ErrorHandler); export const recoverFromError = ErrorHandler.recoverFromError.bind(ErrorHandler);