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
JavaScript
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(),
};
}
}