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

1,210 lines 57.8 kB
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 { safeExecute } from '../utils/errorHandler.js'; import { CGMBError, } from './types.js'; export class LayerManager { claudeLayer = null; geminiLayer = null; aiStudioLayer = null; config; layerInitialized = { claude: false, gemini: false, aistudio: false }; claudeLayerPromise = null; geminiLayerPromise = null; aiStudioLayerPromise = null; constructor(config) { this.config = config; } getClaudeLayer() { if (!this.claudeLayer) { logger.info('Lazy initializing Claude Code layer'); this.claudeLayer = new ClaudeCodeLayer(); if (!this.layerInitialized.claude) { this.claudeLayer.initialize().then(() => { this.layerInitialized.claude = true; logger.info('Claude Code layer initialized via lazy loading'); }).catch(error => { logger.error('Failed to initialize Claude Code layer', error); }); } } return this.claudeLayer; } getGeminiLayer() { if (!this.geminiLayer) { logger.info('Lazy initializing Gemini CLI layer'); this.geminiLayer = new GeminiCLILayer(); } return this.geminiLayer; } getAIStudioLayer() { if (!this.aiStudioLayer) { logger.info('Lazy initializing AI Studio layer'); this.aiStudioLayer = new AIStudioLayer(this.getGeminiLayer()); if (!this.layerInitialized.aistudio) { this.aiStudioLayer.initialize().then(() => { this.layerInitialized.aistudio = true; logger.info('AI Studio layer initialized via lazy loading'); }).catch(error => { logger.error('Failed to initialize AI Studio layer', error); }); } } return this.aiStudioLayer; } async getClaudeLayerAsync() { if (!this.claudeLayerPromise) { this.claudeLayerPromise = (async () => { if (!this.claudeLayer) { logger.info('Async initializing Claude Code layer'); this.claudeLayer = new ClaudeCodeLayer(); } if (!this.layerInitialized.claude) { await this.claudeLayer.initialize(); this.layerInitialized.claude = true; logger.info('Claude Code layer initialized (async)'); } return this.claudeLayer; })(); } return this.claudeLayerPromise; } async getGeminiLayerAsync() { if (!this.geminiLayerPromise) { this.geminiLayerPromise = (async () => { if (!this.geminiLayer) { logger.info('Async initializing Gemini CLI layer'); this.geminiLayer = new GeminiCLILayer(); } if (!this.layerInitialized.gemini) { await this.geminiLayer.initialize(); this.layerInitialized.gemini = true; logger.info('Gemini CLI layer initialized (async)'); } return this.geminiLayer; })(); } return this.geminiLayerPromise; } async getAIStudioLayerAsync() { if (!this.aiStudioLayerPromise) { this.aiStudioLayerPromise = (async () => { if (!this.aiStudioLayer) { logger.info('Async initializing AI Studio layer'); await this.getGeminiLayerAsync(); this.aiStudioLayer = new AIStudioLayer(this.geminiLayer); } if (!this.layerInitialized.aistudio) { await this.aiStudioLayer.initialize(); this.layerInitialized.aistudio = true; logger.info('AI Studio layer initialized (async)'); } return this.aiStudioLayer; })(); } return this.aiStudioLayerPromise; } async processSimpleFast(prompt, files) { logger.info('Fast simple processing', { promptLength: prompt.length, hasFiles: !!files?.length, mode: 'fast' }); try { const geminiLayer = await this.getGeminiLayerAsync(); const result = await geminiLayer.execute({ type: 'text_processing', prompt, files: files || [], useSearch: true }); logger.info('Fast processing completed', { duration: result.metadata?.duration, success: result.success }); return result; } catch (error) { logger.error('Fast processing failed', error); throw error; } } analyzeTask(task, userPreferredLayer) { const prompt = task.prompt || task.request || task.input || ''; const files = task.files || []; const fileTypes = files.map((f) => f.type || this.detectFileType(f.path)); const hasFiles = files.length > 0; const hasImages = fileTypes.some((type) => ['image'].includes(type)); const hasDocuments = fileTypes.some((type) => ['pdf', 'document', 'text', 'spreadsheet', 'presentation'].includes(type)); const hasAudio = fileTypes.some((type) => ['audio'].includes(type)); const hasVideo = fileTypes.some((type) => ['video'].includes(type)); const hasCodeFiles = fileTypes.some((type) => ['code', 'config', 'markup', 'stylesheet', 'database'].includes(type)); const promptLength = prompt.length; const isCodeRelated = this.detectCodeContent(prompt) || hasCodeFiles; const needsCurrentInfo = this.detectCurrentInfoNeed(prompt); const isGenerationTask = this.detectGenerationTask(prompt, task); const complexity = this.assessComplexity(prompt, files, task); if (userPreferredLayer) { return { complexity, hasFiles, fileTypes, needsCurrentInfo, isGenerationTask, isCodeRelated, estimatedTokens: Math.ceil(promptLength / 4) + files.length * 100, preferredLayer: userPreferredLayer, reasoning: `User explicitly requested ${userPreferredLayer} layer`, }; } let preferredLayer; let reasoning; if (hasCodeFiles) { if (isGenerationTask) { preferredLayer = 'aistudio'; reasoning = 'Code files with generation task - AI Studio for multimodal processing'; } else { preferredLayer = 'aistudio'; reasoning = 'Code files detected - AI Studio for code analysis'; } } else if (hasFiles && (hasImages || hasAudio || hasVideo)) { preferredLayer = 'aistudio'; reasoning = 'Media files requiring AI Studio multimodal processing'; } else if (hasFiles && hasDocuments) { preferredLayer = 'aistudio'; reasoning = 'Document files requiring AI Studio OCR and analysis capabilities'; } else if (isGenerationTask && (hasImages || hasAudio || hasVideo || prompt.includes('generate') || prompt.includes('create'))) { preferredLayer = 'aistudio'; reasoning = 'Generation task requiring AI Studio capabilities (Imagen 3, Veo 2)'; } else if (needsCurrentInfo || task.useSearch !== false || task.type === 'search') { preferredLayer = 'gemini'; reasoning = 'Current information or search required - Gemini CLI optimal'; } else if (complexity === 'high' || isCodeRelated || promptLength > 2000) { preferredLayer = 'claude'; reasoning = 'Complex reasoning or code analysis - Claude Code optimal'; } else if (complexity === 'low' && promptLength < 500) { preferredLayer = 'gemini'; reasoning = 'Simple task - Gemini CLI for speed'; } else { preferredLayer = 'aistudio'; reasoning = 'Default to AI Studio for balanced multimodal capabilities'; } const estimatedTokens = Math.ceil(promptLength / 4) + files.length * 100; return { complexity, hasFiles, fileTypes, needsCurrentInfo, isGenerationTask, isCodeRelated, estimatedTokens, preferredLayer, reasoning, }; } async executeWithOptimalLayer(task) { const userPreferredLayer = task.options?.preferredLayer; const analysis = this.analyzeTask(task, userPreferredLayer); logger.info('Optimal layer analysis completed', { preferredLayer: analysis.preferredLayer, complexity: analysis.complexity, reasoning: analysis.reasoning, hasFiles: analysis.hasFiles, fileTypes: analysis.fileTypes, userSpecified: !!userPreferredLayer, }); try { return await this.executeWithLayer(analysis.preferredLayer, task); } catch (error) { logger.warn('Primary layer execution failed, attempting fallback', { primaryLayer: analysis.preferredLayer, error: error.message, }); return await this.executeWithFallback(task, analysis.preferredLayer); } } async executeWithLayer(layerType, task) { switch (layerType) { case 'claude': const claudeLayer = await this.getClaudeLayerAsync(); return await claudeLayer.execute(task); case 'gemini': const geminiLayer = await this.getGeminiLayerAsync(); return await geminiLayer.execute(task); case 'aistudio': const aiStudioLayer = await this.getAIStudioLayerAsync(); return await aiStudioLayer.execute(task); default: throw new Error(`Unknown layer type: ${layerType}`); } } async executeWithFallback(task, failedLayer) { const fallbackOrder = this.getFallbackOrder(failedLayer, task); for (const layerType of fallbackOrder) { try { await this.ensureLayerInitialized(layerType); if (!this.layerInitialized[layerType]) { logger.warn(`Skipping ${layerType} - layer not initialized`, { originalLayer: failedLayer, }); continue; } logger.info('Attempting fallback execution', { layer: layerType, originalLayer: failedLayer, }); return await this.executeWithLayer(layerType, task); } catch (error) { logger.warn('Fallback layer also failed', { layer: layerType, error: error.message, }); continue; } } throw new Error(`All layers failed for task. Primary: ${failedLayer}, Fallbacks: ${fallbackOrder.join(', ')}`); } getFallbackOrder(failedLayer, task) { const analysis = this.analyzeTask(task); switch (failedLayer) { case 'claude': return analysis.hasFiles ? ['aistudio', 'gemini'] : ['gemini', 'aistudio']; case 'gemini': return analysis.complexity === 'high' ? ['aistudio', 'claude'] : ['aistudio', 'claude']; case 'aistudio': return analysis.complexity === 'high' ? ['gemini', 'claude'] : ['gemini', 'claude']; default: return ['aistudio', 'gemini', 'claude']; } } async ensureLayerInitialized(layerType) { if (layerType in this.layerInitialized && !this.layerInitialized[layerType]) { switch (layerType) { case 'claude': await this.getClaudeLayer().initialize(); this.layerInitialized.claude = true; break; case 'aistudio': await this.getAIStudioLayer().initialize(); this.layerInitialized.aistudio = true; break; case 'gemini': await this.getGeminiLayer().initialize(); this.layerInitialized.gemini = true; break; } } } detectFileType(path) { const ext = path.toLowerCase().split('.').pop() || ''; if (['ts', 'js', 'jsx', 'tsx', 'py', 'java', 'cpp', 'c', 'cs', 'go', 'rs', 'rb', 'php', 'swift', 'kt', 'scala', 'clj', 'r', 'sh', 'bash', 'zsh', 'ps1'].includes(ext)) { return 'code'; } if (['json', 'yaml', 'yml', 'toml', 'ini', 'conf', 'env', 'dockerfile', 'makefile', 'cmake', 'gradle', 'package-lock.json', 'yarn.lock'].includes(ext)) { return 'config'; } if (['html', 'xml', 'svg', 'vue', 'svelte', 'handlebars', 'hbs', 'mustache', 'pug', 'ejs', 'liquid'].includes(ext)) { return 'markup'; } if (['css', 'scss', 'sass', 'less', 'stylus'].includes(ext)) { return 'stylesheet'; } if (['sql', 'sqlite', 'db'].includes(ext)) { return 'database'; } if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tiff', 'ico', 'svg'].includes(ext)) { return 'image'; } if (['mp3', 'wav', 'm4a', 'flac', 'aac', 'ogg', 'wma'].includes(ext)) { return 'audio'; } if (['mp4', 'mov', 'avi', 'webm', 'mkv', 'flv', 'wmv', '3gp'].includes(ext)) { return 'video'; } if (['pdf'].includes(ext)) { return 'pdf'; } if (['doc', 'docx', 'odt', 'rtf', 'pages'].includes(ext)) { return 'document'; } if (['xls', 'xlsx', 'ods', 'csv', 'numbers'].includes(ext)) { return 'spreadsheet'; } if (['ppt', 'pptx', 'odp', 'keynote'].includes(ext)) { return 'presentation'; } if (['txt', 'md', 'rst', 'log', 'readme', 'license', 'changelog'].includes(ext)) { return 'text'; } return 'unknown'; } detectCodeContent(prompt) { const codeKeywords = [ 'function', 'class', 'import', 'export', 'const', 'let', 'var', 'def', 'if __name__', 'import ', 'from ', 'return', 'public', 'private', 'protected', 'static', 'code', 'programming', 'script', 'algorithm', 'debug', 'refactor' ]; const lowerPrompt = prompt.toLowerCase(); return codeKeywords.some(keyword => lowerPrompt.includes(keyword)) || /```/.test(prompt) || /\.(js|ts|py|java|cpp|c|go|rs|php)/.test(prompt); } detectCurrentInfoNeed(prompt) { const currentInfoKeywords = [ 'latest', 'recent', 'current', 'today', 'now', 'news', 'trends', '2024', '2025', 'this year', 'this month', 'this week', 'search', 'find', 'what is happening', 'breaking', 'update' ]; const lowerPrompt = prompt.toLowerCase(); return currentInfoKeywords.some(keyword => lowerPrompt.includes(keyword)); } detectGenerationTask(prompt, task) { const generationKeywords = [ 'generate', 'create', 'make', 'produce', 'build', 'design', 'draw', 'paint', 'compose', 'write', 'author', 'craft' ]; const lowerPrompt = prompt.toLowerCase(); return generationKeywords.some(keyword => lowerPrompt.includes(keyword)) || task.type === 'generation' || task.action === 'generate' || lowerPrompt.includes('image of') || lowerPrompt.includes('picture of'); } assessComplexity(prompt, files, task) { let score = 0; if (prompt.length > 2000) { score += 3; } else if (prompt.length > 500) { score += 2; } else if (prompt.length > 100) { score += 1; } if (files.length > 5) { score += 3; } else if (files.length > 2) { score += 2; } else if (files.length > 0) { score += 1; } if (task.type === 'workflow' || task.action === 'orchestrate') { score += 3; } if (task.type === 'analysis' && files.length > 0) { score += 2; } const complexKeywords = [ 'analyze', 'compare', 'evaluate', 'synthesize', 'optimize', 'complex', 'detailed', 'comprehensive', 'thorough', 'in-depth' ]; const lowerPrompt = prompt.toLowerCase(); const complexityMatches = complexKeywords.filter(keyword => lowerPrompt.includes(keyword)).length; score += complexityMatches; if (score >= 6) { return 'high'; } if (score >= 3) { return 'medium'; } return 'low'; } isSimplePrompt(prompt, files) { const filePathRegex = /(?:[A-Za-z]:\\[^\s"'<>|]+\.[a-zA-Z0-9]+|\/(?!https?:)[^\s"'<>|]+\.[a-zA-Z0-9]+|\.\.?\/[^\s"'<>|]+\.[a-zA-Z0-9]+)/gi; const embeddedPaths = prompt.match(filePathRegex) || []; if (embeddedPaths.length > 0) { logger.debug('File paths detected in prompt - not simple', { paths: embeddedPaths }); return false; } const isSimple = prompt.length < 1000 && (!files || files.length === 0) && !prompt.includes('workflow') && !prompt.includes('orchestrate') && !prompt.includes('generate image') && !prompt.includes('convert') && !prompt.includes('analyze multiple'); logger.debug('Simple prompt check', { promptLength: prompt.length, hasFiles: !!files?.length, isSimple }); return isSimple; } async initializeLayers() { logger.info('Layer initialization is now on-demand. Skipping bulk initialization.'); logger.info('Layers will be initialized when first accessed'); } async processMultimodal(prompt, files, workflow, options) { logger.info('LayerManager.processMultimodal called', { workflow, fileCount: files.length, promptLength: prompt.length, options: options ? Object.keys(options) : [] }); if (this.isSimplePrompt(prompt, files) && workflow === 'analysis') { logger.info('Using fast path for simple multimodal request'); try { const result = await this.processSimpleFast(prompt, files); return { success: result.success, results: [result], metadata: { workflow: 'analysis', execution_mode: 'fast', total_duration: result.metadata?.duration || 0, steps_completed: 1, steps_failed: 0, layers_used: ['gemini'], optimization: 'fast-path-bypass' } }; } catch (error) { logger.warn('Fast path failed, falling back to full workflow', { error: error.message }); } } if (files.length > 0 && files.length <= 2 && workflow === 'analysis') { logger.info('Using single-file fast path for analysis', { fileCount: files.length, fileTypes: files.map(f => f.type || 'unknown') }); try { const aiStudioLayer = await this.getAIStudioLayerAsync(); const result = await aiStudioLayer.execute({ type: 'multimodal_processing', action: 'analyze', prompt, files, options: options || {} }); return { success: result.success, results: [result], summary: result.success ? `File analysis completed successfully using AI Studio direct path.` : `File analysis failed: ${result.error || 'Unknown error'}`, metadata: { workflow: 'analysis', execution_mode: 'single-file-fast', total_duration: result.metadata?.duration || 0, steps_completed: result.success ? 1 : 0, steps_failed: result.success ? 0 : 1, layers_used: ['aistudio'], optimization: 'single-file-fast-path' } }; } catch (error) { logger.warn('Single-file fast path failed, falling back to full workflow', { error: error.message, fileCount: files.length }); } } const executionPlan = await this.createWorkflowPlan(workflow, { prompt, files, options: options || {}, }); return this.executeWorkflow(executionPlan, { prompt, files }, { executionMode: options?.execution_mode || 'adaptive', timeout: options?.timeout || 300000, }); } async analyzeDocuments(documents, analysisType, outputRequirements, options) { logger.info('Starting document analysis', { analysisType, documentCount: documents.length, outputRequirements, }); const executionPlan = { steps: [ { id: 'preprocess', layer: 'claude', action: 'analyze_requirements', input: { documents, analysisType, outputRequirements, }, }, { id: 'document_processing', layer: 'aistudio', action: 'process_documents', input: { documents, analysisType, }, dependsOn: ['preprocess'], }, { id: 'synthesis', layer: 'claude', action: 'synthesize_analysis', input: { analysisResults: '@document_processing.output', requirements: outputRequirements, }, dependsOn: ['document_processing'], }, ], fallbackStrategies: { aistudio_unavailable: { replace: 'document_processing', with: { id: 'fallback_processing', layer: 'gemini', action: 'analyze_documents', input: { documents, analysisType, }, }, }, }, }; return this.executeWorkflow(executionPlan, { documents, analysisType }, { executionMode: options?.execution_mode || 'sequential', timeout: options?.timeout || 300000, }); } async executeWorkflow(workflow, inputData, options) { const startTime = Date.now(); logger.info('Starting workflow execution', { stepsCount: workflow.steps.length, executionMode: options.executionMode, }); try { let results; switch (options.executionMode) { case 'sequential': results = await this.executeSequential(workflow, inputData, options); break; case 'parallel': results = await this.executeParallel(workflow, inputData, options); break; case 'adaptive': results = await this.executeAdaptive(workflow, inputData, options); break; default: throw new CGMBError(`Unknown execution mode: ${options.executionMode}`, 'INVALID_EXECUTION_MODE'); } const totalDuration = Date.now() - startTime; const successful = Object.values(results).filter(r => r.success).length; const failed = Object.values(results).length - successful; logger.info('Workflow execution completed', { totalDuration, stepsCompleted: successful, stepsFailed: failed, }); return { success: failed === 0, results, summary: this.generateWorkflowSummary(results), metadata: { total_duration: totalDuration, steps_completed: successful, steps_failed: failed, total_cost: this.calculateTotalCost(results), }, }; } catch (error) { const totalDuration = Date.now() - startTime; logger.error('Workflow execution failed', { error, totalDuration }); throw new CGMBError('Workflow execution failed', 'WORKFLOW_EXECUTION_FAILED', undefined, { originalError: error, duration: totalDuration }); } } async executeSequential(workflow, inputData, options) { const results = {}; const stepOutputs = {}; const sortedSteps = this.topologicalSort(workflow.steps); for (const step of sortedSteps) { try { const stepInput = this.resolveStepInput(step.input, stepOutputs, inputData); const result = await this.executeStep(step, stepInput, options); results[step.id] = result; if (result.success && result.data) { stepOutputs[step.id] = { output: result.data }; } logger.debug(`Step ${step.id} completed`, { success: result.success, duration: result.metadata.duration, }); } catch (error) { logger.error(`Step ${step.id} failed`, error); const fallbackResult = await this.tryFallbackStrategy(step, workflow, error, options); if (fallbackResult) { results[step.id] = fallbackResult; if (fallbackResult.success && fallbackResult.data) { stepOutputs[step.id] = { output: fallbackResult.data }; } } else { results[step.id] = { success: false, error: error.message, metadata: { layer: step.layer, duration: 0, }, }; } } } return results; } async executeParallel(workflow, inputData, options) { const results = {}; const dependencyLevels = this.groupStepsByDependencies(workflow.steps); for (const level of dependencyLevels) { const promises = level.map(async (step) => { try { const stepInput = this.resolveStepInput(step.input, results, inputData); const result = await this.executeStep(step, stepInput, options); return { stepId: step.id, result }; } catch (error) { logger.error(`Step ${step.id} failed in parallel execution`, error); return { stepId: step.id, result: { success: false, error: error.message, metadata: { layer: step.layer, duration: 0, }, }, }; } }); const levelResults = await Promise.all(promises); levelResults.forEach(({ stepId, result }) => { results[stepId] = result; }); } return results; } async executeAdaptive(workflow, inputData, options) { const analysis = await this.analyzeWorkload(workflow, inputData); logger.info('Adaptive execution analysis', analysis); if (analysis.requiresComplexReasoning) { return this.executeSequential(workflow, inputData, options); } else if (analysis.estimatedComplexity === 'low') { return this.executeParallel(workflow, inputData, options); } else { return this.executeHybrid(workflow, inputData, options, analysis); } } async analyzeWorkload(workflow, inputData) { const hasMultimodalFiles = inputData.files && Array.isArray(inputData.files) && inputData.files.length > 0; const hasComplexPrompt = inputData.prompt && typeof inputData.prompt === 'string' && inputData.prompt.length > 1000; const multipleSteps = workflow.steps.length > 3; const isGenerationRequest = this.detectGenerationRequest(typeof inputData.prompt === 'string' ? inputData.prompt : '', workflow); const isImageGeneration = this.detectImageGeneration(typeof inputData.prompt === 'string' ? inputData.prompt : '', workflow); const isAudioGeneration = this.detectAudioGeneration(typeof inputData.prompt === 'string' ? inputData.prompt : '', workflow); const isDocumentProcessing = this.detectDocumentProcessing(typeof inputData.prompt === 'string' ? inputData.prompt : '', inputData.files); const requiresComplexReasoning = hasComplexPrompt || multipleSteps; const requiresMultimodalProcessing = hasMultimodalFiles || isGenerationRequest; const requiresGrounding = false || (typeof inputData.prompt === 'string' && (inputData.prompt.toLowerCase().includes('search') || inputData.prompt.toLowerCase().includes('latest') || inputData.prompt.toLowerCase().includes('current') || inputData.prompt.toLowerCase().includes('today') || inputData.prompt.toLowerCase().includes('weather') || inputData.prompt.toLowerCase().includes('news') || inputData.prompt.toLowerCase().includes('stock') || inputData.prompt.toLowerCase().includes('now') || inputData.prompt.toLowerCase().includes('recent') || inputData.prompt.toLowerCase().includes('update') || inputData.prompt.includes('検索') || inputData.prompt.includes('最新') || inputData.prompt.includes('今日') || inputData.prompt.includes('天気') || inputData.prompt.includes('ニュース') || inputData.prompt.includes('株価') || inputData.prompt.includes('現在'))); let estimatedComplexity = 'low'; if (multipleSteps && hasMultimodalFiles) { estimatedComplexity = 'high'; } else if (hasComplexPrompt || hasMultimodalFiles || multipleSteps || isGenerationRequest) { estimatedComplexity = 'medium'; } let recommendedLayer = 'gemini'; const isSimplePrompt = !hasMultimodalFiles && !isGenerationRequest && !requiresComplexReasoning && !multipleSteps; if (requiresGrounding && !hasMultimodalFiles) { recommendedLayer = 'gemini'; logger.info('Routing to Gemini CLI for web search', { prompt: typeof inputData.prompt === 'string' ? inputData.prompt.substring(0, 100) + '...' : 'No prompt', requiresGrounding: true }); } else if (isImageGeneration || isAudioGeneration || isGenerationRequest) { recommendedLayer = 'aistudio'; logger.info('Routing to AI Studio for generation task', { isImageGeneration, isAudioGeneration, prompt: typeof inputData.prompt === 'string' ? inputData.prompt.substring(0, 100) + '...' : 'No prompt' }); } else if (isDocumentProcessing) { recommendedLayer = 'aistudio'; logger.info('Routing to AI Studio for document processing', { hasFiles: hasMultimodalFiles, prompt: typeof inputData.prompt === 'string' ? inputData.prompt.substring(0, 100) + '...' : 'No prompt' }); } else if (requiresComplexReasoning) { recommendedLayer = 'claude'; } else if (requiresMultimodalProcessing) { recommendedLayer = 'aistudio'; } else if (isSimplePrompt) { recommendedLayer = 'gemini'; logger.info('Routing simple prompt to Gemini CLI', { promptLength: typeof inputData.prompt === 'string' ? inputData.prompt.length : 0, hasFiles: hasMultimodalFiles, requiresGrounding }); } logger.info('Workload analysis completed', { hasMultimodalFiles, hasComplexPrompt, multipleSteps, isGenerationRequest, isImageGeneration, isAudioGeneration, isDocumentProcessing, isSimplePrompt, requiresComplexReasoning, requiresMultimodalProcessing, requiresGrounding, estimatedComplexity, recommendedLayer, prompt: typeof inputData.prompt === 'string' ? inputData.prompt.substring(0, 200) + '...' : 'No prompt' }); return { requiresComplexReasoning: !!requiresComplexReasoning, requiresMultimodalProcessing: !!requiresMultimodalProcessing, requiresGrounding: !!requiresGrounding, estimatedComplexity, recommendedLayer, confidence: 0.7, }; } async executeHybrid(workflow, inputData, options, analysis) { const stepGroups = this.groupStepsByRecommendedLayer(workflow.steps, analysis); const results = {}; for (const [_priority, steps] of Object.entries(stepGroups)) { if (steps.length === 1) { const step = steps[0]; const stepInput = this.resolveStepInput(step.input, results, inputData); results[step.id] = await this.executeStep(step, stepInput, options); } else { const promises = steps.map(async (step) => { const stepInput = this.resolveStepInput(step.input, results, inputData); return { stepId: step.id, result: await this.executeStep(step, stepInput, options), }; }); const stepResults = await Promise.all(promises); stepResults.forEach(({ stepId, result }) => { results[stepId] = result; }); } } return results; } async tryFallbackStrategy(failedStep, workflow, error, options) { if (!workflow.fallbackStrategies) { return null; } const strategyKey = `${failedStep.layer}_unavailable`; const strategy = workflow.fallbackStrategies[strategyKey]; if (!strategy || strategy.replace !== failedStep.id) { return null; } logger.info(`Attempting fallback strategy for step ${failedStep.id}`, { strategy: strategyKey, fallbackLayer: strategy.with.layer, }); try { const fallbackInput = this.resolveStepInput(strategy.with.input, {}, failedStep.input); return await this.executeStep(strategy.with, fallbackInput, options); } catch (fallbackError) { logger.error(`Fallback strategy failed for step ${failedStep.id}`, fallbackError); return null; } } async createWorkflowPlan(workflowType, context) { switch (workflowType) { case 'analysis': return this.createAnalysisWorkflow(context); case 'conversion': return this.createConversionWorkflow(context); case 'extraction': return this.createExtractionWorkflow(context); case 'generation': return this.createGenerationWorkflow(context); default: throw new CGMBError(`Unknown workflow type: ${workflowType}`, 'INVALID_WORKFLOW_TYPE'); } } createAnalysisWorkflow(context) { return { steps: [ { id: 'preprocess', layer: 'claude', action: 'analyze_requirements', input: { prompt: context.prompt, files: context.files, analysisType: 'multimodal_analysis', }, }, { id: 'multimodal_analysis', layer: 'aistudio', action: 'process_multimodal', input: { files: context.files, instructions: context.prompt, options: context.options, }, dependsOn: ['preprocess'], }, { id: 'synthesis', layer: 'claude', action: 'synthesize_results', input: { analysis_results: '@multimodal_analysis.output', original_prompt: context.prompt, }, dependsOn: ['multimodal_analysis'], }, ], fallbackStrategies: { aistudio_unavailable: { replace: 'multimodal_analysis', with: { id: 'fallback_analysis', layer: 'gemini', action: 'analyze_with_grounding', input: { prompt: context.prompt, files: context.files, }, }, }, }, }; } createConversionWorkflow(context) { return { steps: [ { id: 'format_analysis', layer: 'claude', action: 'analyze_conversion_requirements', input: { files: context.files, targetFormat: context.prompt, }, }, { id: 'file_conversion', layer: 'aistudio', action: 'convert_files', input: { files: context.files, conversion_instructions: context.prompt, options: context.options, }, dependsOn: ['format_analysis'], }, { id: 'quality_check', layer: 'gemini', action: 'validate_conversion', input: { original_files: context.files, converted_results: '@file_conversion.output', }, dependsOn: ['file_conversion'], }, ], }; } createExtractionWorkflow(context) { return { steps: [ { id: 'extraction_planning', layer: 'claude', action: 'plan_extraction', input: { files: context.files, extraction_requirements: context.prompt, }, }, { id: 'data_extraction', layer: 'aistudio', action: 'extract_data', input: { files: context.files, extraction_plan: '@extraction_planning.output', options: context.options, }, dependsOn: ['extraction_planning'], }, { id: 'structure_data', layer: 'claude', action: 'structure_extracted_data', input: { raw_data: '@data_extraction.output', requirements: context.prompt, }, dependsOn: ['data_extraction'], }, ], }; } createGenerationWorkflow(context) { return { steps: [ { id: 'content_analysis', layer: 'aistudio', action: 'analyze_source_content', input: { files: context.files, generation_goals: context.prompt, }, }, { id: 'generation_strategy', layer: 'claude', action: 'develop_generation_strategy', input: { content_analysis: '@content_analysis.output', requirements: context.prompt, }, dependsOn: ['content_analysis'], }, { id: 'content_generation', layer: 'aistudio', action: 'generate_content', input: { strategy: '@generation_strategy.output', source_files: context.files, prompt: context.prompt, options: { generation_type: 'auto_detect', priority_models: { image: 'imagen-3', video: 'veo-2', audio: 'text-to-speech' } } }, dependsOn: ['generation_strategy'], }, ], }; } getLayer(layerType) { switch (layerType) { case 'claude': return this.getClaudeLayer(); case 'gemini': return this.getGeminiLayer(); case 'aistudio': return this.getAIStudioLayer(); default: throw new CGMBError(`Unknown layer type: ${layerType}`, 'INVALID_LAYER_TYPE'); } } async getLayerAsync(layerType) { switch (layerType) { case 'claude': return await this.getClaudeLayerAsync(); case 'gemini': return await this.getGeminiLayerAsync(); case 'aistudio': return await this.getAIStudioLayerAsync(); default: throw new CGMBError(`Unknown layer type: ${layerType}`, 'INVALID_LAYER_TYPE'); } } topologicalSort(steps) { const sorted = []; const visited = new Set(); const visiting = new Set(); const visit = (step) => { if (visiting.has(step.id)) { throw new CGMBError(`Circular dependency detected at step: ${step.id}`, 'CIRCULAR_DEPENDENCY'); } if (visited.has(step.id)) { return; } visiting.add(step.id); const dependencies = step.dependsOn || []; for (const depId of dependencies) { const depStep = steps.find(s => s.id === depId); if (depStep) { visit(depStep); } } visiting.delete(step.id); visited.add(step.id); sorted.push(step); }; for (const step of steps) { visit(step); } return sorted; } groupStepsByDependencies(steps) { const levels = []; const processed = new Set(); while (processed.size < steps.length) { const currentLevel = steps.filter(step => { if (processed.has(step.id)) { return false; } const dependencies = step.dependsOn || []; return dependencies.every(dep => processed.has(dep)); }); if (currentLevel.length === 0) { throw new CGMBError('Unable to resolve step dependencies', 'DEPENDENCY_RESOLUTION_FAILED'); } levels.push(currentLevel); currentLevel.forEach(step => processed.add(step.id)); } return levels; } groupStepsByRecommendedLayer(steps, analysis) { const groups = { high: [], medium: [], low: [], }; steps.forEach(step => { if (step.layer === analysis.recommendedLayer) { groups.high.push(step); } else if (step.layer === 'claude') { groups.medium.push(step); } else { groups.low.push(step); } }); return groups; } resolveStepInput(input, stepOutputs, baseInput) { const resolved = { ...baseInput }; for (const [key, value] of Object.entries(input)) { if (typeof value === 'string' && value.startsWith('@')) { const [stepId, ...pathParts] = value.slice(1).split('.'); const stepOutput = stepOutputs[stepId]; if (stepOutput) { const layerResult = stepOutput; if (layerResult.success === false) { logger.warn(`Step reference ${value} points to failed step ${stepId}`, { stepId, error: layerResult.error || 'Unknown error' }); resolved[key] = { _stepFailed: true, _stepId: stepId, _error: layerResult.error || 'Previous step failed', _originalReference: value }; continue; } let resolvedValue = stepOutput; for (const part of pathParts) { if (typeof resolvedValue === 'object' && resolvedValue !== null) { resolvedValue = resolvedValue[part]; } else { resolvedValue = undefined; break; } } resolved[key] = resolvedValue; } else { logger.warn(`Could not resolve step reference: ${value}`, { stepId, availableSteps: Object.keys(stepOutputs), hint: 'The referenced step may have failed or not been executed' }); resolved[key] = { _stepMissing: true, _stepId: stepId, _originalReference: value, _hint: 'Referenced step output not available - step may have failed or not executed' }; } } else { resolved[key] = value; } } return resolved; } generateWorkflowSummary(results) { const total = Object.keys(results).length; const successful = Object.values(results).filter(r => r.success).length; const failed = total - successful; if (failed === 0) { return `All ${total} workflow steps completed successfully.`; } else if (successful === 0) { return `All ${total} workflow steps failed.`; } else { return `${successful}/${total} workflow steps completed successfully, ${failed} failed.`; } } calculateTotalCost(results) { return Object.values(results).reduce((total, result) => { return to