UNPKG

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
"use strict"; 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