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

300 lines (299 loc) 12.1 kB
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)}`; } }