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

337 lines (336 loc) 11.6 kB
import { execSync } from 'child_process'; import { logger } from '../utils/logger.js'; import { safeExecute } from '../utils/errorHandler.js'; import { AuthVerifier } from '../auth/AuthVerifier.js'; export class CapabilityDetector { authVerifier; capabilitiesCache; CACHE_TTL = 300000; constructor() { this.authVerifier = new AuthVerifier(); } async detectAvailableCapabilities() { return safeExecute(async () => { if (this.capabilitiesCache && this.isCacheValid()) { logger.debug('Using cached capabilities'); return this.capabilitiesCache; } logger.info('Detecting available capabilities...'); const startTime = Date.now(); const capabilities = { claudeCode: await this.detectClaudeCode(), geminiCLI: await this.detectGeminiCLI(), aiStudio: await this.detectAIStudio(), lastChecked: new Date(), }; this.capabilitiesCache = capabilities; const duration = Date.now() - startTime; logger.info('Capability detection completed', { claudeCode: capabilities.claudeCode.available, geminiCLI: capabilities.geminiCLI.available, aiStudio: capabilities.aiStudio.available, duration, }); return capabilities; }, { operationName: 'detect-capabilities', layer: 'claude', timeout: 30000, }); } async canExecuteEnhancement(plan) { return safeExecute(async () => { const capabilities = await this.detectAvailableCapabilities(); const availableLayers = []; const missingLayers = []; for (const layer of plan.layers) { const isAvailable = await this.isLayerAvailable(layer, capabilities); if (isAvailable) { availableLayers.push(layer); } else { missingLayers.push(layer); } } const canExecute = availableLayers.length > 0; const fallbackRequired = missingLayers.length > 0; logger.debug('Enhancement plan capability check', { planLayers: plan.layers, availableLayers, missingLayers, canExecute, fallbackRequired, }); return { canExecute, availableLayers, missingLayers, fallbackRequired, }; }, { operationName: 'check-enhancement-capabilities', layer: 'claude', timeout: 10000, }); } async getCapabilityStatus() { const capabilities = await this.detectAvailableCapabilities(); const services = {}; if (capabilities.claudeCode.available && capabilities.claudeCode.authenticated) { services.claude = 'authenticated'; } else if (capabilities.claudeCode.available) { services.claude = 'available'; } else { services.claude = 'missing'; } if (capabilities.geminiCLI.available && capabilities.geminiCLI.authenticated) { services.gemini = 'authenticated'; } else if (capabilities.geminiCLI.available) { services.gemini = 'available'; } else { services.gemini = 'missing'; } if (capabilities.aiStudio.available && capabilities.aiStudio.authenticated) { services.aistudio = 'authenticated'; } else if (capabilities.aiStudio.available) { services.aistudio = 'available'; } else { services.aistudio = 'missing'; } const authenticatedCount = Object.values(services).filter(s => s === 'authenticated').length; const availableCount = Object.values(services).filter(s => s !== 'missing').length; let overall; if (authenticatedCount === 3) { overall = 'full'; } else if (authenticatedCount >= 1 || availableCount >= 2) { overall = 'partial'; } else { overall = 'minimal'; } const recommendations = this.generateRecommendations(services); return { overall, services, recommendations, }; } async isLayerAvailable(layer, capabilities) { if (!capabilities) { capabilities = await this.detectAvailableCapabilities(); } switch (layer) { case 'claude': return capabilities.claudeCode.available && capabilities.claudeCode.authenticated; case 'gemini': return capabilities.geminiCLI.available && capabilities.geminiCLI.authenticated; case 'aistudio': return capabilities.aiStudio.available && capabilities.aiStudio.authenticated && capabilities.aiStudio.mcpServerAvailable; default: return false; } } async detectClaudeCode() { try { const claudePath = await this.findExecutablePath('claude'); if (!claudePath) { return { available: false, authenticated: false, }; } let version; try { const output = execSync('claude --version', { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }); version = output.trim(); } catch { } const authResult = await this.authVerifier.verifyClaudeCodeAuth(); return { available: true, version, authenticated: authResult.success, path: claudePath, }; } catch (error) { logger.debug('Claude Code detection failed', { error: error.message }); return { available: false, authenticated: false, }; } } async detectGeminiCLI() { try { const geminiPath = await this.findExecutablePath('gemini'); if (!geminiPath) { return { available: false, authenticated: false, }; } let version; try { const output = execSync('gemini --version', { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }); version = output.trim(); } catch { } const authResult = await this.authVerifier.verifyGeminiAuth(); return { available: true, version, authenticated: authResult.success, path: geminiPath, }; } catch (error) { logger.debug('Gemini CLI detection failed', { error: error.message }); return { available: false, authenticated: false, }; } } async detectAIStudio() { try { const authResult = await this.authVerifier.verifyAIStudioAuth(); const mcpServerAvailable = await this.checkAIStudioMCPServer(); return { available: authResult.success || mcpServerAvailable, authenticated: authResult.success, mcpServerAvailable, }; } catch (error) { logger.debug('AI Studio detection failed', { error: error.message }); return { available: false, authenticated: false, mcpServerAvailable: false, }; } } async checkAIStudioMCPServer() { try { execSync('npx -y aistudio-mcp-server --version', { stdio: 'ignore', timeout: 10000 }); return true; } catch { try { execSync('which aistudio-mcp-server', { stdio: 'ignore' }); return true; } catch { return false; } } } async findExecutablePath(command) { try { const output = execSync(`which ${command}`, { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }); return output.trim(); } catch { try { const output = execSync(`where ${command}`, { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }); return output.split('\n')[0]?.trim(); } catch { return undefined; } } } generateRecommendations(services) { const recommendations = []; if (services.claude === 'missing') { recommendations.push('Install Claude Code: npm install -g @anthropic-ai/claude-code'); } else if (services.claude === 'available') { recommendations.push('Authenticate Claude Code: claude auth'); } if (services.gemini === 'missing') { recommendations.push('Install Gemini CLI: npm install -g @google/gemini-cli'); } else if (services.gemini === 'available') { recommendations.push('Authenticate Gemini: gemini auth or set GEMINI_API_KEY'); } if (services.aistudio === 'missing') { recommendations.push('Install AI Studio MCP: npm install -g aistudio-mcp-server'); } else if (services.aistudio === 'available') { recommendations.push('Set GEMINI_API_KEY for AI Studio access'); } if (recommendations.length === 0) { recommendations.push('All services ready! CGMB is fully functional.'); } return recommendations; } isCacheValid() { if (!this.capabilitiesCache) { return false; } const age = Date.now() - this.capabilitiesCache.lastChecked.getTime(); return age < this.CACHE_TTL; } clearCache() { this.capabilitiesCache = undefined; logger.debug('Capabilities cache cleared'); } async hasMinimalCapabilities() { try { const capabilities = await this.detectAvailableCapabilities(); return capabilities.claudeCode.available; } catch { return false; } } async hasEnhancedCapabilities() { try { const capabilities = await this.detectAvailableCapabilities(); return (capabilities.claudeCode.authenticated && (capabilities.geminiCLI.authenticated || capabilities.aiStudio.authenticated)); } catch { return false; } } async getDetailedCapabilityReport() { const capabilities = await this.detectAvailableCapabilities(); const status = await this.getCapabilityStatus(); return { capabilities, status, timestamp: new Date(), }; } }