i18n-ai-translate
Version:
AI-powered localization CLI, Node library, and GitHub Action. Translate i18next JSON, Gettext PO, Java .properties, and iOS .strings with ChatGPT, Claude, Gemini, or local Ollama models.
74 lines • 2.85 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isRateLimitError = isRateLimitError;
exports.extractRetryAfterMs = extractRetryAfterMs;
exports.retryWithBackoff = retryWithBackoff;
const utils_1 = require("./utils");
const DEFAULT_BASE_DELAY_MS = 1_000;
const DEFAULT_MAX_DELAY_MS = 60_000;
/** Detects provider rate-limit errors across OpenAI, Anthropic, and Gemini shapes. */
function isRateLimitError(err) {
if (err === null || typeof err !== "object")
return false;
const anyErr = err;
if (anyErr.status === 429)
return true;
if (typeof anyErr.message === "string") {
if (/\b429\b/.test(anyErr.message))
return true;
if (/rate[ _-]?limit/i.test(anyErr.message))
return true;
if (/RESOURCE_EXHAUSTED/.test(anyErr.message))
return true;
}
return false;
}
/** Reads a Retry-After header (seconds or HTTP-date) off an SDK error. */
function extractRetryAfterMs(err) {
if (err === null || typeof err !== "object")
return null;
const headers = err.headers;
if (!headers)
return null;
const raw = headers["retry-after"] ?? headers["Retry-After"];
if (!raw)
return null;
const seconds = Number(raw);
if (Number.isFinite(seconds))
return Math.max(0, seconds * 1_000);
const asDate = Date.parse(raw);
if (Number.isFinite(asDate))
return Math.max(0, asDate - Date.now());
return null;
}
function computeBackoffMs(attempt, baseDelayMs, maxDelayMs) {
const exponential = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt);
return Math.floor(Math.random() * exponential);
}
/** Retries `job` with full-jitter exponential backoff; penalizes the shared limiter on 429s. */
async function retryWithBackoff(job, options) {
const { maxRetries, baseDelayMs = DEFAULT_BASE_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS, rateLimiter, verbose, } = options;
let attempt = 0;
while (true) {
try {
return await job();
}
catch (err) {
if (attempt >= maxRetries)
throw err;
const rateLimited = isRateLimitError(err);
const retryAfter = extractRetryAfterMs(err);
const computed = computeBackoffMs(attempt, baseDelayMs, maxDelayMs);
const waitMs = Math.min(maxDelayMs, retryAfter !== null ? retryAfter : computed);
if (rateLimited && rateLimiter) {
rateLimiter.penalize(waitMs);
}
if (verbose) {
(0, utils_1.printWarn)(`Retry ${attempt + 1}/${maxRetries} after ${waitMs}ms (${rateLimited ? "rate-limited" : "error"}): ${err}`);
}
await (0, utils_1.delay)(waitMs);
attempt++;
}
}
}
//# sourceMappingURL=retry.js.map