UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

58 lines (57 loc) • 2.08 kB
const MAX_BACKOFF_MS = 32_000; const JITTER_FRACTION = 0.25; const MAX_HEADER_WAIT_MS = 60_000; const RATE_LIMIT_STATUS = 429; export function parseRetryAfter(res) { const h = res?.headers?.get?.("retry-after"); if (!h) return undefined; const secs = Number(h); if (Number.isFinite(secs)) return Math.max(0, secs * 1000); const date = Date.parse(h); if (Number.isFinite(date)) return Math.max(0, date - Date.now()); return undefined; } export function parseRateLimitReset(res) { const h = res?.headers?.get?.("anthropic-ratelimit-unified-reset"); if (!h) return undefined; const epochSecs = Number(h); if (!Number.isFinite(epochSecs)) return undefined; const ms = Math.round(epochSecs * 1000 - Date.now()); return ms > 0 ? ms : undefined; } function clampHint(rawMs) { return rawMs > MAX_HEADER_WAIT_MS ? { ms: MAX_HEADER_WAIT_MS, rawMs } : { ms: rawMs }; } export function providerWaitHint(res) { const hints = []; const retryAfter = parseRetryAfter(res); if (retryAfter !== undefined) hints.push(clampHint(retryAfter)); if (res?.status === RATE_LIMIT_STATUS) { const reset = parseRateLimitReset(res); if (reset !== undefined) hints.push(clampHint(reset)); } if (hints.length === 0) return undefined; return hints.reduce((largest, next) => { if (next.ms !== largest.ms) return next.ms > largest.ms ? next : largest; return (next.rawMs ?? next.ms) > (largest.rawMs ?? largest.ms) ? next : largest; }); } function providerWaitHintMs(res) { return providerWaitHint(res)?.ms; } export const FAST_MAX_BACKOFF_MS = 1_000; export function retryBackoffMs(baseDelayMs, attempt, res, rand = Math.random, maxBackoffMs = MAX_BACKOFF_MS) { const exp = Math.min(maxBackoffMs, baseDelayMs * 2 ** attempt); const computed = Math.round(exp + rand() * JITTER_FRACTION * exp); const hint = providerWaitHintMs(res); return hint === undefined ? computed : Math.max(hint, computed); }