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
300 lines (299 loc) • 12.1 kB
JavaScript
import { logger } from '../utils/logger.js';
import { safeExecute } from '../utils/errorHandler.js';
export class RequestAnalyzer {
FILE_EXTENSIONS = {
multimodal: ['.pdf', '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.mp3', '.wav', '.m4a', '.mp4', '.mov', '.avi'],
document: ['.doc', '.docx', '.txt', '.md', '.rtf', '.odt'],
data: ['.csv', '.json', '.xml', '.yaml', '.yml'],
code: ['.js', '.ts', '.py', '.java', '.c', '.cpp', '.go', '.rs', '.php'],
};
GROUNDING_KEYWORDS = [
'latest', 'current', 'recent', 'today', 'now', 'search', 'find',
'what is happening', 'news', 'update', 'trending', 'live',
'real-time', 'up-to-date', 'fresh', 'new', 'breaking'
];
COMPLEXITY_INDICATORS = {
high: ['analyze', 'compare', 'evaluate', 'synthesize', 'comprehensive', 'detailed analysis'],
medium: ['explain', 'describe', 'summarize', 'review', 'assess', 'examine'],
low: ['list', 'show', 'display', 'print', 'output', 'format']
};
async analyze(args) {
return safeExecute(async () => {
const startTime = Date.now();
logger.debug('Starting request analysis', { args });
const input = args.join(' ');
const analysis = {
canEnhance: false,
requiredCapabilities: [],
fallbackToOriginal: false,
enhancementType: 'passthrough',
confidence: 0,
priority: 'low',
estimatedComplexity: 'simple',
};
if (this.isSystemCommand(args)) {
logger.debug('System command detected, no enhancement needed');
return {
...analysis,
fallbackToOriginal: true,
confidence: 1.0,
};
}
const fileAnalysis = this.analyzeFiles(args, input);
if (fileAnalysis.hasMultimodalFiles) {
analysis.canEnhance = true;
analysis.enhancementType = 'multimodal';
analysis.requiredCapabilities.push('aistudio');
analysis.confidence = Math.max(analysis.confidence, 0.9);
analysis.priority = 'high';
analysis.estimatedComplexity = 'moderate';
logger.debug('Multimodal files detected', fileAnalysis);
}
const groundingAnalysis = this.analyzeGroundingNeeds(input);
if (groundingAnalysis.needsGrounding) {
analysis.canEnhance = true;
if (analysis.enhancementType === 'passthrough') {
analysis.enhancementType = 'grounding';
}
analysis.requiredCapabilities.push('gemini');
analysis.confidence = Math.max(analysis.confidence, groundingAnalysis.confidence);
analysis.priority = groundingAnalysis.priority;
logger.debug('Grounding needs detected', groundingAnalysis);
}
const complexityAnalysis = this.analyzeComplexity(input);
if (complexityAnalysis.needsReasoning) {
analysis.canEnhance = true;
if (analysis.enhancementType === 'passthrough') {
analysis.enhancementType = 'reasoning';
}
analysis.requiredCapabilities.push('claude');
analysis.confidence = Math.max(analysis.confidence, complexityAnalysis.confidence);
analysis.estimatedComplexity = complexityAnalysis.complexity;
logger.debug('Complex reasoning detected', complexityAnalysis);
}
if (!analysis.requiredCapabilities.includes('claude')) {
analysis.requiredCapabilities.push('claude');
}
analysis.fallbackToOriginal = this.shouldFallbackToOriginal(args, analysis);
const duration = Date.now() - startTime;
logger.info('Request analysis completed', {
canEnhance: analysis.canEnhance,
enhancementType: analysis.enhancementType,
requiredCapabilities: analysis.requiredCapabilities,
confidence: analysis.confidence,
duration,
});
return analysis;
}, {
operationName: 'analyze-request',
layer: 'claude',
timeout: 5000,
});
}
async generateEnhancementPlan(analysis) {
return safeExecute(async () => {
logger.debug('Generating enhancement plan', { analysis });
if (!analysis.canEnhance || analysis.fallbackToOriginal) {
return {
enhance: false,
type: 'passthrough',
layers: ['claude'],
confidence: 1.0,
fallbackStrategy: {
enabled: true,
fallbackTo: ['claude'],
},
};
}
const layers = this.determineLayerOrder(analysis);
const estimatedDuration = this.estimateExecutionDuration(analysis, layers);
const plan = {
enhance: true,
type: analysis.enhancementType,
layers,
confidence: analysis.confidence,
fallbackStrategy: {
enabled: true,
fallbackTo: ['claude'],
},
estimatedDuration,
};
logger.info('Enhancement plan generated', plan);
return plan;
}, {
operationName: 'generate-enhancement-plan',
layer: 'claude',
timeout: 2000,
});
}
isSystemCommand(args) {
const systemCommands = ['--help', '-h', '--version', '-v', 'help', 'version'];
return args.some(arg => systemCommands.includes(arg.toLowerCase()));
}
analyzeFiles(args, input) {
const fileArgs = args.filter(arg => arg.startsWith('@') || this.isFilePath(arg));
const hasFiles = fileArgs.length > 0;
let hasMultimodalFiles = false;
const fileTypes = [];
for (const fileArg of fileArgs) {
const filePath = fileArg.startsWith('@') ? fileArg.slice(1) : fileArg;
const extension = this.getFileExtension(filePath);
if (this.FILE_EXTENSIONS.multimodal.includes(extension)) {
hasMultimodalFiles = true;
fileTypes.push('multimodal');
}
else if (this.FILE_EXTENSIONS.document.includes(extension)) {
fileTypes.push('document');
}
else if (this.FILE_EXTENSIONS.data.includes(extension)) {
fileTypes.push('data');
}
else if (this.FILE_EXTENSIONS.code.includes(extension)) {
fileTypes.push('code');
}
}
return {
hasFiles,
hasMultimodalFiles,
fileTypes: [...new Set(fileTypes)],
fileCount: fileArgs.length,
};
}
analyzeGroundingNeeds(input) {
const lowerInput = input.toLowerCase();
const matchedKeywords = this.GROUNDING_KEYWORDS.filter(keyword => lowerInput.includes(keyword));
const needsGrounding = matchedKeywords.length > 0;
let confidence = 0;
if (needsGrounding) {
confidence = Math.min(0.3 + (matchedKeywords.length * 0.2), 0.8);
if (matchedKeywords.some(kw => ['today', 'now', 'current', 'latest'].includes(kw))) {
confidence = Math.min(confidence + 0.2, 0.9);
}
}
let priority = 'low';
if (confidence > 0.7) {
priority = 'high';
}
else if (confidence > 0.4) {
priority = 'medium';
}
return {
needsGrounding,
confidence,
priority,
matchedKeywords,
};
}
analyzeComplexity(input) {
const lowerInput = input.toLowerCase();
let complexity = 'simple';
let confidence = 0.3;
for (const [level, keywords] of Object.entries(this.COMPLEXITY_INDICATORS)) {
const matches = keywords.filter(keyword => lowerInput.includes(keyword));
if (matches.length > 0) {
complexity = level;
confidence += matches.length * 0.1;
break;
}
}
const wordCount = input.split(/\s+/).length;
if (wordCount > 100) {
complexity = complexity === 'simple' ? 'moderate' : 'complex';
confidence += 0.1;
}
if (wordCount > 200) {
complexity = 'complex';
confidence += 0.2;
}
const reasoningPatterns = [
'why', 'how', 'explain', 'because', 'therefore', 'however',
'compare', 'contrast', 'analyze', 'evaluate', 'assess'
];
const reasoningMatches = reasoningPatterns.filter(pattern => lowerInput.includes(pattern));
if (reasoningMatches.length > 0) {
confidence += reasoningMatches.length * 0.05;
}
const needsReasoning = complexity !== 'simple' || confidence > 0.5;
return {
needsReasoning,
confidence: Math.min(confidence, 0.8),
complexity,
};
}
shouldFallbackToOriginal(args, analysis) {
if (this.isSystemCommand(args)) {
return true;
}
if (analysis.confidence < 0.3) {
return true;
}
const hasFiles = args.some(arg => arg.startsWith('@') || this.isFilePath(arg));
if (!hasFiles && analysis.estimatedComplexity === 'simple' && analysis.confidence < 0.5) {
return true;
}
return false;
}
determineLayerOrder(analysis) {
const layers = [];
switch (analysis.enhancementType) {
case 'multimodal':
if (analysis.requiredCapabilities.includes('aistudio')) {
layers.push('aistudio');
}
layers.push('claude');
break;
case 'grounding':
if (analysis.requiredCapabilities.includes('gemini')) {
layers.push('gemini');
}
layers.push('claude');
break;
case 'reasoning':
layers.push('claude');
if (analysis.requiredCapabilities.includes('gemini')) {
layers.push('gemini');
}
break;
default:
layers.push('claude');
}
return [...new Set(layers)];
}
estimateExecutionDuration(analysis, layers) {
let baseDuration = 2000;
baseDuration += layers.length * 1000;
switch (analysis.estimatedComplexity) {
case 'complex':
baseDuration += 10000;
break;
case 'moderate':
baseDuration += 5000;
break;
default:
}
if (analysis.enhancementType === 'multimodal') {
baseDuration += 15000;
}
return baseDuration;
}
isFilePath(str) {
return /\.[a-zA-Z0-9]{1,4}$/.test(str) || str.includes('/') || str.includes('\\');
}
getFileExtension(filePath) {
const match = filePath.match(/\.([^.]+)$/);
return match ? `.${match[1]?.toLowerCase()}` : '';
}
async quickAnalyze(args) {
try {
const analysis = await this.analyze(args);
return analysis.canEnhance && !analysis.fallbackToOriginal;
}
catch {
return false;
}
}
getAnalysisSummary(analysis) {
const capabilities = analysis.requiredCapabilities.join(', ');
return `${analysis.enhancementType} (${capabilities}) - confidence: ${analysis.confidence.toFixed(2)}`;
}
}