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

499 lines (498 loc) 18.7 kB
import { execSync, spawn } from 'child_process'; import { logger } from '../utils/logger.js'; import { retry, safeExecute } from '../utils/errorHandler.js'; import { AuthVerifier } from '../auth/AuthVerifier.js'; export class ClaudeCodeLayer { authVerifier; claudePath; isInitialized = false; isLightweightInitialized = false; lastAuthCheck = 0; AUTH_CACHE_TTL = 24 * 60 * 60 * 1000; DEFAULT_TIMEOUT = 300000; MAX_RETRIES = 3; constructor() { this.authVerifier = new AuthVerifier(); } async initializeLightweight() { if (this.isLightweightInitialized) { return; } logger.debug('Performing lightweight Claude Code initialization...'); if (!this.claudePath) { this.claudePath = await this.findClaudeCodePath() || ''; if (!this.claudePath) { throw new Error('Claude Code executable not found'); } } const now = Date.now(); if (now - this.lastAuthCheck > this.AUTH_CACHE_TTL) { const authResult = await this.authVerifier.verifyClaudeCodeAuth(); if (!authResult.success) { throw new Error(`Claude Code authentication failed: ${authResult.error}`); } this.lastAuthCheck = now; } this.isLightweightInitialized = true; logger.debug('Lightweight Claude Code initialization completed'); } async initialize() { return safeExecute(async () => { if (this.isInitialized) { return; } logger.info('Initializing Claude Code layer...'); const authResult = await this.authVerifier.verifyClaudeCodeAuth(); if (!authResult.success) { throw new Error(`Claude Code authentication failed: ${authResult.error}`); } this.claudePath = await this.findClaudeCodePath() || ''; if (!this.claudePath) { throw new Error('Claude Code executable not found'); } await this.testClaudeCodeConnection(); this.isInitialized = true; logger.info('Claude Code layer initialized successfully', { claudePath: this.claudePath, authenticated: authResult.success, }); }, { operationName: 'initialize-claude-code-layer', layer: 'claude', timeout: 30000, }); } async isAvailable() { try { if (!this.isInitialized) { await this.initialize(); } return this.isInitialized; } catch (error) { logger.debug('Claude Code layer not available', { error: error.message }); return false; } } canHandle(task) { if (!task || typeof task !== 'object') { return false; } if (task.type === 'claude_code' || task.action === 'execute' || task.action === 'complex_reasoning') { return true; } if (task.type === 'reasoning' || task.prompt) { return true; } if (task.type === 'workflow' || task.workflow) { return true; } if (task.action === 'synthesize_response' || task.request) { return true; } return false; } async execute(task) { return safeExecute(async () => { const startTime = Date.now(); if (!this.isInitialized && !this.isLightweightInitialized) { if (!task.workflow && !task.depth && task.action !== 'complex_reasoning') { await this.initializeLightweight(); } else { await this.initialize(); } } logger.info('Executing Claude Code task', { taskType: task.type || 'general', action: task.action || 'execute', }); let result; switch (task.action || task.type) { case 'complex_reasoning': const reasoningResult = await this.executeComplexReasoning(task); result = reasoningResult.reasoning || 'Reasoning completed'; break; case 'synthesize_response': result = await this.synthesizeResponse(task); break; case 'workflow': const workflowResult = await this.orchestrateWorkflow(task.workflow || task); result = workflowResult.summary || 'Workflow completed'; break; default: result = await this.executeGeneral(task); } const duration = Date.now() - startTime; return { success: true, data: result, metadata: { layer: 'claude', duration, tokens_used: this.estimateTokensUsed(task, result), cost: this.calculateCost(task, result), model: 'claude-code', }, }; }, { operationName: 'execute-claude-code-task', layer: 'claude', timeout: this.getTaskTimeout(task), }); } async executeComplexReasoning(task) { return retry(async () => { logger.debug('Executing complex reasoning task', { promptLength: task.prompt?.length || 0, depth: task.depth || 'medium', domain: task.domain, }); const prompt = this.buildReasoningPrompt({ prompt: task.prompt || 'Please provide reasoning', depth: task.depth, context: task.context, domain: task.domain, }); const result = await this.executeClaudeCommand(prompt, { timeout: this.DEFAULT_TIMEOUT, reasoning: true, }); return this.parseReasoningResult(result, { prompt: task.prompt || 'Please provide reasoning', depth: task.depth, context: task.context, domain: task.domain, }); }, { maxAttempts: this.MAX_RETRIES, delay: 2000, operationName: 'complex-reasoning', }); } async synthesizeResponse(task) { return retry(async () => { logger.debug('Synthesizing response', { inputSources: task.inputs ? Object.keys(task.inputs).length : 1, request: task.request?.substring(0, 100) + '...', }); const prompt = this.buildSynthesisPrompt(task); const result = await this.executeClaudeCommand(prompt, { timeout: this.DEFAULT_TIMEOUT, synthesis: true, }); return result.trim(); }, { maxAttempts: this.MAX_RETRIES, delay: 1500, operationName: 'synthesize-response', }); } async orchestrateWorkflow(workflow) { return retry(async () => { logger.info('Orchestrating workflow', { stepCount: workflow.steps?.length || 0, timeout: workflow.timeout || this.DEFAULT_TIMEOUT, }); const workflowDef = workflow; const prompt = this.buildWorkflowPrompt(workflowDef); const result = await this.executeClaudeCommand(prompt, { timeout: workflowDef.timeout || this.DEFAULT_TIMEOUT * 2, workflow: true, }); return this.parseWorkflowResult(result, workflowDef); }, { maxAttempts: this.MAX_RETRIES, delay: 3000, operationName: 'orchestrate-workflow', }); } getCapabilities() { return [ 'complex_reasoning', 'synthesize_response', 'workflow_orchestration', 'code_analysis', 'text_processing', 'general_intelligence', 'task_planning', 'problem_solving', ]; } getCost(task) { return 0; } getEstimatedDuration(task) { const baseTime = 5000; if (task.type === 'workflow' || task.action === 'workflow') { return baseTime * 3; } if (task.action === 'complex_reasoning') { return baseTime * 2; } if (task.prompt && task.prompt.length > 1000) { return baseTime * 1.5; } return baseTime; } async executeGeneral(task) { const prompt = task.prompt || task.request || task.input || 'Please help with this task.'; return await this.executeClaudeCommand(prompt, { timeout: this.getTaskTimeout(task), }); } async executeClaudeCommand(prompt, options = {}) { if (!this.claudePath) { throw new Error('Claude Code not initialized'); } const timeout = options.timeout || this.DEFAULT_TIMEOUT; return new Promise((resolve, reject) => { logger.debug('Executing Claude command', { promptLength: prompt.length, timeout, options, }); const child = spawn(this.claudePath, [prompt], { stdio: 'pipe', cwd: process.cwd(), env: process.env, }); let output = ''; let errorOutput = ''; const timeoutId = setTimeout(() => { child.kill('SIGKILL'); reject(new Error(`Claude Code execution timeout after ${timeout}ms`)); }, timeout); child.stdout.on('data', (data) => { output += data.toString(); }); child.stderr.on('data', (data) => { errorOutput += data.toString(); }); child.on('close', (code) => { clearTimeout(timeoutId); if (code === 0) { logger.debug('Claude command completed successfully', { outputLength: output.length, code, }); resolve(output); } else { const error = `Claude Code exited with code ${code}: ${errorOutput}`; logger.error('Claude command failed', { code, error: errorOutput }); reject(new Error(error)); } }); child.on('error', (error) => { clearTimeout(timeoutId); logger.error('Claude command process error', { error: error.message }); reject(error); }); }); } async findClaudeCodePath() { const possiblePaths = [ 'claude', 'claude-original', '/usr/local/bin/claude', '/usr/local/bin/claude-original', '/opt/homebrew/bin/claude', '/opt/homebrew/bin/claude-original', ]; for (const path of possiblePaths) { try { execSync(`${path} --version`, { stdio: 'ignore', timeout: 5000 }); logger.debug('Found Claude Code at', { path }); return path; } catch { continue; } } try { const output = execSync('which claude 2>/dev/null || where claude 2>nul', { encoding: 'utf8', stdio: 'pipe', timeout: 5000, }); const paths = output.trim().split('\n').filter(p => p && !p.includes('cgmb')); if (paths.length > 0) { return paths[0]; } } catch { } return undefined; } async testClaudeCodeConnection() { try { const { execSync } = await import('child_process'); try { const output = execSync(`${this.claudePath} --version`, { timeout: 30000, encoding: 'utf8', stdio: 'pipe' }); if (output && output.trim()) { logger.debug('Claude Code connection test successful via --version', { version: output.trim().substring(0, 100) }); return; } } catch (versionError) { logger.debug('Claude --version failed, trying --help', { error: versionError.message }); try { const helpOutput = execSync(`${this.claudePath} --help`, { timeout: 15000, encoding: 'utf8', stdio: 'pipe' }); if (helpOutput && (helpOutput.includes('Claude') || helpOutput.includes('Usage:') || helpOutput.length > 20)) { logger.debug('Claude Code connection test successful via --help', { helpLength: helpOutput.length }); return; } } catch (helpError) { logger.warn('Both --version and --help failed for Claude Code', { versionError: versionError.message, helpError: helpError.message }); } } try { const { access, constants } = await import('fs/promises'); await access(this.claudePath, constants.F_OK | constants.X_OK); logger.debug('Claude Code binary exists and is executable, considering it available'); return; } catch (accessError) { throw new Error(`Claude Code binary not accessible: ${accessError.message}`); } } catch (error) { throw new Error(`Claude Code connection test failed: ${error.message}`); } } buildReasoningPrompt(task) { let prompt = `Please provide detailed reasoning for the following:\n\n${task.prompt}`; if (task.context) { prompt += `\n\nContext: ${task.context}`; } if (task.depth) { const depthInstructions = { shallow: 'Provide a brief, high-level analysis.', medium: 'Provide a thorough analysis with key reasoning steps.', deep: 'Provide a comprehensive, step-by-step analysis with detailed justification.', }; prompt += `\n\nDepth: ${depthInstructions[task.depth]}`; } if (task.domain) { prompt += `\n\nDomain: Focus on ${task.domain} perspectives and principles.`; } prompt += '\n\nPlease structure your response with clear reasoning steps and a conclusion.'; return prompt; } buildSynthesisPrompt(task) { let prompt = 'Please synthesize and respond to the following:\n\n'; if (task.request) { prompt += `Request: ${task.request}\n\n`; } if (task.inputs && typeof task.inputs === 'object') { prompt += 'Input Sources:\n'; Object.entries(task.inputs).forEach(([source, content], index) => { prompt += `${index + 1}. ${source}: ${content}\n`; }); prompt += '\n'; } prompt += 'Please provide a comprehensive, well-structured response that synthesizes all the information.'; return prompt; } buildWorkflowPrompt(workflow) { let prompt = 'Please execute the following workflow:\n\n'; if (workflow.steps) { prompt += 'Steps:\n'; workflow.steps.forEach((step, index) => { prompt += `${index + 1}. ${step.action}: ${JSON.stringify(step.input)}\n`; }); prompt += '\n'; } prompt += 'Please execute each step and provide a comprehensive result.'; return prompt; } parseReasoningResult(output, task) { const lines = output.trim().split('\n'); const steps = []; let reasoning = ''; let conclusion = ''; let inSteps = false; for (const line of lines) { const trimmed = line.trim(); if (!trimmed) { continue; } if (/^\d+\./.test(trimmed) || /^[-*]/.test(trimmed)) { steps.push(trimmed); inSteps = true; } else if (inSteps && trimmed.toLowerCase().includes('conclusion')) { conclusion = trimmed; inSteps = false; } else if (!inSteps) { reasoning += trimmed + ' '; } } if (!reasoning && !conclusion) { reasoning = output.trim(); } return { reasoning: reasoning.trim() || output.trim(), conclusion: conclusion.trim() || 'Analysis completed.', confidence: 0.8, steps: steps.length > 0 ? steps : undefined, }; } parseWorkflowResult(output, workflow) { return { success: true, results: { workflow_execution: { success: true, data: output, metadata: { layer: 'claude', duration: 0, model: 'claude-code', }, }, }, summary: 'Workflow executed successfully via Claude Code', metadata: { total_duration: 0, steps_completed: workflow.steps?.length || 1, steps_failed: 0, total_cost: 0, }, }; } getTaskTimeout(task) { if (typeof task.timeout === 'number') { return task.timeout; } return this.getEstimatedDuration(task) + 30000; } estimateTokensUsed(task, result) { const inputText = JSON.stringify(task); const outputText = typeof result === 'string' ? result : JSON.stringify(result); return Math.ceil((inputText.length + outputText.length) / 4); } calculateCost(task, result) { return 0; } }