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
JavaScript
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: '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,
};
}
}