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
179 lines (178 loc) • 6.88 kB
JavaScript
import { logger } from './logger.js';
export class TimeoutManager {
static DEFAULT_TIMEOUT = 120000;
static ENVIRONMENT_TIMEOUTS = {
fresh_installation: 1.5,
mcp_startup: 2.0,
authentication: 1.3,
};
static async executeWithTimeout(operation, options) {
const baseTimeout = options.timeout || this.DEFAULT_TIMEOUT;
const multiplier = options.environmentType
? this.ENVIRONMENT_TIMEOUTS[options.environmentType]
: 1.0;
const finalTimeout = Math.round(baseTimeout * multiplier);
const controller = new AbortController();
const startTime = Date.now();
logger.debug('Starting timeout-managed operation', {
operation: options.name,
baseTimeout,
multiplier,
finalTimeout,
environmentType: options.environmentType || 'standard'
});
let timeoutId;
let warningTimeoutId;
try {
if (options.onWarning) {
const warningTime = Math.round(finalTimeout * 0.7);
warningTimeoutId = setTimeout(() => {
const elapsed = Date.now() - startTime;
options.onWarning(elapsed);
}, warningTime);
}
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
controller.abort();
const elapsed = Date.now() - startTime;
logger.warn('Operation timeout exceeded', {
operation: options.name,
timeout: finalTimeout,
elapsed,
environmentType: options.environmentType
});
reject(new Error(`Operation "${options.name}" timed out after ${finalTimeout}ms ` +
`(${Math.round(elapsed / 1000)}s elapsed, environment: ${options.environmentType || 'standard'})`));
}, finalTimeout);
});
const result = await Promise.race([
operation(controller.signal),
timeoutPromise
]);
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = undefined;
}
if (warningTimeoutId) {
clearTimeout(warningTimeoutId);
warningTimeoutId = undefined;
}
const elapsed = Date.now() - startTime;
logger.debug('Operation completed successfully', {
operation: options.name,
elapsed,
timeoutUsed: finalTimeout,
efficiency: Math.round((elapsed / finalTimeout) * 100) + '%'
});
return result;
}
catch (error) {
if (timeoutId) {
clearTimeout(timeoutId);
}
if (warningTimeoutId) {
clearTimeout(warningTimeoutId);
}
const elapsed = Date.now() - startTime;
logger.error('Operation failed', {
operation: options.name,
elapsed,
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
static detectEnvironment() {
const isNewEnvironment = this.isFreshInstallation();
if (isNewEnvironment) {
return {
type: 'fresh_installation',
reason: 'Fresh installation detected - authentication and MCP startup may take longer'
};
}
const needsMCPStartup = this.needsMCPServerStartup();
if (needsMCPStartup) {
return {
type: 'mcp_startup',
reason: 'MCP server startup required - cold start may take longer'
};
}
return {
type: 'standard',
reason: 'Standard environment with warm processes'
};
}
static isFreshInstallation() {
try {
const indicators = [
!process.env.HOME || !require('fs').existsSync(require('path').join(process.env.HOME, '.config', 'gemini')),
!require('fs').existsSync(require('path').join(process.cwd(), 'logs')),
!process.env.CGMB_INITIALIZED,
];
return indicators.filter(Boolean).length >= 2;
}
catch {
return false;
}
}
static needsMCPServerStartup() {
try {
const { execSync } = require('child_process');
try {
const processes = execSync('pgrep -f "ai-studio-mcp-server" || true', {
encoding: 'utf8',
timeout: 1000
});
return !processes.trim();
}
catch {
return true;
}
}
catch {
return true;
}
}
static wrapCLICommand(command, commandName, baseTimeout = 120000) {
const environment = this.detectEnvironment();
return this.executeWithTimeout(async (signal) => {
return await command();
}, {
name: `CLI-${commandName}`,
timeout: baseTimeout,
...(environment.type !== 'standard' && { environmentType: environment.type }),
onWarning: (elapsed) => {
logger.info(`⚠️ ${commandName} is taking longer than expected (${Math.round(elapsed / 1000)}s)...`);
logger.info(`💡 ${environment.reason}`);
}
});
}
static createMCPTimeout(layer, operation, hasFiles = false) {
const environment = this.detectEnvironment();
const baseTimeouts = {
claude: 300000,
gemini: 60000,
aistudio: 180000,
};
let baseTimeout = baseTimeouts[layer];
if (hasFiles) {
baseTimeout = Math.round(baseTimeout * 1.5);
}
if (operation.includes('generate_image') || operation.includes('image_generation')) {
baseTimeout = Math.round(baseTimeout * 0.8);
}
else if (operation.includes('generate_audio') || operation.includes('audio_generation')) {
baseTimeout = Math.round(baseTimeout * 0.6);
}
return {
timeout: baseTimeout,
environmentType: environment.type === 'standard' ? 'standard' : environment.type
};
}
}
export async function withTimeout(operation, name, timeoutMs = 120000) {
return TimeoutManager.executeWithTimeout(async () => operation(), { name, timeout: timeoutMs });
}
export async function withCLITimeout(operation, commandName, baseTimeout = 120000) {
return TimeoutManager.wrapCLICommand(operation, commandName, baseTimeout);
}