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,156 lines (1,155 loc) 46.9 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 }; 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 processSimpleFast(prompt, files) { logger.info('Fast simple processing', { promptLength: prompt.length, hasFiles: !!files?.length, mode: 'fast' }); try { const result = await this.getGeminiLayer().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) { 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'].includes(type)); const hasAudio = fileTypes.some((type) => ['audio'].includes(type)); const hasVideo = fileTypes.some((type) => ['video'].includes(type)); const promptLength = prompt.length; const isCodeRelated = this.detectCodeContent(prompt); const needsCurrentInfo = this.detectCurrentInfoNeed(prompt); const isGenerationTask = this.detectGenerationTask(prompt, task); const complexity = this.assessComplexity(prompt, files, task); let preferredLayer; let reasoning; if (hasFiles && (hasImages || hasDocuments || hasAudio || hasVideo)) { preferredLayer = 'aistudio'; reasoning = 'Multimodal files requiring AI Studio processing'; } 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 = 'claude'; reasoning = 'Default to Claude Code for balanced capabilities'; } const estimatedTokens = Math.ceil(promptLength / 4) + files.length * 100; return { complexity, hasFiles, fileTypes, needsCurrentInfo, isGenerationTask, isCodeRelated, estimatedTokens, preferredLayer, reasoning, }; } async executeWithOptimalLayer(task) { const analysis = this.analyzeTask(task); logger.info('Optimal layer analysis completed', { preferredLayer: analysis.preferredLayer, complexity: analysis.complexity, reasoning: analysis.reasoning, hasFiles: analysis.hasFiles, fileTypes: analysis.fileTypes, }); 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 = this.getClaudeLayer(); await this.ensureLayerInitialized('claude'); return await claudeLayer.execute(task); case 'gemini': const geminiLayer = this.getGeminiLayer(); return await geminiLayer.execute(task); case 'aistudio': const aiStudioLayer = this.getAIStudioLayer(); await this.ensureLayerInitialized('aistudio'); 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 { 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' ? ['claude', 'aistudio'] : ['aistudio', 'claude']; case 'aistudio': return analysis.complexity === 'high' ? ['claude', 'gemini'] : ['gemini', 'claude']; default: return ['claude', 'gemini', 'aistudio']; } } 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 (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(ext)) { return 'image'; } if (['mp3', 'wav', 'm4a', 'flac'].includes(ext)) { return 'audio'; } if (['mp4', 'mov', 'avi', 'webm'].includes(ext)) { return 'video'; } if (['pdf'].includes(ext)) { return 'pdf'; } if (['txt', 'md'].includes(ext)) { return 'text'; } if (['doc', 'docx'].includes(ext)) { return 'document'; } 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 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 }); } } 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'); } } 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) { 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}`); resolved[key] = value; } } 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 total + (result.metadata.cost || 0); }, 0); } detectGenerationRequest(prompt, workflow) { if (!prompt) { return false; } const generationKeywords = [ 'generate', 'create', 'make', 'produce', 'draw', 'design', '生成', '作成', '作る', '描く', 'つくる' ]; const isWorkflowGeneration = workflow.steps.some(step => step.action.includes('generate') || step.action.includes('create') || step.layer === 'aistudio'); const hasGenerationKeywords = generationKeywords.some(keyword => prompt.toLowerCase().includes(keyword.toLowerCase())); return isWorkflowGeneration || hasGenerationKeywords; } detectImageGeneration(prompt, _workflow) { if (!prompt) { return false; } const imageKeywords = [ 'image', 'picture', 'photo', 'illustration', 'drawing', 'artwork', 'visual', 'graphic', 'sketch', 'painting', 'render', '画像', '写真', 'イラスト', '絵', '図', 'ピクチャー' ]; const generationKeywords = [ 'generate', 'create', 'make', 'produce', 'draw', 'design', '生成', '作成', '作る', '描く' ]; const hasImageKeyword = imageKeywords.some(keyword => prompt.toLowerCase().includes(keyword.toLowerCase())); const hasGenerationKeyword = generationKeywords.some(keyword => prompt.toLowerCase().includes(keyword.toLowerCase())); return hasImageKeyword && hasGenerationKeyword; } detectAudioGeneration(prompt, _workflow) { if (!prompt) { return false; } const audioKeywords = [ 'audio', 'sound', 'music', 'voice', 'speech', 'narration', '音声', '音楽', 'サウンド', '声', 'ナレーション' ]; const generationKeywords = [ 'generate', 'create', 'make', 'produce', 'synthesize', '生成', '作成', '作る', '合成' ]; const hasAudioKeyword = audioKeywords.some(keyword => prompt.toLowerCase().includes(keyword.toLowerCase())); const hasGenerationKeyword = generationKeywords.some(keyword => prompt.toLowerCase().includes(keyword.toLowerCase())); return hasAudioKeyword && hasGenerationKeyword; } detectDocumentProcessing(prompt, files) { if (!prompt) { return false; } const documentKeywords = [ 'document', 'pdf', 'analyze', 'extract', 'summarize', 'compare', 'text', 'file', 'content', 'read', 'process', 'ドキュメント', '文書', '分析', '抽出', '要約' ]; const hasDocumentKeyword = documentKeywords.some(keyword => prompt.toLowerCase().includes(keyword.toLowerCase())); let hasDocumentFiles = false; if (files && Array.isArray(files)) { hasDocumentFiles = files.some((file) => { const fileExt = file.path?.toLowerCase() || ''; return fileExt.endsWith('.pdf') || fileExt.endsWith('.docx') || fileExt.endsWith('.doc') || fileExt.endsWith('.txt'); }); } return hasDocumentKeyword || hasDocumentFiles; } async executeStep(step, input, options) { const startTime = Date.now(); logger.debug(`Executing step ${step.id} on ${step.layer} layer`, { stepId: step.id, layer: step.layer, action: step.action, }); try { const layer = this.getLayer(step.layer); const executionParams = { type: this.mapActionToTaskType(step.action), action: step.action, ...input, }; const result = await safeExecute(() => layer.execute(executionParams), { operationName: `execute-step-${step.id}`, layer: step.layer, timeout: options.timeout || 300000, }); const duration = Date.now() - startTime; return { success: true, data: result, metadata: { layer: step.layer, duration, model: step.action, }, }; } catch (error) { const duration = Date.now() - startTime; logger.error(`Step ${step.id} failed on ${step.layer} layer`, { error: error.message, duration, stepId: step.id, layer: step.layer, action: step.action, }); return { success: false, error: error.message, data: null, metadata: { layer: step.layer, duration, model: step.action, }, }; } } mapActionToTaskType(action) { const actionMap = { 'analyze_requirements': 'text_processing', 'process_multimodal': 'multimodal_processing', 'synthesize_results': 'text_processing', 'analyze_with_grounding': 'text_processing', 'process_documents': 'document_processing', 'extract_content': 'extraction', 'convert_format': 'conversion', 'analyze_source_content': 'content_analysis', 'develop_generation_strategy': 'strategy_development', 'generate_content': 'content_generation', }; return actionMap[action] || 'text_processing'; } }