/** * Retrying JSON-API request — the toolkit-tier HTTP primitive for host CLIs' * vendor clients. * * Extracted from the first embedding host, where ten vendor clients carried * byte-similar copies of the same loop: abort-controller timeout, retry on * 429/5xx with exponential backoff + jitter, `Retry-After` honored when sane, * vendor-specific error taxonomy applied by the caller. This module owns the * loop; callers keep their auth headers and error classes: * * const r = await requestWithRetries(url, { * method, body, * headers: { Authorization: `ApiKey ${key}`, Accept: "application/json" }, * timeoutMs: this.timeoutMs, * maxRetries: this.maxRetries, * onResponse: ({ status }) => log(`${method} ${url} → ${status}`), * networkError: (msg) => new VendorError("network_error", msg), * }); * if (!r.ok) throw makeHttpError(r.status, url, r.text, r.headers); * * Design choices, so they survive review: * - Terminal non-2xx responses RETURN (`ok: false`) rather than throw — the * vendor error taxonomy belongs to the caller, not this module. * - Only terminal NETWORK failures throw (after retries), because there is * no response to hand back; `networkError` lets the caller keep its class. * - The response body is always read (even on retried statuses) so keep-alive * sockets are released. */ export interface RetryingResponse { /** `status` in the 2xx range. */ ok: boolean; status: number; /** Response body as text; callers handle JSON parsing. */ text: string; url: string; headers: Headers; } export interface RequestWithRetriesOptions { /** HTTP method. Default GET. */ method?: string; /** Extra headers. Content-Type defaults to application/json when a non-string body is given. */ headers?: Record; /** * Request body. Strings, Uint8Array, FormData, Blob, and ReadableStream pass * through untouched; any other value is JSON.stringify'd (with a * Content-Type: application/json default). */ body?: unknown; /** Per-attempt timeout (AbortController). Default 30s. */ timeoutMs?: number; /** Retries after the first attempt. Default 3. */ maxRetries?: number; /** Which statuses to retry. Default: 429 and 5xx. */ shouldRetry?: (status: number) => boolean; /** Delay before retry `attempt` (0-based). Default `backoffDelayMs`. */ delayMs?: (attempt: number, retryAfterSeconds?: number | null) => number; /** Observability hook — fires once per received response (every attempt). */ onResponse?: (info: { method: string; url: string; status: number; attempt: number; }) => void; /** * Wrap a terminal network failure (fetch threw on the last attempt) in the * caller's error class. Default: a plain Error with the message. */ networkError?: (message: string, url: string, retries: number) => Error; } /** * Exponential backoff with jitter: 500ms · 2^attempt, capped at 30s, plus up * to 250ms of jitter. A sane `Retry-After` (0 < s < 60) short-circuits the * curve — the server knows better than the guess. */ export declare function backoffDelayMs(attempt: number, retryAfterSeconds?: number | null): number; export declare function requestWithRetries(url: string, opts?: RequestWithRetriesOptions): Promise; //# sourceMappingURL=request.d.ts.map