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

678 lines (677 loc) 26.5 kB
import { LayerManager } from '../core/LayerManager.js'; import { ClaudeCodeLayer } from '../layers/ClaudeCodeLayer.js'; import { GeminiCLILayer } from '../layers/GeminiCLILayer.js'; import { AIStudioLayer } from '../layers/AIStudioLayer.js'; import { logger } from '../utils/logger.js'; import { retry, safeExecute } from '../utils/errorHandler.js'; import { AuthVerifier } from '../auth/AuthVerifier.js'; export class WorkflowOrchestrator { layerManager; claudeLayer; geminiLayer; aiStudioLayer; authVerifier; MAX_CONCURRENT_STEPS = 5; MAX_WORKFLOW_DURATION = 30 * 60 * 1000; MAX_RETRY_ATTEMPTS = 3; constructor(config) { 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(config || defaultConfig); this.claudeLayer = new ClaudeCodeLayer(); this.geminiLayer = new GeminiCLILayer(); this.aiStudioLayer = new AIStudioLayer(); this.authVerifier = new AuthVerifier(); } async executeWorkflow(workflow) { return safeExecute(async () => { const startTime = Date.now(); logger.info('Starting workflow execution', { workflowId: workflow.id || 'unnamed', stepCount: workflow.steps?.length || 0, estimatedDuration: workflow.timeout || 'not specified', }); this.validateWorkflow(workflow); const executionPlan = await this.createExecutionPlan(workflow); await this.initializeRequiredLayers(executionPlan); const stepResults = await this.executeWorkflowSteps(workflow, executionPlan); const finalResult = await this.generateFinalResult(workflow, stepResults); const totalDuration = Date.now() - startTime; return { success: true, results: stepResults, summary: finalResult.summary, metadata: { total_duration: totalDuration, steps_completed: Object.keys(stepResults).length, steps_failed: this.countFailedSteps(stepResults), total_cost: this.calculateTotalCost(stepResults), }, }; }, { operationName: 'execute-workflow', layer: 'orchestrator', timeout: workflow.timeout || this.MAX_WORKFLOW_DURATION, }); } async createWorkflow(template, files, instructions, options) { const workflowId = `${template}_${Date.now()}`; switch (template) { case 'document_processing': return this.createDocumentProcessingWorkflow(workflowId, files, instructions, options); case 'content_analysis': return this.createContentAnalysisWorkflow(workflowId, files, instructions, options); case 'multimodal_pipeline': return this.createMultimodalPipelineWorkflow(workflowId, files, instructions, options); case 'research_workflow': return this.createResearchWorkflow(workflowId, files, instructions, options); default: throw new Error(`Unknown workflow template: ${template}`); } } async executePipeline(steps, options) { const workflow = { id: `pipeline_${Date.now()}`, steps: steps.map((step, index) => ({ id: `step_${index}`, action: step.action, layer: step.layer, input: step.input, dependsOn: step.dependsOn || [], timeout: 120000, })), parallel: options?.parallel || false, continueOnError: options?.continueOnError || false, timeout: options?.timeout || this.MAX_WORKFLOW_DURATION, }; return this.executeWorkflow(workflow); } async getWorkflowStatus(workflowId) { return { status: 'not_found', progress: 0, completedSteps: [], failedSteps: [], }; } validateWorkflow(workflow) { if (!workflow.steps || workflow.steps.length === 0) { throw new Error('Workflow must have at least one step'); } if (workflow.steps.length > 50) { throw new Error('Workflow cannot have more than 50 steps'); } const stepIds = new Set(workflow.steps.map(step => step.id)); for (const step of workflow.steps) { if (!step.id) { throw new Error('Each workflow step must have an ID'); } if (!step.action) { throw new Error(`Step ${step.id} must have an action`); } if (!step.layer) { throw new Error(`Step ${step.id} must specify a layer`); } if (step.dependsOn) { for (const depId of step.dependsOn) { if (!stepIds.has(depId)) { throw new Error(`Step ${step.id} depends on non-existent step ${depId}`); } } } } this.detectCircularDependencies(workflow.steps); } detectCircularDependencies(steps) { const visited = new Set(); const recursionStack = new Set(); const stepMap = new Map(steps.map(step => [step.id, step])); const hasCycle = (stepId) => { if (recursionStack.has(stepId)) { return true; } if (visited.has(stepId)) { return false; } visited.add(stepId); recursionStack.add(stepId); const step = stepMap.get(stepId); if (step?.dependsOn) { for (const depId of step.dependsOn) { if (hasCycle(depId)) { return true; } } } recursionStack.delete(stepId); return false; }; for (const step of steps) { if (hasCycle(step.id)) { throw new Error('Circular dependency detected in workflow'); } } } async createExecutionPlan(workflow) { return retry(async () => { const sortedSteps = this.topologicalSort(workflow.steps); const executionPhases = this.groupStepsByPhase(sortedSteps, workflow.parallel || false); const resourceEstimates = await this.estimateResources(sortedSteps); const strategy = this.determineExecutionStrategy(workflow, executionPhases, resourceEstimates); return { id: workflow.id || `workflow-${Date.now()}`, workflow: workflow, input_data: {}, execution_mode: strategy === 'hybrid' ? 'adaptive' : strategy, estimated_duration: resourceEstimates.reduce((sum, est) => sum + est.estimated_duration, 0), estimated_cost: resourceEstimates.reduce((sum, est) => sum + (est.estimated_cost || 0), 0), priority: 'medium', created_at: new Date(), }; }, { maxAttempts: 2, delay: 1000, operationName: 'create-execution-plan', }); } topologicalSort(steps) { const result = []; const visited = new Set(); const tempVisited = new Set(); const stepMap = new Map(steps.map(step => [step.id, step])); const visit = (stepId) => { if (tempVisited.has(stepId)) { throw new Error('Circular dependency detected'); } if (visited.has(stepId)) { return; } tempVisited.add(stepId); const step = stepMap.get(stepId); if (step?.dependsOn) { for (const depId of step.dependsOn) { visit(depId); } } tempVisited.delete(stepId); visited.add(stepId); if (step) { result.push(step); } }; for (const step of steps) { if (!visited.has(step.id)) { visit(step.id); } } return result; } groupStepsByPhase(steps, allowParallel) { if (!allowParallel) { return steps.map(step => [step]); } const phases = []; const processedSteps = new Set(); while (processedSteps.size < steps.length) { const currentPhase = []; for (const step of steps) { if (processedSteps.has(step.id)) { continue; } const canExecute = !step.dependsOn || step.dependsOn.every(depId => processedSteps.has(depId)); if (canExecute && currentPhase.length < this.MAX_CONCURRENT_STEPS) { currentPhase.push(step); processedSteps.add(step.id); } } if (currentPhase.length === 0) { throw new Error('Unable to resolve workflow dependencies'); } phases.push(currentPhase); } return phases; } async estimateResources(steps) { const estimates = []; for (const step of steps) { let duration = 30000; let cost = 0; let memory = 256; let cpu = 0.5; switch (step.layer) { case 'claude': duration = 60000; cost = 0.01; memory = 512; cpu = 1.0; break; case 'gemini': duration = 30000; cost = 0; memory = 256; cpu = 0.5; break; case 'aistudio': duration = 120000; cost = 0.005; memory = 1024; cpu = 0.8; break; } if (step.action.includes('complex') || step.action.includes('analysis')) { duration *= 2; cost *= 1.5; memory *= 1.5; } estimates.push({ estimated_duration: duration, estimated_cost: cost, estimated_tokens: memory * 100, complexity_score: Math.min(cpu / 10, 10), recommended_execution_mode: 'adaptive', required_capabilities: [step.layer], }); } return estimates; } determineExecutionStrategy(workflow, phases, estimates) { const totalSteps = workflow.steps?.length || 0; const totalDuration = estimates.reduce((sum, est) => sum + est.estimated_duration, 0); const maxPhaseSize = Math.max(...phases.map(phase => phase.length)); if (totalSteps <= 3 || totalDuration < 60000) { return 'sequential'; } if (maxPhaseSize > 1 && workflow.parallel) { return 'parallel'; } return 'hybrid'; } extractRequiredLayers(steps) { const layers = new Set(); steps.forEach(step => layers.add(step.layer)); return Array.from(layers); } async initializeRequiredLayers(plan) { const initPromises = []; const requiredLayers = this.extractRequiredLayers(plan.workflow.steps); for (const layer of requiredLayers) { switch (layer) { case 'claude': initPromises.push(this.claudeLayer.initialize()); break; case 'gemini': initPromises.push(this.geminiLayer.initialize()); break; case 'aistudio': initPromises.push(this.aiStudioLayer.initialize()); break; } } await Promise.all(initPromises); logger.debug('Initialized layers for workflow', { layers: requiredLayers }); } groupStepsIntoPhases(steps) { const phases = []; const completed = new Set(); const remaining = [...steps]; while (remaining.length > 0) { const readySteps = remaining.filter(step => !step.dependsOn || step.dependsOn.every(dep => completed.has(dep))); if (readySteps.length === 0) { throw new Error('Circular dependency detected in workflow steps'); } phases.push(readySteps); readySteps.forEach(step => { completed.add(step.id); const index = remaining.indexOf(step); remaining.splice(index, 1); }); } return phases; } async executeWorkflowSteps(workflow, plan) { const results = {}; const context = {}; const phases = this.groupStepsIntoPhases(plan.workflow.steps); for (const phase of phases) { logger.info(`Executing workflow phase with ${phase.length} step(s)`); const phasePromises = phase.map((step) => this.executeWorkflowStep(step, context, results)); const phaseResults = await Promise.allSettled(phasePromises); for (let i = 0; i < phase.length; i++) { const step = phase[i]; const result = phaseResults[i]; if (!step || !result) { continue; } if (result.status === 'fulfilled') { results[step.id] = result.value; context[step.id] = result.value.data; logger.debug('Step completed successfully', { stepId: step.id, duration: result.value.metadata?.duration, }); } else { const error = result.reason; logger.error('Step failed', { stepId: step.id, error: error instanceof Error ? error.message : 'Unknown error', }); results[step.id] = { success: false, error: error instanceof Error ? error.message : 'Unknown error', data: null, metadata: { layer: step.layer, duration: 0, }, }; if (!workflow.continueOnError) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new Error(`Workflow failed at step ${step.id}: ${errorMessage}`); } } } } return results; } async executeWorkflowStep(step, context, previousResults) { return retry(async () => { logger.debug('Executing workflow step', { stepId: step.id, layer: step.layer, action: step.action, }); const stepInput = this.prepareStepInput(step, context, previousResults); let result; switch (step.layer) { case 'claude': result = await this.claudeLayer.execute({ action: step.action, ...stepInput, }); break; case 'gemini': result = await this.geminiLayer.execute({ action: step.action, ...stepInput, }); break; case 'aistudio': result = await this.aiStudioLayer.execute({ action: step.action, ...stepInput, }); break; default: throw new Error(`Unknown layer: ${step.layer}`); } return result; }, { maxAttempts: this.MAX_RETRY_ATTEMPTS, delay: 2000, operationName: `execute-step-${step.id}`, }); } prepareStepInput(step, context, previousResults) { let input = { ...step.input }; if (typeof input === 'object' && input !== null) { input = this.replaceContextPlaceholders(input, context); } if (step.dependsOn && step.dependsOn.length > 0) { const dependencyResults = step.dependsOn.map(depId => ({ stepId: depId, result: previousResults[depId]?.data, })); input.dependencies = dependencyResults; } return input; } replaceContextPlaceholders(obj, context) { if (typeof obj === 'string') { return obj.replace(/\{\{(\w+)\}\}/g, (match, stepId) => { return context[stepId] || match; }); } if (Array.isArray(obj)) { return obj.map(item => this.replaceContextPlaceholders(item, context)); } if (typeof obj === 'object' && obj !== null) { const result = {}; for (const [key, value] of Object.entries(obj)) { result[key] = this.replaceContextPlaceholders(value, context); } return result; } return obj; } async generateFinalResult(workflow, stepResults) { try { const summaryPrompt = `Generate a summary of the workflow execution results. Workflow had ${Object.keys(stepResults).length} steps.`; const summaryResult = await this.claudeLayer.synthesizeResponse({ request: summaryPrompt, inputs: { workflowId: workflow.id, stepResults: Object.entries(stepResults).map(([stepId, result]) => ({ stepId, success: result.success, data: result.data, })), }, }); return { summary: summaryResult, }; } catch (error) { logger.warn('Failed to generate workflow summary', { error: error.message }); return { summary: `Workflow ${workflow.id} completed with ${Object.keys(stepResults).length} steps.`, }; } } countFailedSteps(stepResults) { return Object.values(stepResults).filter(result => !result.success).length; } calculateTotalCost(stepResults) { return Object.values(stepResults).reduce((total, result) => { return total + (result.metadata?.cost || 0); }, 0); } extractLayersUsed(stepResults) { const layers = new Set(); Object.values(stepResults).forEach(result => { if (result.metadata?.layer) { layers.add(result.metadata.layer); } }); return Array.from(layers); } createDocumentProcessingWorkflow(id, files, instructions, options) { return { id, steps: [ { id: 'extract_content', layer: 'aistudio', action: 'document_analysis', input: { files, instructions: 'Extract all content and structure from documents' }, dependsOn: [], }, { id: 'analyze_content', layer: 'claude', action: 'complex_reasoning', input: { prompt: `${instructions}. Use the extracted content: {{extract_content}}`, depth: options?.depth || 'medium', }, dependsOn: ['extract_content'], }, { id: 'generate_insights', layer: 'claude', action: 'synthesize_response', input: { request: 'Generate insights and conclusions from the analysis', inputs: { analysis: '{{analyze_content}}' }, }, dependsOn: ['analyze_content'], }, ], parallel: false, continueOnError: false, timeout: 600000, }; } createContentAnalysisWorkflow(id, files, instructions, options) { return { id, steps: [ { id: 'process_multimodal', layer: 'aistudio', action: 'multimodal_processing', input: { files, instructions: 'Process and analyze all content types' }, dependsOn: [], }, { id: 'contextual_grounding', layer: 'gemini', action: 'grounded_search', input: { prompt: `${instructions}. Context: {{process_multimodal}}`, useSearch: true, }, dependsOn: ['process_multimodal'], }, { id: 'comprehensive_analysis', layer: 'claude', action: 'complex_reasoning', input: { prompt: `Perform comprehensive analysis: ${instructions}`, context: 'Multimodal: {{process_multimodal}}, Grounded: {{contextual_grounding}}', depth: 'deep', }, dependsOn: ['process_multimodal', 'contextual_grounding'], }, ], parallel: true, continueOnError: false, timeout: 900000, }; } createMultimodalPipelineWorkflow(id, files, instructions, options) { return { id, steps: [ { id: 'extract_multimodal', layer: 'aistudio', action: 'multimodal_processing', input: { files, instructions: 'Extract and process all multimodal content' }, dependsOn: [], }, { id: 'enhance_with_search', layer: 'gemini', action: 'grounded_search', input: { prompt: `Enhance understanding with current information: {{extract_multimodal}}`, useSearch: true, }, dependsOn: ['extract_multimodal'], }, { id: 'synthesize_final', layer: 'claude', action: 'synthesize_response', input: { request: instructions, inputs: { multimodal: '{{extract_multimodal}}', grounded: '{{enhance_with_search}}', }, }, dependsOn: ['extract_multimodal', 'enhance_with_search'], }, ], parallel: false, continueOnError: true, timeout: 1200000, }; } createResearchWorkflow(id, files, instructions, options) { return { id, steps: [ { id: 'initial_research', layer: 'gemini', action: 'grounded_search', input: { prompt: `Research background information: ${instructions}`, useSearch: true, }, dependsOn: [], }, { id: 'analyze_documents', layer: 'aistudio', action: 'document_analysis', input: { files, instructions: 'Analyze documents in context of research' }, dependsOn: [], }, { id: 'cross_reference', layer: 'claude', action: 'complex_reasoning', input: { prompt: `Cross-reference research with document analysis: ${instructions}`, context: 'Research: {{initial_research}}, Documents: {{analyze_documents}}', depth: 'deep', }, dependsOn: ['initial_research', 'analyze_documents'], }, { id: 'final_synthesis', layer: 'claude', action: 'synthesize_response', input: { request: 'Create comprehensive research synthesis', inputs: { research: '{{initial_research}}', documents: '{{analyze_documents}}', analysis: '{{cross_reference}}', }, }, dependsOn: ['cross_reference'], }, ], parallel: true, continueOnError: false, timeout: 1800000, }; } getAvailableTemplates() { return [ 'document_processing', 'content_analysis', 'multimodal_pipeline', 'research_workflow', ]; } getOrchestratorLimits() { return { maxConcurrentSteps: this.MAX_CONCURRENT_STEPS, maxWorkflowDuration: this.MAX_WORKFLOW_DURATION, maxRetryAttempts: this.MAX_RETRY_ATTEMPTS, }; } }