autoagent-cli
Version:
Run autonomous AI agents using Claude or Gemini for task execution
133 lines (132 loc) • 4.61 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.retryDefaults = exports.RateLimitError = exports.RetryError = void 0;
exports.retry = retry;
exports.retryWithJitter = retryWithJitter;
exports.createRetryableFunction = createRetryableFunction;
exports.isRateLimitError = isRateLimitError;
exports.isRateLimitOrUsageError = isRateLimitOrUsageError;
exports.extractRetryAfter = extractRetryAfter;
const logger_1 = require("./logger");
class RetryError extends Error {
constructor(message, attempts, lastError) {
super(message);
this.attempts = attempts;
this.lastError = lastError;
this.name = 'RetryError';
}
}
exports.RetryError = RetryError;
class RateLimitError extends Error {
constructor(message, retryAfter) {
super(message);
this.retryAfter = retryAfter;
this.name = 'RateLimitError';
}
}
exports.RateLimitError = RateLimitError;
const defaultOptions = {
maxAttempts: 3,
initialDelay: 1000,
maxDelay: 30000,
backoffMultiplier: 2,
shouldRetry: (error) => {
if (error instanceof RateLimitError) {
return true;
}
if (isRateLimitOrUsageError(error.message)) {
return true;
}
const message = error.message.toLowerCase();
if (message.includes('not found') ||
message.includes('unauthorized') ||
message.includes('forbidden')) {
return false;
}
if (error.name === 'NetworkError' ||
message.includes('timeout') ||
message.includes('connection')) {
return true;
}
return true;
},
onRetry: (error, attempt) => {
logger_1.Logger.warning(`Retry attempt ${attempt}: ${error.message}`);
}
};
async function retry(fn, options) {
const opts = { ...defaultOptions, ...options };
let lastError = null;
for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
try {
return await fn();
}
catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
if (!opts.shouldRetry(lastError)) {
throw lastError;
}
if (attempt === opts.maxAttempts) {
throw new RetryError(`Failed after ${opts.maxAttempts} attempts`, opts.maxAttempts, lastError);
}
let delay = opts.initialDelay * Math.pow(opts.backoffMultiplier, attempt - 1);
if (lastError instanceof RateLimitError && lastError.retryAfter !== undefined && lastError.retryAfter !== null && lastError.retryAfter > 0) {
delay = lastError.retryAfter * 1000;
}
delay = Math.min(delay, opts.maxDelay);
opts.onRetry(lastError, attempt);
await sleep(delay);
}
}
throw new RetryError(`Failed after ${opts.maxAttempts} attempts`, opts.maxAttempts, lastError || new Error('Unknown error'));
}
async function retryWithJitter(fn, options) {
const jitteredOptions = {
...options,
onRetry: (error, attempt) => {
options?.onRetry?.(error, attempt);
}
};
return retry(async () => {
const jitterDelay = Math.random() * 100;
await sleep(jitterDelay);
return fn();
}, jitteredOptions);
}
function createRetryableFunction(fn, options) {
return (async (...args) => {
return retry(() => fn(...args), options);
});
}
function isRateLimitError(error) {
if (error instanceof RateLimitError) {
return true;
}
return isRateLimitOrUsageError(error.message);
}
function isRateLimitOrUsageError(message) {
const lowercaseMessage = message.toLowerCase();
return lowercaseMessage.includes('rate limit') ||
lowercaseMessage.includes('usage limit') ||
lowercaseMessage.includes('too many requests') ||
lowercaseMessage.includes('quota exceeded');
}
function extractRetryAfter(error) {
if (error instanceof RateLimitError) {
return error.retryAfter;
}
const match = error.message.match(/retry[- ]?after[: ]+(\d+)/i);
if (match !== null && match[1] !== undefined && match[1] !== '') {
return parseInt(match[1], 10);
}
return undefined;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
exports.retryDefaults = {
maxAttempts: defaultOptions.maxAttempts,
initialDelay: defaultOptions.initialDelay,
maxDelay: defaultOptions.maxDelay,
backoffMultiplier: defaultOptions.backoffMultiplier,
};