UNPKG

autoagent-cli

Version:

Run autonomous AI agents using Claude or Gemini for task execution

185 lines (184 loc) 8.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RateLimitMonitor = exports.RateLimitDetectedError = void 0; const child_process_1 = require("child_process"); const stream_formatter_1 = require("../utils/stream-formatter"); const logger_1 = require("../utils/logger"); class RateLimitDetectedError extends Error { constructor(provider, originalError) { super(`Rate limit detected for ${provider}: ${originalError}`); this.provider = provider; this.originalError = originalError; this.name = 'RateLimitDetectedError'; } } exports.RateLimitDetectedError = RateLimitDetectedError; class RateLimitMonitor { constructor(rateLimiter) { this.rateLimiter = rateLimiter; } async spawnWithMonitoring(command, args, provider, options) { return new Promise((resolve, reject) => { const child = (0, child_process_1.spawn)(command, args, { stdio: (options?.stdinContent ?? '').length > 0 ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; let rateLimitDetected = false; let terminatedEarly = false; if (options?.enableStreaming === true) { if (provider === 'gemini') { stream_formatter_1.StreamFormatter.showHeader('gemini'); } else if (provider === 'claude') { stream_formatter_1.StreamFormatter.showHeader('claude'); } } if (options?.signal) { options.signal.addEventListener('abort', () => { child.kill('SIGTERM'); reject(new Error('Process aborted by user')); }); } if ((options?.stdinContent ?? '').length > 0 && child.stdin) { child.stdin.write(options?.stdinContent ?? ''); child.stdin.end(); } child.stdout?.on('data', (data) => { const chunk = data.toString(); stdout += chunk; if (options?.enableStreaming === true) { this.handleStreamingOutput(chunk, provider); } if (!rateLimitDetected && this.rateLimiter.isRateLimitError(provider, chunk)) { rateLimitDetected = true; terminatedEarly = true; logger_1.Logger.warning(`Rate limit detected in stdout for ${provider} - terminating process`); child.kill('SIGTERM'); } }); child.stderr?.on('data', (data) => { const chunk = data.toString(); stderr += chunk; if (process.env.DEBUG === 'true') { console.error(`[DEBUG] ${provider} stderr chunk: ${chunk}`); } if (!rateLimitDetected && this.rateLimiter.isRateLimitError(provider, chunk)) { rateLimitDetected = true; terminatedEarly = true; logger_1.Logger.warning(`Rate limit detected in stderr for ${provider} - terminating process immediately`); child.kill('SIGTERM'); } }); child.on('error', (error) => { reject(error); }); child.on('close', (code) => { if (options?.enableStreaming === true) { if (provider === 'gemini') { const remaining = stream_formatter_1.StreamFormatter.flushGeminiBuffer(); if (remaining) { stream_formatter_1.StreamFormatter.displayGeminiText(remaining); } stream_formatter_1.StreamFormatter.showFooter(); } else if (provider === 'claude') { stream_formatter_1.StreamFormatter.showFooter(); } } if (rateLimitDetected) { this.rateLimiter.markProviderRateLimited(provider, stderr || stdout || 'Rate limit detected during real-time monitoring').catch(err => { logger_1.Logger.error(`Failed to mark rate limit: ${err}`); }); } resolve({ stdout, stderr, exitCode: code, rateLimitDetected, terminatedEarly }); }); }); } async executeGeminiWithFallback(args, options) { const bestModel = await this.rateLimiter.getBestGeminiModel(); let finalArgs = [...args]; if (!finalArgs.includes('--model')) { finalArgs.push('--model', bestModel); } try { const result = await this.spawnWithMonitoring('gemini', finalArgs, 'gemini', options); if (result.rateLimitDetected && result.terminatedEarly && bestModel === 'gemini-2.5-pro') { logger_1.Logger.info('Switching to Gemini Flash model due to rate limit'); finalArgs = args.filter(arg => arg !== '--model' && arg !== 'gemini-2.5-pro'); finalArgs.push('--model', 'gemini-2.5-flash'); const fallbackResult = await this.spawnWithMonitoring('gemini', finalArgs, 'gemini', options); if (fallbackResult.rateLimitDetected && fallbackResult.terminatedEarly) { throw new RateLimitDetectedError('gemini', 'All Gemini models are rate limited. Consider using Claude instead.'); } return fallbackResult; } if (result.rateLimitDetected && result.terminatedEarly && bestModel === 'gemini-2.5-flash') { throw new RateLimitDetectedError('gemini', 'All Gemini models are rate limited. Consider using Claude instead.'); } if (result.rateLimitDetected) { throw new RateLimitDetectedError('gemini', 'Gemini model failed due to rate limit. Consider using Claude instead.'); } return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('All Gemini models are rate limited')) { throw new RateLimitDetectedError('gemini', errorMessage); } if (this.rateLimiter.isRateLimitError('gemini', errorMessage)) { await this.rateLimiter.markProviderRateLimited('gemini', errorMessage); } throw error; } } async executeClaudeWithMonitoring(args, options) { try { const result = await this.spawnWithMonitoring('claude', args, 'claude', options); if (result.rateLimitDetected && result.terminatedEarly) { throw new RateLimitDetectedError('claude', result.stderr || 'Rate limit detected'); } return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (this.rateLimiter.isRateLimitError('claude', errorMessage)) { await this.rateLimiter.markProviderRateLimited('claude', errorMessage); throw new RateLimitDetectedError('claude', errorMessage); } throw error; } } handleStreamingOutput(chunk, provider) { if (process.env.DEBUG === 'true') { console.error('[DEBUG] Raw chunk:', chunk); } if (provider === 'gemini') { const formatted = stream_formatter_1.StreamFormatter.formatGeminiOutput(chunk); if (formatted) { stream_formatter_1.StreamFormatter.displayGeminiText(formatted); } } else if (provider === 'claude') { const lines = chunk.split('\n').filter(line => line.trim().length > 0); for (const line of lines) { try { const message = JSON.parse(line); stream_formatter_1.StreamFormatter.formatClaudeMessage(message); } catch (e) { if (process.env.DEBUG === 'true') { console.error('[DEBUG] Parse error:', e, 'Line:', line); } } } } } } exports.RateLimitMonitor = RateLimitMonitor;