UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

153 lines • 8.22 kB
import { type ByteWindow } from '../../utils/ranges.ts'; export declare const MAX_BODY_BYTES: number; export type ErrorOf = (response: Response, body: string) => Error; /** * `fetchFn` (the global fetch, read per call, when absent) bounded by how * long a request may wait on the server: to connect and answer, then for * each chunk of the body the caller asks for. The clock restarts when the * headers arrive and on every chunk, and stops while the caller is not * reading, once the body ends and when the caller cancels it, so a download * that keeps flowing is never cut off. A request that waits longer fails * with a `TimeoutError` DOMException. The body is relayed, locking the * original only while a read is pending, so a body drained, failed or * cancelled is left unlocked; the relay keeps the original's `url` and * `redirected`. Python's twin is `stall_timeout`, which aiohttp spells as * sock_connect and sock_read. */ export declare function stallTimeout(seconds?: number, fetchFn?: typeof fetch): typeof fetch; /** * Pull a GET response in bounded chunks, cancelling its body on early exit. * `retry` applies to opening the response only: once a chunk has reached the * caller a failure propagates, since a second attempt would hand it the same * bytes twice. */ export declare function apiStream(url: string, options: Pick<ApiRequestOptions, 'errorOf' | 'headers' | 'fetchFn' | 'retry' | 'sleep' | 'timeout'> & { signal?: AbortSignal; /** Told the answering response's headers, lower-cased, once and before * the first chunk, so an empty body reports them too. */ onResponse?: (headers: Record<string, string>) => void; }): AsyncGenerator<Uint8Array, void>; /** Pull a response body in bounded chunks, cancelling it on early exit. */ export declare function bodyChunks(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<Uint8Array, void>; export interface RetryPolicy { /** Response statuses worth retrying. */ readonly statuses: ReadonlySet<number>; /** Retries allowed after the first attempt. */ readonly maxRetries: number; /** * Ceiling on every inter-attempt wait, whether the server asked for it * or the exponential fallback chose it. */ readonly maxBackoff: number; /** * Where the wait between attempts comes from: 'header' reads Retry-After * and falls back to exponential backoff (Graph's convention); 'body' * reads a JSON `retry_after` field and falls back to 1s (Discord's). */ readonly delaySource: 'header' | 'body'; /** * Also retry connection-level failures, which never carry a response; * the wait for those is the exponential backoff. */ readonly retryTransport?: boolean; /** * A veto over a listed status, given the status and the body text, for * an API whose one status means two things. Airtable answers both "slow * down" and "monthly quota spent" with a 429, and only the first is worth * waiting for; a vetoed response maps through `errorOf` at once. Absent * retries every listed status. */ readonly retryable?: (status: number, text: string) => boolean; /** * Per-status floor under the wait, for an API that documents a penalty * window: after a 429, Airtable refuses every request for 30 seconds, so * a shorter backoff only spends the retries inside the window. * `maxBackoff` still caps the result. */ readonly minDelays?: Readonly<Record<number, number>>; } export declare const NO_RETRY: RetryPolicy; /** * How to read the reply: 'json' parses the body (an empty one reads as * null); 'none' ignores it; 'bytes' returns it raw, trimmed to the window * when the server ignored the Range; 'text' returns it as a string; * 'location' returns the Location header; 'response' returns the decoded body * with status and lower-cased headers; 'bytes_response' returns the 'bytes' * body with the same status and headers, for a caller that needs a content * token the response carries. */ export type ReadMode = 'json' | 'none' | 'bytes' | 'bytes_response' | 'text' | 'location' | 'response'; /** * `base` and `path` joined by one slash, with each defined `query` entry * URI-encoded onto the end; an undefined value is left out. */ export declare function buildUrl(base: string, path: string, query: Record<string, string | number | undefined>): string; /** * A response's headers as a plain record, names lower-cased; fetch already * joins a repeated header, as python's lowered_headers does. */ export declare function loweredHeaders(headers: Headers): Record<string, string>; /** Decoded body plus the wire metadata cursor pagination reads. Mirrors * python's `ApiResponse`; a caller asking for `read: 'response'` gets this * rather than the bare body, because a `Link` header is the only thing that * says whether another page exists. */ export interface ApiResponse { data: unknown; status: number; /** Lower-cased, the way python's dump spells them, so one header is read * by one name in both languages. */ headers: Record<string, string>; } export interface ApiRequestOptions { errorOf: ErrorOf; /** Request headers, already merged by the caller. */ headers?: Record<string, string> | undefined; params?: Record<string, string | number | boolean> | undefined; /** JSON request body; absent sends no body, so a caller that means "send * an empty object" passes `{}` explicitly. */ json?: unknown; /** Raw request body (bytes, form data), for endpoints that do not speak * JSON; exclusive with `json`. */ body?: BodyInit | undefined; retry?: RetryPolicy | undefined; read?: ReadMode | undefined; /** The byte range to request; the Range header and the trim-if-unranged * guard both come from it. */ window?: ByteWindow | undefined; /** Maximum decoded bytes retained by successful bytes/bytes_response reads. * Defaults to 64 MiB; use a stream for larger downloads. Ignored Range * prefixes do not count, and overflow fails rather than truncates. */ maxBodyBytes?: number | undefined; /** Seconds a request may wait to connect, or between two reads of its * response, before it fails (`stallTimeout`); absent is `DEFAULT_TIMEOUT`. */ timeout?: number | undefined; /** The fetch to use, so transports keep their injection seam. */ fetchFn?: typeof fetch | undefined; /** The wait between attempts, in seconds, injectable so a test or a * simulated clock spends no real time; absent is a real timer. */ sleep?: ((seconds: number) => Promise<void>) | undefined; /** The signal the request answers to in place of the call's. Cleanup that * must still run once the call stopped, as aborting an upload does, * passes a bounded one of its own. */ signal?: AbortSignal | undefined; } /** * Whether a server-supplied delay is one we can actually wait out. NaN and * infinity are unusable (`setTimeout` silently clamps both to 1ms, turning * the wait into a hot retry), and a negative delay is malformed per RFC * 9110, so all three fall back the way an unparseable header does. */ export declare function usableDelay(value: number): boolean; export declare function headerDelay(response: Response, attempt: number, retry: RetryPolicy): number; export declare function bodyDelay(text: string, retry: RetryPolicy): number; /** Raise a wait to the policy's floor for this status, then cap it. */ export declare function flooredDelay(delay: number, status: number, retry: RetryPolicy): number; /** Collect a bounded byte response, discarding an ignored Range prefix. */ export declare function readBytes(response: Pick<Response, 'body' | 'status'>, window: ByteWindow | undefined, maxBodyBytes: number): Promise<Uint8Array<ArrayBuffer>>; /** * One round-trip against an HTTP API, with retry and error mapping. * Returns the reply per `options.read`, defaulting to the parsed JSON * body (null when the body is empty, e.g. a 204). */ export declare function apiRequest(method: string, url: string, options: ApiRequestOptions): Promise<unknown>; //# sourceMappingURL=client.d.ts.map