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