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