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