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
JavaScript
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);