autoagent-cli
Version:
Run autonomous AI agents using Claude or Gemini for task execution
185 lines (184 loc) • 8.6 kB
JavaScript
;
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;