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

332 lines (331 loc) 13 kB
import { spawn } from 'child_process'; import { logger } from '../utils/logger.js'; import { safeExecute } from '../utils/errorHandler.js'; import { RequestAnalyzer } from './RequestAnalyzer.js'; import { CapabilityDetector } from '../intelligence/CapabilityDetector.js'; import { LayerManager } from '../core/LayerManager.js'; export class ClaudeProxy { requestAnalyzer; capabilityDetector; layerManager; ORIGINAL_CLAUDE_PATHS = [ 'claude-original', '/usr/local/bin/claude-original', '/opt/homebrew/bin/claude-original', ]; constructor() { this.requestAnalyzer = new RequestAnalyzer(); this.capabilityDetector = new CapabilityDetector(); const defaultConfig = { gemini: { api_key: '', model: 'gemini-2.5-pro', timeout: 60000, max_tokens: 16384, temperature: 0.2 }, claude: { code_path: '/usr/local/bin/claude', timeout: 300000 }, aistudio: { enabled: true, max_files: 10, max_file_size: 100 }, cache: { enabled: true, ttl: 3600 }, logging: { level: 'info' }, }; this.layerManager = new LayerManager(defaultConfig); } async handleClaudeCommand(args) { const startTime = Date.now(); try { logger.info('Processing Claude command', { args: args.length > 0 ? args : ['<empty>'], timestamp: new Date().toISOString(), }); const request = { args, originalCommand: `claude ${args.join(' ')}`, workingDirectory: process.cwd(), environment: process.env, timestamp: new Date(), }; const analysis = await this.requestAnalyzer.analyze(args); logger.debug('Request analysis completed', { canEnhance: analysis.canEnhance, enhancementType: analysis.enhancementType, confidence: analysis.confidence, }); if (analysis.canEnhance && !analysis.fallbackToOriginal) { const plan = await this.requestAnalyzer.generateEnhancementPlan(analysis); const capabilityCheck = await this.capabilityDetector.canExecuteEnhancement(plan); if (capabilityCheck.canExecute) { logger.info('Executing enhanced Claude command', { enhancementType: plan.type, layers: plan.layers, availableLayers: capabilityCheck.availableLayers, }); await this.executeEnhanced(request, plan, analysis); return; } else { logger.warn('Enhancement not possible, falling back to original Claude', { requiredLayers: plan.layers, missingLayers: capabilityCheck.missingLayers, }); } } logger.info('Executing original Claude command'); await this.executePassthrough(request); } catch (error) { const duration = Date.now() - startTime; logger.error('Claude command execution failed', { error: error.message, args, duration, }); await this.emergencyFallback(args); } } async executeEnhanced(request, plan, analysis) { return safeExecute(async () => { const startTime = Date.now(); try { await this.layerManager.initializeLayers(); const workflowDefinition = { id: `proxy-workflow-${Date.now()}`, steps: plan.layers.map((layer, index) => ({ id: `step-${index}`, layer: layer, action: this.getActionForLayer(layer, analysis), input: { request: request.originalCommand, args: request.args, enhancementType: plan.type, }, timeout: plan.estimatedDuration, })), timeout: plan.estimatedDuration ? plan.estimatedDuration + 30000 : 120000, }; const context = { request: request.originalCommand, args: request.args, }; const options = { executionMode: 'adaptive', timeout: plan.estimatedDuration || 300000, }; const result = await this.layerManager.executeWorkflow(workflowDefinition, context, options); const duration = Date.now() - startTime; if (result.success) { logger.info('Enhanced execution completed successfully', { duration, layersUsed: Object.keys(result.results), totalCost: result.metadata.total_cost, }); this.outputEnhancedResult(result, plan); } else { logger.warn('Enhanced execution failed, falling back', { duration, errors: Object.values(result.results).filter(r => !r.success).map(r => r.error), }); await this.executePassthrough(request); } } catch (error) { logger.error('Enhanced execution error', { error: error.message, plan, }); await this.executePassthrough(request); } }, { operationName: 'execute-enhanced', layer: 'claude', timeout: plan.estimatedDuration ? plan.estimatedDuration + 60000 : 180000, }); } async executePassthrough(request) { return safeExecute(async () => { const claudePath = await this.findOriginalClaude(); if (!claudePath) { throw new Error('Original Claude Code not found. Please ensure Claude Code is installed.'); } logger.debug('Executing passthrough to original Claude', { claudePath, args: request.args, }); await this.spawnClaudeProcess(claudePath, request.args); }, { operationName: 'execute-passthrough', layer: 'claude', timeout: 300000, }); } async emergencyFallback(args) { logger.warn('Executing emergency fallback'); try { const claudePath = await this.findOriginalClaude(); if (claudePath) { await this.spawnClaudeProcess(claudePath, args); return; } } catch (error) { logger.error('Emergency fallback failed', { error: error.message }); } console.error('CGMB Error: Unable to execute Claude command'); console.error('Please ensure Claude Code is properly installed:'); console.error(' npm install -g @anthropic-ai/claude-code'); console.error(''); console.error('For CGMB support: cgmb verify'); process.exit(1); } async findOriginalClaude() { for (const path of this.ORIGINAL_CLAUDE_PATHS) { try { const { spawn: testSpawn } = await import('child_process'); const child = testSpawn(path, ['--version'], { stdio: 'ignore' }); await new Promise((resolve, reject) => { const timeout = setTimeout(() => { child.kill(); reject(new Error('Timeout')); }, 5000); child.on('close', (code) => { clearTimeout(timeout); if (code === 0) { resolve(); } else { reject(new Error(`Exit code: ${code}`)); } }); child.on('error', reject); }); logger.debug('Found original Claude at', { path }); return path; } catch { continue; } } try { const { execSync } = await import('child_process'); const output = execSync('which claude 2>/dev/null || where claude 2>nul', { encoding: 'utf8', stdio: 'pipe' }); const paths = output.trim().split('\n').filter(p => p && !p.includes('cgmb')); if (paths.length > 0) { logger.debug('Found Claude in PATH', { path: paths[0] }); return paths[0] || null; } } catch { } logger.error('Original Claude Code not found'); return null; } async spawnClaudeProcess(claudePath, args) { return new Promise((resolve, reject) => { const child = spawn(claudePath, args, { stdio: 'inherit', cwd: process.cwd(), env: process.env, }); child.on('close', (code) => { if (code === 0) { resolve(); } else { reject(new Error(`Claude process exited with code ${code}`)); } }); child.on('error', (error) => { reject(new Error(`Failed to start Claude process: ${error.message}`)); }); process.on('SIGINT', () => { child.kill('SIGINT'); }); process.on('SIGTERM', () => { child.kill('SIGTERM'); }); }); } getActionForLayer(layer, analysis) { switch (layer) { case 'claude': if (analysis.enhancementType === 'reasoning') { return 'complex_reasoning'; } else { return 'synthesize_response'; } case 'gemini': if (analysis.enhancementType === 'grounding') { return 'grounded_search'; } else { return 'contextual_analysis'; } case 'aistudio': if (analysis.enhancementType === 'multimodal') { return 'multimodal_processing'; } else { return 'document_analysis'; } default: return 'execute'; } } outputEnhancedResult(result, plan) { const layerResults = Object.values(result.results); const primaryResult = layerResults.find(r => r.success && r.data); if (primaryResult?.data) { if (typeof primaryResult.data === 'string') { console.log(primaryResult.data); } else if (primaryResult.data.content) { console.log(primaryResult.data.content); } else { console.log(JSON.stringify(primaryResult.data, null, 2)); } } else { console.log('Enhanced analysis completed successfully.'); if (result.summary) { console.log(result.summary); } } if (process.env.CGMB_DEBUG) { console.error(`\n[CGMB Enhanced via ${plan.type}]`); } } async validateStartup() { try { const hasMinimal = await this.capabilityDetector.hasMinimalCapabilities(); if (!hasMinimal) { logger.warn('Minimal capabilities not available'); return false; } logger.info('CGMB proxy startup validation passed'); return true; } catch (error) { logger.error('Startup validation failed', { error: error.message }); return false; } } async getProxyStatus() { const capabilities = await this.capabilityDetector.getCapabilityStatus(); const originalClaudeAvailable = (await this.findOriginalClaude()) !== null; let status; if (capabilities.overall === 'full' && originalClaudeAvailable) { status = 'ready'; } else if (capabilities.overall === 'minimal' || originalClaudeAvailable) { status = 'degraded'; } else { status = 'offline'; } return { status, capabilities, originalClaudeAvailable, }; } }