/** * Record provider traffic once, replay it everywhere else. * * A conformance suite or an evaluation that needs live credentials runs where the credentials are — * which is rarely CI, and never a contributor's first checkout. Recording captures the real wire * traffic of one run into reviewable files; replay serves those files back, deterministically and * with no network, so the same suite runs on every pull request. Anything the recording did not * cover fails loudly instead of quietly reaching the live API. */ /** A recorded request, with credentials removed. */ export interface RecordedRequest { /** HTTP method. */ method: string; /** URL, with credential query parameters removed and the query sorted. */ url: string; /** Headers, with credential headers redacted. */ headers: Record; /** The body, as text or base64. */ body?: string; /** How `body` is encoded. */ bodyEncoding?: 'utf8' | 'base64'; } /** A recorded response. */ export interface RecordedResponse { /** HTTP status code. */ status: number; /** HTTP status text. */ statusText?: string; /** Headers, without the transport headers `fetch` has already applied. */ headers: Record; /** The body, as text or base64. */ body: string; /** How `body` is encoded. */ bodyEncoding: 'utf8' | 'base64'; } /** One request and the response it got, as written to a fixture file. */ export interface RecordedExchange { /** What the request is matched by on replay. */ key: string; /** Position among recorded requests that share a key, for a request made several times. */ index: number; /** The request, as recorded. */ request: RecordedRequest; /** The response, as recorded. */ response: RecordedResponse; /** ISO-8601 time it was recorded. */ recordedAt: string; } /** What a matcher sees: the request, with credentials already removed. */ export interface MatchInput { /** HTTP method. */ method: string; /** URL, credentials removed and query sorted. */ url: string; /** Headers, credentials redacted. */ headers: Record; /** The body as text, with any multipart boundary normalized. */ body?: string; } /** Where fixtures live and how requests are matched to them. */ export interface FixtureOptions { /** Directory the fixture files live in, one file per exchange. */ directory: string; /** * How a request is matched to a recording. Defaults to a hash of the method, the URL with its * query sorted, and the body with object keys sorted. Override it to ignore a field that changes * on every call, such as a timestamp in the body. */ match?: (request: MatchInput) => string; /** Top-level JSON body fields left out of the default match: a request id, a timestamp. */ ignoreBodyFields?: readonly string[]; /** Header names removed from recordings, in addition to the credential headers always removed. */ redactHeaders?: readonly string[]; /** Query parameters removed from recorded URLs, in addition to the credential ones always removed. */ redactQuery?: readonly string[]; } /** Options for recording. */ export interface RecordOptions extends FixtureOptions { /** The real fetch. Defaults to `globalThis.fetch` at the time of the call. */ fetch?: typeof globalThis.fetch; /** * Last chance to rewrite an exchange before it is written: personal data in a prompt, a long body. * Credentials are already gone by then; this is for everything else, and a PII detector from * `nexus-ai-pro/security` plugs in here. */ redact?: (exchange: RecordedExchange) => RecordedExchange; /** Replaces the system clock, for `recordedAt`. */ now?: () => Date; } /** Options for replaying. */ export interface ReplayOptions extends FixtureOptions { /** * What an unrecorded request does. `throw` (the default) raises `FixtureMissingError`; `live` * sends it to the real API through `fetch`, and is only for deliberately topping up recordings. */ onMissing?: 'throw' | 'live'; /** The real fetch, for `onMissing: 'live'`. */ fetch?: typeof globalThis.fetch; } /** A fetch that also lets a test wait for every fixture it has written. */ export type RecordingFetch = typeof globalThis.fetch & { flush(): Promise; }; /** Raised on replay when no recording matches a request. */ export declare class FixtureMissingError extends Error { /** HTTP method of the unmatched request. */ readonly method: string; /** URL of the unmatched request. */ readonly url: string; /** The match key it had, for finding the fixture that should have matched. */ readonly key: string; constructor( /** HTTP method of the unmatched request. */ method: string, /** URL of the unmatched request. */ url: string, /** The match key it had, for finding the fixture that should have matched. */ key: string, directory: string); } /** Wraps a real fetch so that every exchange is written to the fixture directory. */ export declare function recordingFetch(options: RecordOptions): RecordingFetch; /** Serves recorded exchanges instead of calling the network. */ export declare function replayFetch(options: ReplayOptions): typeof globalThis.fetch; /** `record` captures live traffic, `replay` serves fixtures, `live` passes through untouched. */ export type FixtureMode = 'record' | 'replay' | 'live'; /** * Picks recording, replay, or the live network by mode, so one test file serves all three. Defaults * to the `NEXUS_FIXTURES` environment variable, then to `replay`. */ export declare function fixtureFetch(options: RecordOptions & ReplayOptions & { mode?: FixtureMode; }): typeof globalThis.fetch & { flush?: () => Promise; }; /** * Replaces `globalThis.fetch` until the returned function is called, for code that does not take a * `fetch` option — several completion providers call the global directly. */ export declare function installFetch(replacement: typeof globalThis.fetch): () => void; /** Reads every exchange in a fixture directory, for inspection or a custom replay. */ export declare function readFixtures(directory: string): Promise;