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
508 lines (507 loc) • 19.1 kB
JavaScript
import { execSync, spawn } from 'child_process';
import { join } from 'path';
import { logger } from '../utils/logger.js';
import { retry, safeExecute } from '../utils/errorHandler.js';
import { AuthVerifier } from '../auth/AuthVerifier.js';
import { isPlatformWindows } from '../utils/platformUtils.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,
shell: isPlatformWindows()
});
let output = '';
let errorOutput = '';
const timeoutId = setTimeout(() => {
isPlatformWindows() ? child.kill() : 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 isWindows = process.platform === 'win32';
const appDataPath = process.env.APPDATA || '';
const possiblePaths = [
'claude',
'claude-original',
...(isWindows && appDataPath ? [
join(appDataPath, 'npm', 'claude.cmd'),
join(appDataPath, 'npm', '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 cmd = isWindows ? 'where claude' : 'which claude 2>/dev/null';
const output = execSync(cmd, {
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;
}
}