@yanlinglabs/winter-provider-conformance 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +41 -0
- package/README.md +51 -0
- package/dist/corpus/azure.d.ts +7 -0
- package/dist/corpus/classifier-safety.d.ts +146 -0
- package/dist/corpus/continuity.d.ts +52 -0
- package/dist/corpus/cross-vendor-headers.d.ts +38 -0
- package/dist/corpus/harness.d.ts +57 -0
- package/dist/corpus/runner.d.ts +69 -0
- package/dist/fakes/anthropic-console-oauth.d.ts +36 -0
- package/dist/fakes/anthropic-messages.d.ts +115 -0
- package/dist/fakes/azure-openai.d.ts +18 -0
- package/dist/fakes/bedrock.d.ts +90 -0
- package/dist/fakes/codex-oauth.d.ts +46 -0
- package/dist/fakes/gemini.d.ts +121 -0
- package/dist/fakes/index.d.ts +15 -0
- package/dist/fakes/index.js +56 -0
- package/dist/fakes/jwt-verify.d.ts +13 -0
- package/dist/fakes/openai-chat.d.ts +61 -0
- package/dist/fakes/openai-models.d.ts +49 -0
- package/dist/fakes/openai-responses.d.ts +86 -0
- package/dist/fakes/redact-opaque.d.ts +4 -0
- package/dist/fakes/server.d.ts +143 -0
- package/dist/fakes/vertex.d.ts +60 -0
- package/dist/fakes/xai-oauth.d.ts +2 -0
- package/dist/index-k4mhh1q5.js +4377 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1671 -0
- package/dist/live/cases.d.ts +87 -0
- package/dist/live/index.d.ts +101 -0
- package/package.json +50 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/** One request the fake received, recorded BEFORE any route ran. */
|
|
2
|
+
export interface RecordedRequest {
|
|
3
|
+
method: string;
|
|
4
|
+
/** Path only -- the host is always `127.0.0.1:<ephemeral>` and would make an assertion port-dependent. */
|
|
5
|
+
path: string;
|
|
6
|
+
/** The full URL search string, so a test can assert on query parameters (`api-version`, `key`, …) without re-parsing. */
|
|
7
|
+
search: string;
|
|
8
|
+
/** REDACTED: an `authorization`/`x-api-key`/`api-key` value is replaced with its scheme plus `***`. */
|
|
9
|
+
headers: Record<string, string>;
|
|
10
|
+
/** The raw body text, or `""`. Bodies are NOT redacted: an adapter's own request body is what a serialization assertion is about, and it never carries a credential (every credential rides a header). */
|
|
11
|
+
body: string;
|
|
12
|
+
}
|
|
13
|
+
export interface FakeRoute {
|
|
14
|
+
/** Matched against the request PATH exactly, or as a prefix when it ends in `*`. */
|
|
15
|
+
path: string;
|
|
16
|
+
method?: string;
|
|
17
|
+
handler: (req: Request, recorded: RecordedRequest) => Response | Promise<Response>;
|
|
18
|
+
}
|
|
19
|
+
export interface FakeServer {
|
|
20
|
+
/** `http://127.0.0.1:<port>` -- what a `ConnectionProfile.baseUrl` points at. */
|
|
21
|
+
url: string;
|
|
22
|
+
/** Every request received, in order. The ground truth. */
|
|
23
|
+
requests: RecordedRequest[];
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export interface StartFakeOptions {
|
|
27
|
+
routes: FakeRoute[];
|
|
28
|
+
/**
|
|
29
|
+
* Answers any path no route matched. Defaults to a 404 whose body names the path -- an explicit
|
|
30
|
+
* failure a test can assert on, rather than a hang or a confusing connection reset.
|
|
31
|
+
*/
|
|
32
|
+
fallback?: (req: Request, recorded: RecordedRequest) => Response | Promise<Response>;
|
|
33
|
+
/** How long `close()` waits before reporting the server would not stop. Default 2000 ms. */
|
|
34
|
+
closeDeadlineMs?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Starts a loopback fake.
|
|
38
|
+
*
|
|
39
|
+
* ALWAYS close it in a `finally`. A leaked fake keeps a port and an event loop alive for the rest of
|
|
40
|
+
* the process, which is how one careless test makes an unrelated one flaky.
|
|
41
|
+
*/
|
|
42
|
+
export declare function startFake(opts: StartFakeOptions): Promise<FakeServer>;
|
|
43
|
+
/** Runs `fn` against a fake and ALWAYS closes it. The shape every lane's fixture should use, so a leaked fake is not something anyone has to remember. */
|
|
44
|
+
export declare function withFake<T>(opts: StartFakeOptions, fn: (fake: FakeServer) => Promise<T>): Promise<T>;
|
|
45
|
+
/** One server-sent event as a family fake scripts it. `event` is omitted for a data-only frame. */
|
|
46
|
+
export interface SseFrame {
|
|
47
|
+
event?: string;
|
|
48
|
+
data: string;
|
|
49
|
+
/** Milliseconds to wait BEFORE writing this frame -- the primitive behind the slow-stream scenario. */
|
|
50
|
+
delayMs?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface SseResponseOptions {
|
|
53
|
+
status?: number;
|
|
54
|
+
headers?: Record<string, string>;
|
|
55
|
+
/**
|
|
56
|
+
* END the stream after `dropAfter` frames, WITHOUT the family's terminating event.
|
|
57
|
+
*
|
|
58
|
+
* The mid-stream-drop primitive, and its exact meaning is worth stating because a weaker reading
|
|
59
|
+
* would make it useless. What an SSE adapter can actually observe is "the byte stream ended before
|
|
60
|
+
* the terminator arrived" -- that is the failure a dropped upstream connection produces, and it is
|
|
61
|
+
* what this reproduces. What it deliberately does NOT do is call `controller.error()`: measured
|
|
62
|
+
* against Bun's own client, an errored server-side stream is indistinguishable from a truncated one
|
|
63
|
+
* (`res.text()` returns the frames written so far and does not throw) while ALSO surfacing an
|
|
64
|
+
* unhandled error that fails the runner for the wrong reason. So the primitive gives the honest,
|
|
65
|
+
* observable half, and a scenario asserts on the absent terminator rather than on an exception.
|
|
66
|
+
*/
|
|
67
|
+
dropAfter?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* An SSE response from a scripted frame list.
|
|
71
|
+
*
|
|
72
|
+
* FINITE BY CONSTRUCTION, and Task 2 recorded why that matters: an infinitely-pulling loopback fake
|
|
73
|
+
* hangs the RUNNER rather than failing the test, because its own event loop keeps pumping into a
|
|
74
|
+
* torn-down connection. Every stream this helper produces ends -- by running out of frames, or by
|
|
75
|
+
* the deliberate drop.
|
|
76
|
+
*/
|
|
77
|
+
export declare function sseResponse(frames: SseFrame[], opts?: SseResponseOptions): Response;
|
|
78
|
+
/** A JSON response. */
|
|
79
|
+
export declare function jsonResponse(value: unknown, status?: number, headers?: Record<string, string>): Response;
|
|
80
|
+
/** An error status WITH headers -- the shape a `Retry-After` / `anthropic-ratelimit-*` / `x-ratelimit-*` scenario needs. */
|
|
81
|
+
export declare function errorResponse(status: number, body: unknown, headers?: Record<string, string>): Response;
|
|
82
|
+
/**
|
|
83
|
+
* A redirect, for the endpoint-policy scenarios.
|
|
84
|
+
*
|
|
85
|
+
* `absolute` is what makes a CROSS-ORIGIN redirect expressible: the policy's own rule is that
|
|
86
|
+
* credentials are never forwarded across an origin change and a redirect carrying a request BODY is
|
|
87
|
+
* refused outright, and neither can be exercised with a same-origin relative `Location`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function redirectResponse(location: string, status?: 301 | 302 | 307 | 308): Response;
|
|
90
|
+
/**
|
|
91
|
+
* A response that never finishes writing, for the STALL scenario.
|
|
92
|
+
*
|
|
93
|
+
* Bounded by `holdMs` so it cannot outlive the test that started it, whatever the client does. The
|
|
94
|
+
* stall watchdog under test fires long before this does; the bound exists so a BROKEN watchdog fails
|
|
95
|
+
* the test on a timeout it can explain rather than hanging the runner.
|
|
96
|
+
*
|
|
97
|
+
* THE TIMER IS CANCELLED WHEN THE CONSUMER TEARS THE STREAM DOWN, and the reason is worth stating
|
|
98
|
+
* because getting it wrong was a cross-lane defect rather than a local one. A stall scenario ONLY
|
|
99
|
+
* ever ends by the consumer cancelling -- the watchdog fires long before `holdMs` by construction --
|
|
100
|
+
* so the "consumer cancelled first" path is the NORMAL path here, not an edge case. An unconditional
|
|
101
|
+
* `setTimeout(() => controller.close())` then fired against an already-closed controller and threw
|
|
102
|
+
* `TypeError: Invalid state: Controller is already closed` from a bare timer callback, which Bun
|
|
103
|
+
* attributes to WHICHEVER TEST HAPPENS TO BE RUNNING when it lands. It took down this package's own
|
|
104
|
+
* `corpus/runner.test.ts` and unrelated adapter cases in two separate lanes, purely by timing.
|
|
105
|
+
*
|
|
106
|
+
* Belt AND braces, deliberately: `cancel()` clears the timer, and the flag guards the close anyway --
|
|
107
|
+
* a stream can also be errored or closed by a path that never calls `cancel()`, and this helper is
|
|
108
|
+
* frozen spine that six lanes build on.
|
|
109
|
+
*/
|
|
110
|
+
export declare function stalledResponse(holdMs?: number): Response;
|
|
111
|
+
/** One scripted answer, keyed by the MODEL ID the request asked for. */
|
|
112
|
+
export type ScenarioResponder = (recorded: RecordedRequest, attempt: number) => Response | Promise<Response>;
|
|
113
|
+
export interface ScenarioTableOptions {
|
|
114
|
+
/**
|
|
115
|
+
* Reads the model id out of a request. Family-specific -- OpenAI Responses and Anthropic Messages
|
|
116
|
+
* both put it in the JSON body, Gemini puts it in the PATH -- so each lane supplies its own rather
|
|
117
|
+
* than this base guessing.
|
|
118
|
+
*/
|
|
119
|
+
modelOf: (recorded: RecordedRequest) => string | undefined;
|
|
120
|
+
/** modelId -> the scripted answer. A responder receives the 1-based ATTEMPT count for that model, so a retry scenario is a single entry rather than a stateful closure per test. */
|
|
121
|
+
scenarios: Record<string, ScenarioResponder | Response[]>;
|
|
122
|
+
/** Answers a model id with no entry. Defaults to a 400 naming the id -- a loud, assertable failure. */
|
|
123
|
+
unknownModel?: ScenarioResponder;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Builds a route handler from a MODEL-KEYED table.
|
|
127
|
+
*
|
|
128
|
+
* Keying on the model id is what lets one fake serve a whole corpus: a scenario picks its behaviour
|
|
129
|
+
* by asking for `"stall-model"` or `"retry-then-200"` instead of every case needing its own server,
|
|
130
|
+
* its own port and its own teardown. The ATTEMPT counter is per model, so "529 then 200" is one
|
|
131
|
+
* two-element array rather than a closure a test has to reset.
|
|
132
|
+
*/
|
|
133
|
+
export declare function scenarioTable(opts: ScenarioTableOptions): (req: Request, recorded: RecordedRequest) => Response | Promise<Response>;
|
|
134
|
+
/** The requests whose path matches, for an assertion that does not want to count a health probe. */
|
|
135
|
+
export declare function requestsTo(fake: FakeServer, path: string): RecordedRequest[];
|
|
136
|
+
/**
|
|
137
|
+
* True when NO request the fake received carries `needle` anywhere -- headers or body.
|
|
138
|
+
*
|
|
139
|
+
* The negative every hermeticity and opacity assertion needs: "the marker appeared in no request
|
|
140
|
+
* body" is exactly how capture (H) proved the sidecar was never sent to a model, and the same shape
|
|
141
|
+
* proves an adapter never replayed opaque state across a domain.
|
|
142
|
+
*/
|
|
143
|
+
export declare function noRequestContains(fake: FakeServer, needle: string): boolean;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type FakeRoute, type RecordedRequest } from "./server.js";
|
|
2
|
+
import { type GeminiFakeOptions } from "./gemini.js";
|
|
3
|
+
/** A keypair for one test run. NEVER committed: this generates a fresh one every call. */
|
|
4
|
+
export declare function generateTestKeyPair(): Promise<{
|
|
5
|
+
privateKeyPem: string;
|
|
6
|
+
publicKey: CryptoKey;
|
|
7
|
+
}>;
|
|
8
|
+
/** One assertion the token endpoint verified, recorded so a test can assert on its claims. */
|
|
9
|
+
export interface VerifiedAssertion {
|
|
10
|
+
header: Record<string, unknown>;
|
|
11
|
+
claims: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
export interface VertexFakeOptions extends GeminiFakeOptions {
|
|
14
|
+
/** The public half of the in-test keypair. The token route verifies every assertion against it. */
|
|
15
|
+
publicKey: CryptoKey;
|
|
16
|
+
project: string;
|
|
17
|
+
location: string;
|
|
18
|
+
/** The `aud` every assertion must carry -- the fake's own token URL. */
|
|
19
|
+
tokenUri: string;
|
|
20
|
+
/** Filled in as assertions are verified. The evidence a claim assertion reads. */
|
|
21
|
+
verified: VerifiedAssertion[];
|
|
22
|
+
/** The access token the exchange mints. Distinctive so a "never logged" negative is meaningful. */
|
|
23
|
+
accessToken?: string;
|
|
24
|
+
/** `expires_in`, in seconds. `0` scripts a token that is already expired, for the cache fixture. */
|
|
25
|
+
expiresIn?: number;
|
|
26
|
+
/** Rejects every exchange, for the credential-failure fixture. */
|
|
27
|
+
rejectExchange?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** The default access token. `test-...` by the lane's own rule; distinctive so `noRequestContains` means something. */
|
|
30
|
+
export declare const VERTEX_TEST_ACCESS_TOKEN = "test-vertex-access-token-1";
|
|
31
|
+
/** The token URL a fixture points a service-account JSON's `token_uri` at. */
|
|
32
|
+
export declare function vertexTokenUrl(fakeUrl: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* The Vertex routes: the token endpoint plus the location-scoped model paths.
|
|
35
|
+
*
|
|
36
|
+
* The model path is prefix-matched under `/v1/projects/...` because the model id and the method are
|
|
37
|
+
* both IN the path. `assertVertexPath` is what checks it is the exact shape ruling R6-A names.
|
|
38
|
+
*
|
|
39
|
+
* IT APPLIES THE GEMINI FAKE'S OWN BODY VALIDATORS (Lane B r4 carry). This route duplicated the
|
|
40
|
+
* model path and validated NOTHING, so the two merge pins and the round-3 decoration pin on this
|
|
41
|
+
* transport bit only via direct `roles`/`parts` assertions — a wire shape Vertex would reject was
|
|
42
|
+
* invisible here while the identical shape 400'd on the Gemini route. The adapters share one wire
|
|
43
|
+
* mapping; the fakes now share the invariants that mapping has to satisfy.
|
|
44
|
+
*/
|
|
45
|
+
export declare function vertexFakeRoutes(opts: VertexFakeOptions): FakeRoute[];
|
|
46
|
+
/**
|
|
47
|
+
* Asserts the request landed on the EXACT location-endpoint path ruling R6-A names, and carried a
|
|
48
|
+
* bearer token rather than an api key.
|
|
49
|
+
*
|
|
50
|
+
* The authorization value is checked in its REDACTED form -- the base replaces a credential header's
|
|
51
|
+
* material as it records, so `Bearer ***` proves both that the adapter authenticated and that the
|
|
52
|
+
* redaction ran.
|
|
53
|
+
*/
|
|
54
|
+
export declare function assertVertexRequest(recorded: RecordedRequest, expected: {
|
|
55
|
+
project: string;
|
|
56
|
+
location: string;
|
|
57
|
+
model: string;
|
|
58
|
+
method?: string;
|
|
59
|
+
search?: string;
|
|
60
|
+
}): void;
|