@yolk-sdk/emulators 0.1.0-canary.96
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/README.md +2135 -0
- package/dist/anthropic.d.mts +269 -0
- package/dist/anthropic.d.mts.map +1 -0
- package/dist/anthropic.mjs +177 -0
- package/dist/anthropic.mjs.map +1 -0
- package/dist/chat-completions.d.mts +288 -0
- package/dist/chat-completions.d.mts.map +1 -0
- package/dist/chat-completions.mjs +451 -0
- package/dist/chat-completions.mjs.map +1 -0
- package/dist/codex.d.mts +265 -0
- package/dist/codex.d.mts.map +1 -0
- package/dist/codex.mjs +199 -0
- package/dist/codex.mjs.map +1 -0
- package/dist/dropbox/api.d.mts +63 -0
- package/dist/dropbox/api.d.mts.map +1 -0
- package/dist/dropbox/api.mjs +565 -0
- package/dist/dropbox/api.mjs.map +1 -0
- package/dist/dropbox/state.d.mts +136 -0
- package/dist/dropbox/state.d.mts.map +1 -0
- package/dist/dropbox/state.mjs +209 -0
- package/dist/dropbox/state.mjs.map +1 -0
- package/dist/dropbox.d.mts +79 -0
- package/dist/dropbox.d.mts.map +1 -0
- package/dist/dropbox.mjs +124 -0
- package/dist/dropbox.mjs.map +1 -0
- package/dist/email-fixtures.d.mts +36 -0
- package/dist/email-fixtures.d.mts.map +1 -0
- package/dist/email-fixtures.mjs +1080 -0
- package/dist/email-fixtures.mjs.map +1 -0
- package/dist/email.d.mts +160 -0
- package/dist/email.d.mts.map +1 -0
- package/dist/email.mjs +608 -0
- package/dist/email.mjs.map +1 -0
- package/dist/emulator-compose.d.mts +40 -0
- package/dist/emulator-compose.d.mts.map +1 -0
- package/dist/emulator-compose.mjs +91 -0
- package/dist/emulator-compose.mjs.map +1 -0
- package/dist/emulator-http.d.mts +53 -0
- package/dist/emulator-http.d.mts.map +1 -0
- package/dist/emulator-http.mjs +117 -0
- package/dist/emulator-http.mjs.map +1 -0
- package/dist/emulator-kernel.d.mts +176 -0
- package/dist/emulator-kernel.d.mts.map +1 -0
- package/dist/emulator-kernel.mjs +413 -0
- package/dist/emulator-kernel.mjs.map +1 -0
- package/dist/fixture-route.d.mts +129 -0
- package/dist/fixture-route.d.mts.map +1 -0
- package/dist/fixture-route.mjs +257 -0
- package/dist/fixture-route.mjs.map +1 -0
- package/dist/fortnox/api.d.mts +92 -0
- package/dist/fortnox/api.d.mts.map +1 -0
- package/dist/fortnox/api.mjs +750 -0
- package/dist/fortnox/api.mjs.map +1 -0
- package/dist/fortnox/state.d.mts +533 -0
- package/dist/fortnox/state.d.mts.map +1 -0
- package/dist/fortnox/state.mjs +612 -0
- package/dist/fortnox/state.mjs.map +1 -0
- package/dist/fortnox.d.mts +128 -0
- package/dist/fortnox.d.mts.map +1 -0
- package/dist/fortnox.mjs +403 -0
- package/dist/fortnox.mjs.map +1 -0
- package/dist/gateway-evaluate-recordings.d.mts +12 -0
- package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
- package/dist/gateway-evaluate-recordings.mjs +128 -0
- package/dist/gateway-evaluate-recordings.mjs.map +1 -0
- package/dist/gateway.d.mts +272 -0
- package/dist/gateway.d.mts.map +1 -0
- package/dist/gateway.mjs +400 -0
- package/dist/gateway.mjs.map +1 -0
- package/dist/github/api.d.mts +63 -0
- package/dist/github/api.d.mts.map +1 -0
- package/dist/github/api.mjs +527 -0
- package/dist/github/api.mjs.map +1 -0
- package/dist/github/state.d.mts +272 -0
- package/dist/github/state.d.mts.map +1 -0
- package/dist/github/state.mjs +303 -0
- package/dist/github/state.mjs.map +1 -0
- package/dist/github.d.mts +77 -0
- package/dist/github.d.mts.map +1 -0
- package/dist/github.mjs +180 -0
- package/dist/github.mjs.map +1 -0
- package/dist/google/calendar.d.mts +10 -0
- package/dist/google/calendar.d.mts.map +1 -0
- package/dist/google/calendar.mjs +252 -0
- package/dist/google/calendar.mjs.map +1 -0
- package/dist/google/drive.d.mts +8 -0
- package/dist/google/drive.d.mts.map +1 -0
- package/dist/google/drive.mjs +252 -0
- package/dist/google/drive.mjs.map +1 -0
- package/dist/google/gmail.d.mts +10 -0
- package/dist/google/gmail.d.mts.map +1 -0
- package/dist/google/gmail.mjs +591 -0
- package/dist/google/gmail.mjs.map +1 -0
- package/dist/google/shared.d.mts +107 -0
- package/dist/google/shared.d.mts.map +1 -0
- package/dist/google/shared.mjs +146 -0
- package/dist/google/shared.mjs.map +1 -0
- package/dist/google/state.d.mts +385 -0
- package/dist/google/state.d.mts.map +1 -0
- package/dist/google/state.mjs +496 -0
- package/dist/google/state.mjs.map +1 -0
- package/dist/google.d.mts +75 -0
- package/dist/google.d.mts.map +1 -0
- package/dist/google.mjs +210 -0
- package/dist/google.mjs.map +1 -0
- package/dist/linkedin-search/api.d.mts +45 -0
- package/dist/linkedin-search/api.d.mts.map +1 -0
- package/dist/linkedin-search/api.mjs +181 -0
- package/dist/linkedin-search/api.mjs.map +1 -0
- package/dist/linkedin-search/state.d.mts +123 -0
- package/dist/linkedin-search/state.d.mts.map +1 -0
- package/dist/linkedin-search/state.mjs +261 -0
- package/dist/linkedin-search/state.mjs.map +1 -0
- package/dist/linkedin-search.d.mts +69 -0
- package/dist/linkedin-search.d.mts.map +1 -0
- package/dist/linkedin-search.mjs +158 -0
- package/dist/linkedin-search.mjs.map +1 -0
- package/dist/mcp/api.d.mts +44 -0
- package/dist/mcp/api.d.mts.map +1 -0
- package/dist/mcp/api.mjs +557 -0
- package/dist/mcp/api.mjs.map +1 -0
- package/dist/mcp/recordings.d.mts +40 -0
- package/dist/mcp/recordings.d.mts.map +1 -0
- package/dist/mcp/recordings.mjs +2390 -0
- package/dist/mcp/recordings.mjs.map +1 -0
- package/dist/mcp/state.d.mts +71 -0
- package/dist/mcp/state.d.mts.map +1 -0
- package/dist/mcp/state.mjs +83 -0
- package/dist/mcp/state.mjs.map +1 -0
- package/dist/mcp.d.mts +84 -0
- package/dist/mcp.d.mts.map +1 -0
- package/dist/mcp.mjs +241 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/messages.d.mts +183 -0
- package/dist/messages.d.mts.map +1 -0
- package/dist/messages.mjs +532 -0
- package/dist/messages.mjs.map +1 -0
- package/dist/microsoft/api.d.mts +47 -0
- package/dist/microsoft/api.d.mts.map +1 -0
- package/dist/microsoft/api.mjs +178 -0
- package/dist/microsoft/api.mjs.map +1 -0
- package/dist/microsoft/calendar.d.mts +30 -0
- package/dist/microsoft/calendar.d.mts.map +1 -0
- package/dist/microsoft/calendar.mjs +271 -0
- package/dist/microsoft/calendar.mjs.map +1 -0
- package/dist/microsoft/drive.d.mts +36 -0
- package/dist/microsoft/drive.d.mts.map +1 -0
- package/dist/microsoft/drive.mjs +298 -0
- package/dist/microsoft/drive.mjs.map +1 -0
- package/dist/microsoft/graph.d.mts +198 -0
- package/dist/microsoft/graph.d.mts.map +1 -0
- package/dist/microsoft/graph.mjs +264 -0
- package/dist/microsoft/graph.mjs.map +1 -0
- package/dist/microsoft/mail.d.mts +48 -0
- package/dist/microsoft/mail.d.mts.map +1 -0
- package/dist/microsoft/mail.mjs +488 -0
- package/dist/microsoft/mail.mjs.map +1 -0
- package/dist/microsoft/state.d.mts +480 -0
- package/dist/microsoft/state.d.mts.map +1 -0
- package/dist/microsoft/state.mjs +625 -0
- package/dist/microsoft/state.mjs.map +1 -0
- package/dist/microsoft.d.mts +168 -0
- package/dist/microsoft.d.mts.map +1 -0
- package/dist/microsoft.mjs +483 -0
- package/dist/microsoft.mjs.map +1 -0
- package/dist/node.d.mts +47 -0
- package/dist/node.d.mts.map +1 -0
- package/dist/node.mjs +178 -0
- package/dist/node.mjs.map +1 -0
- package/dist/notion/api.d.mts +51 -0
- package/dist/notion/api.d.mts.map +1 -0
- package/dist/notion/api.mjs +507 -0
- package/dist/notion/api.mjs.map +1 -0
- package/dist/notion/state.d.mts +329 -0
- package/dist/notion/state.d.mts.map +1 -0
- package/dist/notion/state.mjs +432 -0
- package/dist/notion/state.mjs.map +1 -0
- package/dist/notion.d.mts +72 -0
- package/dist/notion.d.mts.map +1 -0
- package/dist/notion.mjs +127 -0
- package/dist/notion.mjs.map +1 -0
- package/dist/openai.d.mts +165 -0
- package/dist/openai.d.mts.map +1 -0
- package/dist/openai.mjs +141 -0
- package/dist/openai.mjs.map +1 -0
- package/dist/opencode-recordings.d.mts +7 -0
- package/dist/opencode-recordings.d.mts.map +1 -0
- package/dist/opencode-recordings.mjs +222 -0
- package/dist/opencode-recordings.mjs.map +1 -0
- package/dist/opencode.d.mts +70 -0
- package/dist/opencode.d.mts.map +1 -0
- package/dist/opencode.mjs +217 -0
- package/dist/opencode.mjs.map +1 -0
- package/dist/r2-fixtures.d.mts +36 -0
- package/dist/r2-fixtures.d.mts.map +1 -0
- package/dist/r2-fixtures.mjs +245 -0
- package/dist/r2-fixtures.mjs.map +1 -0
- package/dist/r2-guard.d.mts +43 -0
- package/dist/r2-guard.d.mts.map +1 -0
- package/dist/r2-guard.mjs +215 -0
- package/dist/r2-guard.mjs.map +1 -0
- package/dist/r2.d.mts +166 -0
- package/dist/r2.d.mts.map +1 -0
- package/dist/r2.mjs +517 -0
- package/dist/r2.mjs.map +1 -0
- package/dist/responses.d.mts +231 -0
- package/dist/responses.d.mts.map +1 -0
- package/dist/responses.mjs +556 -0
- package/dist/responses.mjs.map +1 -0
- package/dist/route-evidence.d.mts +45 -0
- package/dist/route-evidence.d.mts.map +1 -0
- package/dist/route-evidence.mjs +56 -0
- package/dist/route-evidence.mjs.map +1 -0
- package/dist/router.d.mts +93 -0
- package/dist/router.d.mts.map +1 -0
- package/dist/router.mjs +259 -0
- package/dist/router.mjs.map +1 -0
- package/dist/stateful-core.d.mts +17 -0
- package/dist/stateful-core.d.mts.map +1 -0
- package/dist/stateful-core.mjs +41 -0
- package/dist/stateful-core.mjs.map +1 -0
- package/dist/stateful-emulator.d.mts +565 -0
- package/dist/stateful-emulator.d.mts.map +1 -0
- package/dist/stateful-emulator.mjs +1228 -0
- package/dist/stateful-emulator.mjs.map +1 -0
- package/dist/stateful-secrets.d.mts +84 -0
- package/dist/stateful-secrets.d.mts.map +1 -0
- package/dist/stateful-secrets.mjs +216 -0
- package/dist/stateful-secrets.mjs.map +1 -0
- package/dist/subscription-usage-recordings.d.mts +9 -0
- package/dist/subscription-usage-recordings.d.mts.map +1 -0
- package/dist/subscription-usage-recordings.mjs +53 -0
- package/dist/subscription-usage-recordings.mjs.map +1 -0
- package/dist/subscription-usage.d.mts +53 -0
- package/dist/subscription-usage.d.mts.map +1 -0
- package/dist/subscription-usage.mjs +21 -0
- package/dist/subscription-usage.mjs.map +1 -0
- package/dist/telegram/api.d.mts +34 -0
- package/dist/telegram/api.d.mts.map +1 -0
- package/dist/telegram/api.mjs +257 -0
- package/dist/telegram/api.mjs.map +1 -0
- package/dist/telegram/state.d.mts +128 -0
- package/dist/telegram/state.d.mts.map +1 -0
- package/dist/telegram/state.mjs +154 -0
- package/dist/telegram/state.mjs.map +1 -0
- package/dist/telegram.d.mts +62 -0
- package/dist/telegram.d.mts.map +1 -0
- package/dist/telegram.mjs +127 -0
- package/dist/telegram.mjs.map +1 -0
- package/dist/todoist/api.d.mts +55 -0
- package/dist/todoist/api.d.mts.map +1 -0
- package/dist/todoist/api.mjs +415 -0
- package/dist/todoist/api.mjs.map +1 -0
- package/dist/todoist/state.d.mts +275 -0
- package/dist/todoist/state.d.mts.map +1 -0
- package/dist/todoist/state.mjs +345 -0
- package/dist/todoist/state.mjs.map +1 -0
- package/dist/todoist.d.mts +68 -0
- package/dist/todoist.d.mts.map +1 -0
- package/dist/todoist.mjs +181 -0
- package/dist/todoist.mjs.map +1 -0
- package/dist/xai.d.mts +267 -0
- package/dist/xai.d.mts.map +1 -0
- package/dist/xai.mjs +252 -0
- package/dist/xai.mjs.map +1 -0
- package/package.json +157 -0
- package/src/anthropic.ts +289 -0
- package/src/chat-completions.ts +924 -0
- package/src/codex.ts +296 -0
- package/src/dropbox/api.ts +1084 -0
- package/src/dropbox/state.ts +364 -0
- package/src/dropbox.ts +203 -0
- package/src/email-fixtures.ts +1297 -0
- package/src/email.ts +1108 -0
- package/src/emulator-compose.ts +147 -0
- package/src/emulator-http.ts +184 -0
- package/src/emulator-kernel.ts +844 -0
- package/src/fixture-route.ts +561 -0
- package/src/fortnox/api.ts +1352 -0
- package/src/fortnox/state.ts +798 -0
- package/src/fortnox.ts +801 -0
- package/src/gateway-evaluate-recordings.ts +153 -0
- package/src/gateway.ts +530 -0
- package/src/github/api.ts +986 -0
- package/src/github/state.ts +439 -0
- package/src/github.ts +271 -0
- package/src/google/calendar.ts +486 -0
- package/src/google/drive.ts +471 -0
- package/src/google/gmail.ts +1011 -0
- package/src/google/shared.ts +321 -0
- package/src/google/state.ts +686 -0
- package/src/google.ts +298 -0
- package/src/linkedin-search/api.ts +363 -0
- package/src/linkedin-search/state.ts +373 -0
- package/src/linkedin-search.ts +241 -0
- package/src/mcp/api.ts +995 -0
- package/src/mcp/recordings.ts +2665 -0
- package/src/mcp/state.ts +125 -0
- package/src/mcp.ts +319 -0
- package/src/messages.ts +844 -0
- package/src/microsoft/api.ts +426 -0
- package/src/microsoft/calendar.ts +491 -0
- package/src/microsoft/drive.ts +541 -0
- package/src/microsoft/graph.ts +550 -0
- package/src/microsoft/mail.ts +833 -0
- package/src/microsoft/state.ts +822 -0
- package/src/microsoft.ts +982 -0
- package/src/node.ts +293 -0
- package/src/notion/api.ts +976 -0
- package/src/notion/state.ts +512 -0
- package/src/notion.ts +210 -0
- package/src/openai.ts +198 -0
- package/src/opencode-recordings.ts +257 -0
- package/src/opencode.ts +271 -0
- package/src/r2-fixtures.ts +295 -0
- package/src/r2-guard.ts +323 -0
- package/src/r2.ts +890 -0
- package/src/responses.ts +901 -0
- package/src/route-evidence.ts +90 -0
- package/src/router.ts +490 -0
- package/src/stateful-core.ts +70 -0
- package/src/stateful-emulator.ts +2571 -0
- package/src/stateful-secrets.ts +299 -0
- package/src/subscription-usage-recordings.ts +77 -0
- package/src/subscription-usage.ts +68 -0
- package/src/telegram/api.ts +437 -0
- package/src/telegram/state.ts +229 -0
- package/src/telegram.ts +227 -0
- package/src/todoist/api.ts +736 -0
- package/src/todoist/state.ts +456 -0
- package/src/todoist.ts +316 -0
- package/src/xai.ts +341 -0
package/dist/gateway.mjs
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { emulatorEvidenceHeader } from "./route-evidence.mjs";
|
|
2
|
+
import { composeFetch } from "./emulator-compose.mjs";
|
|
3
|
+
import { FixtureRouteFault, makeFixtureRouteEmulator } from "./fixture-route.mjs";
|
|
4
|
+
import { ChatFault, ChatFaultMatch, ChatScriptedError, ChatScriptedReasoningCompletion, ChatScriptedReasoningTurn, ChatScriptedToolCall, ChatScriptedUsage, makeChatCompletionsEmulator } from "./chat-completions.mjs";
|
|
5
|
+
import { gatewayEvaluateRecordings } from "./gateway-evaluate-recordings.mjs";
|
|
6
|
+
import { Data } from "effect";
|
|
7
|
+
//#region src/gateway.ts
|
|
8
|
+
/**
|
|
9
|
+
* Vercel AI Gateway emulator: a plain fetch handler for the OpenAI-compatible
|
|
10
|
+
* `POST /v1/chat/completions` endpoint, with scripted turns, wire faults, a
|
|
11
|
+
* request ledger, and an `/_emulate/*` control plane.
|
|
12
|
+
*
|
|
13
|
+
* It never imports SDK code: its wire shapes follow the verified live Gateway
|
|
14
|
+
* conformance recordings (2026-09-30), copied as data: `chat.completion.chunk`
|
|
15
|
+
* SSE with several events per network chunk, `delta.reasoning` plus
|
|
16
|
+
* `reasoning_details` for DeepSeek reasoning, a finish event carrying
|
|
17
|
+
* `provider_metadata`, `usage`, `system_fingerprint`, `service_tier`, and
|
|
18
|
+
* `generationId`, then `data: [DONE]`, and the 404 `model_not_found`
|
|
19
|
+
* envelope `{ error: { message, type, param: { modelId } } }` for unknown
|
|
20
|
+
* models. Ids, costs, and routing metadata are synthetic stand-ins. The route
|
|
21
|
+
* is linked to those conformance case ids in `gatewayEmulatorRoutes`
|
|
22
|
+
* (`verified`). The Chat Completions machinery is shared with the OpenAI
|
|
23
|
+
* emulator (`chat-completions.ts`); this module supplies the Gateway's paths,
|
|
24
|
+
* models, envelope, reasoning, and wire profile.
|
|
25
|
+
*
|
|
26
|
+
* Not covered by a recording (synthetic): the 401 error, default fault
|
|
27
|
+
* bodies, the non-streamed `chat.completion` body, and the answer to a request
|
|
28
|
+
* without a `model` (404 `Model '' not found` with `param.modelId: null`).
|
|
29
|
+
*
|
|
30
|
+
* The same fetch handler also answers the classifier route `POST /v1/evaluate`
|
|
31
|
+
* (AI Gateway calls classification "evaluation"; `emulator.evaluate`) with its
|
|
32
|
+
* own manifest (`gatewayEvaluateEmulatorRoutes`, unverified), ledger, faults,
|
|
33
|
+
* and turns. It is fixture-only: a request matching one of the four synthetic
|
|
34
|
+
* classifier conformance recordings (boolean, choice, score, unknown-model
|
|
35
|
+
* error envelope; `gateway-evaluate-recordings.ts`, copied as data) within the
|
|
36
|
+
* shared request-shape latitude gets that recording's response; anything else
|
|
37
|
+
* answers 400 not-emulated.
|
|
38
|
+
*
|
|
39
|
+
* Runtime-portable Web APIs only (`Request`, `Response`, `ReadableStream`,
|
|
40
|
+
* `TextEncoder`, `URL`); no Effect runtime is required to use it.
|
|
41
|
+
*
|
|
42
|
+
* @experimental
|
|
43
|
+
*/
|
|
44
|
+
const gatewayChatCompletionsPath = "/v1/chat/completions";
|
|
45
|
+
/** The AI Gateway classifier ("evaluation") path. */
|
|
46
|
+
const gatewayEvaluatePath = "/v1/evaluate";
|
|
47
|
+
/** Synthetic-safe default model ids, including the Gateway conformance defaults. */
|
|
48
|
+
const gatewayEmulatorDefaultModels = [
|
|
49
|
+
"openai/gpt-4.1-nano",
|
|
50
|
+
"openai/gpt-4.1-mini",
|
|
51
|
+
"deepseek/deepseek-v3.2",
|
|
52
|
+
"deepseek/deepseek-v4.1-flash"
|
|
53
|
+
];
|
|
54
|
+
/** Models that stream reasoning deltas when reasoning is requested. */
|
|
55
|
+
const gatewayEmulatorDefaultReasoningModels = ["deepseek/deepseek-v3.2", "deepseek/deepseek-v4.1-flash"];
|
|
56
|
+
/**
|
|
57
|
+
* SSE events per network chunk by default. The live Gateway packs several
|
|
58
|
+
* events into one network chunk (the recordings carry one to four, always
|
|
59
|
+
* with the finish event and `data: [DONE]` together in the last chunk).
|
|
60
|
+
*/
|
|
61
|
+
const gatewayEmulatorDefaultEventsPerChunk = 2;
|
|
62
|
+
/**
|
|
63
|
+
* Route evidence manifest: every emulated Gateway route and the conformance
|
|
64
|
+
* cases whose verified live recordings (2026-09-30) its wire shapes follow.
|
|
65
|
+
*/
|
|
66
|
+
const gatewayEmulatorRoutes = [{
|
|
67
|
+
method: "POST",
|
|
68
|
+
path: gatewayChatCompletionsPath,
|
|
69
|
+
kind: "provider",
|
|
70
|
+
write: false,
|
|
71
|
+
caseIds: [
|
|
72
|
+
"vercel-ai-gateway.stream.plain-text",
|
|
73
|
+
"vercel-ai-gateway.stream.deepseek-reasoning",
|
|
74
|
+
"vercel-ai-gateway.stream.tool-call-deltas",
|
|
75
|
+
"vercel-ai-gateway.stream.error-envelope"
|
|
76
|
+
],
|
|
77
|
+
evidence: "verified",
|
|
78
|
+
observedAt: "2026-09-30"
|
|
79
|
+
}];
|
|
80
|
+
/**
|
|
81
|
+
* Route evidence manifest of the classifier route (served by the same fetch handler, with its own
|
|
82
|
+
* ledger, faults, and coverage). Its recordings are synthetic placeholders, so it is unverified.
|
|
83
|
+
*/
|
|
84
|
+
const gatewayEvaluateEmulatorRoutes = [{
|
|
85
|
+
method: "POST",
|
|
86
|
+
path: gatewayEvaluatePath,
|
|
87
|
+
kind: "provider",
|
|
88
|
+
write: false,
|
|
89
|
+
caseIds: [
|
|
90
|
+
"vercel-ai-gateway.classify.boolean",
|
|
91
|
+
"vercel-ai-gateway.classify.choice",
|
|
92
|
+
"vercel-ai-gateway.classify.score",
|
|
93
|
+
"vercel-ai-gateway.classify.error-envelope"
|
|
94
|
+
],
|
|
95
|
+
evidence: "unverified",
|
|
96
|
+
observedAt: void 0
|
|
97
|
+
}];
|
|
98
|
+
/** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
|
|
99
|
+
const GatewayFaultMatch = ChatFaultMatch;
|
|
100
|
+
/**
|
|
101
|
+
* Wire faults for emulated routes:
|
|
102
|
+
*
|
|
103
|
+
* - `status`: answer with this status, headers, and body instead of a
|
|
104
|
+
* completion (for example 429 with `retry-after`). The body defaults to a
|
|
105
|
+
* Gateway error envelope. Statuses that cannot carry a body (1xx, 204, 205,
|
|
106
|
+
* 304) and redirects (3xx) are rejected, as are invalid header names or
|
|
107
|
+
* values and a `location` header; scripted errors follow the same rules.
|
|
108
|
+
* - `error-after-chunks`: send `chunks` body chunks, then error the body
|
|
109
|
+
* stream (a dropped connection).
|
|
110
|
+
* - `truncate-after-chunks`: send `chunks` body chunks, then close the body
|
|
111
|
+
* cleanly (for example without `data: [DONE]`).
|
|
112
|
+
*
|
|
113
|
+
* Chunk faults count network chunks: a streamed chunk carries up to
|
|
114
|
+
* `eventsPerChunk` SSE events (default 2), and a whole JSON body counts as one
|
|
115
|
+
* chunk. A chunk fault that cannot take effect (`error-after-chunks` beyond
|
|
116
|
+
* the chunk count, or `truncate-after-chunks` at or beyond it) answers 500
|
|
117
|
+
* with an emulator error instead of silently doing nothing, and is not
|
|
118
|
+
* consumed.
|
|
119
|
+
*/
|
|
120
|
+
const GatewayFault = ChatFault;
|
|
121
|
+
/**
|
|
122
|
+
* Usage for a scripted turn, sent as the Gateway `usage` object (token counts,
|
|
123
|
+
* `completion_tokens_details.reasoning_tokens` defaulting to 0, and synthetic
|
|
124
|
+
* zero costs).
|
|
125
|
+
*/
|
|
126
|
+
const GatewayScriptedUsage = ChatScriptedUsage;
|
|
127
|
+
/** One scripted tool call; its JSON arguments stream as these fragments. */
|
|
128
|
+
const GatewayScriptedToolCall = ChatScriptedToolCall;
|
|
129
|
+
/**
|
|
130
|
+
* A scripted completion. Every field is exact (nothing is filled in) except:
|
|
131
|
+
* `usage` omitted is synthesized when the request asks for usage, and `null`
|
|
132
|
+
* drops it; `finishReason` omitted is `tool_calls` with tool calls, else
|
|
133
|
+
* `stop`. `order` defaults to `reasoning-first`; `reasoningField` defaults to
|
|
134
|
+
* `reasoning` (streamed with `reasoning_details`, as recorded live); the
|
|
135
|
+
* DeepSeek-native `reasoning_content` is sent alone.
|
|
136
|
+
*/
|
|
137
|
+
const GatewayScriptedCompletion = ChatScriptedReasoningCompletion;
|
|
138
|
+
/** A scripted error response: status, body (a string is sent as is), and optional headers. */
|
|
139
|
+
const GatewayScriptedError = ChatScriptedError;
|
|
140
|
+
/** A turn queued for the next chat completion request. */
|
|
141
|
+
const GatewayScriptedTurn = ChatScriptedReasoningTurn;
|
|
142
|
+
/**
|
|
143
|
+
* Thrown by the JS API (`faults.add`, `script.enqueue`) for invalid input, and
|
|
144
|
+
* by `makeGatewayEmulator` for invalid options; a programmer error.
|
|
145
|
+
*/
|
|
146
|
+
var GatewayEmulatorInputInvalid = class extends Data.TaggedError("GatewayEmulatorInputInvalid") {
|
|
147
|
+
get message() {
|
|
148
|
+
return `Invalid Gateway emulator ${this.input}: ${this.reason}`;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
/** A classifier-route fault (`status` 400-599, `error-after-chunks`, `truncate-after-chunks`). */
|
|
152
|
+
const GatewayEvaluateFault = FixtureRouteFault;
|
|
153
|
+
const gatewayErrorEnvelope = (error) => ({ error: {
|
|
154
|
+
message: error.message,
|
|
155
|
+
type: error.type,
|
|
156
|
+
code: error.code
|
|
157
|
+
} });
|
|
158
|
+
const syntheticFingerprint = "fp_synthetic";
|
|
159
|
+
const syntheticAttemptTime = 179e10;
|
|
160
|
+
const syntheticCost = "0";
|
|
161
|
+
const vendorOf = (model) => {
|
|
162
|
+
const [vendor] = model.split("/");
|
|
163
|
+
return vendor === void 0 || vendor.length === 0 || vendor === model ? void 0 : vendor;
|
|
164
|
+
};
|
|
165
|
+
const isOpenAiModel = (model) => vendorOf(model) === "openai";
|
|
166
|
+
const syntheticResponseId = (identity) => `resp_synthetic_${identity.id}`;
|
|
167
|
+
/**
|
|
168
|
+
* The upstream provider the Gateway routes a model to, and its `provider_metadata` entry (keyed by
|
|
169
|
+
* that provider), per model family as recorded: `openai/*` routes to `openai` with
|
|
170
|
+
* `{ responseId, serviceTier }`; `deepseek/*` routes to `baseten` with
|
|
171
|
+
* `{ acceptedPredictionTokens, rejectedPredictionTokens }` (synthetic zero counts). Other families
|
|
172
|
+
* were not recorded: they route to their vendor prefix without an upstream entry.
|
|
173
|
+
*/
|
|
174
|
+
const upstreamOf = (identity) => {
|
|
175
|
+
const vendor = vendorOf(identity.model);
|
|
176
|
+
if (vendor === "openai") return {
|
|
177
|
+
provider: "openai",
|
|
178
|
+
entry: {
|
|
179
|
+
responseId: syntheticResponseId(identity),
|
|
180
|
+
serviceTier: "default"
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
if (vendor === "deepseek") return {
|
|
184
|
+
provider: "baseten",
|
|
185
|
+
entry: {
|
|
186
|
+
acceptedPredictionTokens: 0,
|
|
187
|
+
rejectedPredictionTokens: 0
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return { provider: vendor ?? "synthetic" };
|
|
191
|
+
};
|
|
192
|
+
/** The `gateway` entry of `provider_metadata`: routing, costs, and the generation id. */
|
|
193
|
+
const gatewayRoutingMetadata = (identity) => {
|
|
194
|
+
const { provider } = upstreamOf(identity);
|
|
195
|
+
const responseId = syntheticResponseId(identity);
|
|
196
|
+
return {
|
|
197
|
+
routing: {
|
|
198
|
+
originalModelId: identity.model,
|
|
199
|
+
resolvedProvider: provider,
|
|
200
|
+
fallbacksAvailable: ["synthetic-fallback"],
|
|
201
|
+
planningReasoning: "Synthetic: routing planned by the emulator.",
|
|
202
|
+
canonicalSlug: identity.model,
|
|
203
|
+
finalProvider: provider,
|
|
204
|
+
modelAttemptCount: 1,
|
|
205
|
+
modelAttempts: [{
|
|
206
|
+
canonicalSlug: identity.model,
|
|
207
|
+
success: true,
|
|
208
|
+
providerAttemptCount: 1,
|
|
209
|
+
providerAttempts: [{
|
|
210
|
+
provider,
|
|
211
|
+
credentialType: "system",
|
|
212
|
+
success: true,
|
|
213
|
+
startTime: syntheticAttemptTime,
|
|
214
|
+
endTime: syntheticAttemptTime + 1,
|
|
215
|
+
providerRequestId: `req_synthetic_${identity.id}`,
|
|
216
|
+
statusCode: 200,
|
|
217
|
+
providerResponseId: responseId
|
|
218
|
+
}]
|
|
219
|
+
}],
|
|
220
|
+
totalProviderAttemptCount: 1,
|
|
221
|
+
affinity: { outcome: "skipped_below_min_prefix" },
|
|
222
|
+
clientSessionId: "synthetic-client-session",
|
|
223
|
+
clientSessionIdSource: "fingerprint"
|
|
224
|
+
},
|
|
225
|
+
cost: syntheticCost,
|
|
226
|
+
marketCost: syntheticCost,
|
|
227
|
+
surchargeCost: syntheticCost,
|
|
228
|
+
gatewayCost: syntheticCost,
|
|
229
|
+
inferenceCost: syntheticCost,
|
|
230
|
+
inputInferenceCost: syntheticCost,
|
|
231
|
+
outputInferenceCost: syntheticCost,
|
|
232
|
+
generationId: identity.id
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
/** `provider_metadata`: the upstream provider's entry first (when its family has one), then `gateway`. */
|
|
236
|
+
const gatewayProviderMetadata = (identity) => {
|
|
237
|
+
const metadata = {};
|
|
238
|
+
const upstream = upstreamOf(identity);
|
|
239
|
+
if (upstream.entry !== void 0) metadata[upstream.provider] = upstream.entry;
|
|
240
|
+
metadata.gateway = gatewayRoutingMetadata(identity);
|
|
241
|
+
return metadata;
|
|
242
|
+
};
|
|
243
|
+
const gatewayUsage = (counts) => ({
|
|
244
|
+
prompt_tokens: counts.promptTokens,
|
|
245
|
+
completion_tokens: counts.completionTokens,
|
|
246
|
+
total_tokens: counts.promptTokens + counts.completionTokens,
|
|
247
|
+
cost: 0,
|
|
248
|
+
is_byok: false,
|
|
249
|
+
prompt_tokens_details: {
|
|
250
|
+
cached_tokens: 0,
|
|
251
|
+
audio_tokens: 0,
|
|
252
|
+
video_tokens: 0
|
|
253
|
+
},
|
|
254
|
+
cost_details: {
|
|
255
|
+
upstream_inference_cost: null,
|
|
256
|
+
upstream_inference_prompt_cost: 0,
|
|
257
|
+
upstream_inference_completions_cost: 0
|
|
258
|
+
},
|
|
259
|
+
completion_tokens_details: {
|
|
260
|
+
reasoning_tokens: counts.reasoningTokens ?? 0,
|
|
261
|
+
image_tokens: 0
|
|
262
|
+
},
|
|
263
|
+
cache_creation_input_tokens: 0,
|
|
264
|
+
market_cost: 0,
|
|
265
|
+
gateway_cost: 0
|
|
266
|
+
});
|
|
267
|
+
const gatewayWireProfile = (eventsPerChunk) => ({
|
|
268
|
+
eventsPerChunk,
|
|
269
|
+
streamUsage: "finish-event",
|
|
270
|
+
openingDelta: { role: "assistant" },
|
|
271
|
+
choiceFields: { logprobs: null },
|
|
272
|
+
reasoningField: "reasoning",
|
|
273
|
+
reasoningDetails: true,
|
|
274
|
+
chunkFields: () => ({ system_fingerprint: syntheticFingerprint }),
|
|
275
|
+
finishFields: (identity) => {
|
|
276
|
+
const chunk = {};
|
|
277
|
+
if (isOpenAiModel(identity.model)) chunk.service_tier = "default";
|
|
278
|
+
chunk.generationId = identity.id;
|
|
279
|
+
return {
|
|
280
|
+
delta: { provider_metadata: gatewayProviderMetadata(identity) },
|
|
281
|
+
chunk
|
|
282
|
+
};
|
|
283
|
+
},
|
|
284
|
+
usage: gatewayUsage
|
|
285
|
+
});
|
|
286
|
+
const validEventsPerChunk = (eventsPerChunk) => {
|
|
287
|
+
const value = eventsPerChunk ?? 2;
|
|
288
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new GatewayEmulatorInputInvalid({
|
|
289
|
+
input: "options",
|
|
290
|
+
reason: `eventsPerChunk must be a positive integer, got ${String(eventsPerChunk)}`
|
|
291
|
+
});
|
|
292
|
+
return value;
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* Create a Vercel AI Gateway emulator. Each call has independent ledger,
|
|
296
|
+
* fault, and script state. Throws `GatewayEmulatorInputInvalid` for an
|
|
297
|
+
* invalid `eventsPerChunk`.
|
|
298
|
+
*
|
|
299
|
+
* Without a script, `POST /v1/chat/completions` answers a known model with
|
|
300
|
+
* synthetic text deltas. `stream: true` sends `chat.completion.chunk` SSE in
|
|
301
|
+
* the recorded Gateway shape: a `{ role: 'assistant' }` opening delta, content
|
|
302
|
+
* deltas, and a finish event whose delta carries `provider_metadata` and which
|
|
303
|
+
* carries `usage` (when `stream_options.include_usage` is set),
|
|
304
|
+
* `system_fingerprint`, `service_tier` (for `openai/*` models), and
|
|
305
|
+
* `generationId`, then `data: [DONE]`; every chunk carries
|
|
306
|
+
* `system_fingerprint` and `logprobs: null`, and events are packed
|
|
307
|
+
* `eventsPerChunk` per network chunk. `stream: false` answers one
|
|
308
|
+
* `chat.completion` JSON body. Reasoning models asked for reasoning
|
|
309
|
+
* (`reasoning_effort`, or `thinking.type: 'enabled'`) stream
|
|
310
|
+
* `delta.reasoning` with `delta.reasoning_details` before the text. A request
|
|
311
|
+
* with `tools` gets one tool call whose arguments are synthesized from the
|
|
312
|
+
* tool's JSON Schema and streamed in fragments, finishing with `tool_calls`.
|
|
313
|
+
* Unknown models get the recorded 404 envelope (`type: 'model_not_found'`,
|
|
314
|
+
* `param: { modelId }`, no `code`); a missing bearer credential gets a 401
|
|
315
|
+
* envelope; unknown routes get a 404 JSON error. The ledger records the
|
|
316
|
+
* `max_tokens` limit as `maxCompletionTokens`.
|
|
317
|
+
*
|
|
318
|
+
* Precedence per chat request: authentication and JSON validation, then the
|
|
319
|
+
* first matching fault if it is a `status` fault, then the next scripted
|
|
320
|
+
* turn, then model validation and defaults; a first matching chunk fault then
|
|
321
|
+
* shapes the body. Only the first matching fault (in insertion order) applies. Credential headers are never
|
|
322
|
+
* recorded, and the bearer value is never checked or stored.
|
|
323
|
+
*
|
|
324
|
+
* `POST /v1/evaluate` is fixture-only: a request with a non-empty bearer credential (never checked
|
|
325
|
+
* or stored), the recorded `accept` and `content-type`, no query, and a body matching one of the
|
|
326
|
+
* four classifier recordings (the discriminators `model` and `type` exact; any other string; the
|
|
327
|
+
* same keys, so the recorded question ids, option keys, and level count) gets the recorded
|
|
328
|
+
* response; anything else (another model, `providerOptions`, other questions) answers 400
|
|
329
|
+
* not-emulated. Its ledger, faults (statuses 400-599), scripted errors, and coverage are
|
|
330
|
+
* `emulator.evaluate` (control plane `/_emulate/evaluate/*`); `emulator.faults` and the other
|
|
331
|
+
* top-level APIs stay the chat route's. `reset()` and `POST /_emulate/reset` reset both.
|
|
332
|
+
*/
|
|
333
|
+
const makeGatewayEmulator = (options = {}) => {
|
|
334
|
+
const chat = makeChatCompletionsEmulator({
|
|
335
|
+
path: gatewayChatCompletionsPath,
|
|
336
|
+
routes: gatewayEmulatorRoutes,
|
|
337
|
+
knownModels: options.knownModels ?? gatewayEmulatorDefaultModels,
|
|
338
|
+
reasoningModels: options.reasoningModels ?? gatewayEmulatorDefaultReasoningModels,
|
|
339
|
+
errorEnvelope: gatewayErrorEnvelope,
|
|
340
|
+
unknownModel: {
|
|
341
|
+
status: 404,
|
|
342
|
+
body: (model) => ({ error: {
|
|
343
|
+
message: `Model '${model ?? ""}' not found`,
|
|
344
|
+
type: "model_not_found",
|
|
345
|
+
param: { modelId: model ?? null }
|
|
346
|
+
} })
|
|
347
|
+
},
|
|
348
|
+
auth: { unauthorized: {
|
|
349
|
+
message: "Synthetic: missing or invalid authorization.",
|
|
350
|
+
type: "authentication_error",
|
|
351
|
+
code: "unauthorized"
|
|
352
|
+
} },
|
|
353
|
+
completionTokenField: "max_tokens",
|
|
354
|
+
responseIdPrefix: "gen-synthetic",
|
|
355
|
+
defaultText: [
|
|
356
|
+
"Hello",
|
|
357
|
+
" from the",
|
|
358
|
+
" synthetic gateway."
|
|
359
|
+
],
|
|
360
|
+
turnSchema: GatewayScriptedTurn,
|
|
361
|
+
inputInvalid: (input, reason) => new GatewayEmulatorInputInvalid({
|
|
362
|
+
input,
|
|
363
|
+
reason
|
|
364
|
+
}),
|
|
365
|
+
wire: gatewayWireProfile(validEventsPerChunk(options.eventsPerChunk))
|
|
366
|
+
});
|
|
367
|
+
const evaluate = makeFixtureRouteEmulator({
|
|
368
|
+
method: "POST",
|
|
369
|
+
path: gatewayEvaluatePath,
|
|
370
|
+
routes: gatewayEvaluateEmulatorRoutes,
|
|
371
|
+
recordings: gatewayEvaluateRecordings,
|
|
372
|
+
credential: "bearer",
|
|
373
|
+
headers: [],
|
|
374
|
+
inputInvalid: (input, reason) => new GatewayEmulatorInputInvalid({
|
|
375
|
+
input,
|
|
376
|
+
reason
|
|
377
|
+
})
|
|
378
|
+
});
|
|
379
|
+
return {
|
|
380
|
+
...chat,
|
|
381
|
+
fetch: composeFetch({
|
|
382
|
+
routes: [{
|
|
383
|
+
name: "evaluate",
|
|
384
|
+
paths: [gatewayEvaluatePath],
|
|
385
|
+
part: evaluate
|
|
386
|
+
}],
|
|
387
|
+
fallback: chat,
|
|
388
|
+
control: (request) => chat.fetch(request)
|
|
389
|
+
}),
|
|
390
|
+
reset: () => {
|
|
391
|
+
chat.reset();
|
|
392
|
+
evaluate.reset();
|
|
393
|
+
},
|
|
394
|
+
evaluate
|
|
395
|
+
};
|
|
396
|
+
};
|
|
397
|
+
//#endregion
|
|
398
|
+
export { GatewayEmulatorInputInvalid, GatewayEvaluateFault, GatewayFault, GatewayFaultMatch, GatewayScriptedCompletion, GatewayScriptedError, GatewayScriptedToolCall, GatewayScriptedTurn, GatewayScriptedUsage, emulatorEvidenceHeader, gatewayChatCompletionsPath, gatewayEmulatorDefaultEventsPerChunk, gatewayEmulatorDefaultModels, gatewayEmulatorDefaultReasoningModels, gatewayEmulatorRoutes, gatewayEvaluateEmulatorRoutes, gatewayEvaluatePath, makeGatewayEmulator };
|
|
399
|
+
|
|
400
|
+
//# sourceMappingURL=gateway.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.mjs","names":[],"sources":["../src/gateway.ts"],"sourcesContent":["/**\n * Vercel AI Gateway emulator: a plain fetch handler for the OpenAI-compatible\n * `POST /v1/chat/completions` endpoint, with scripted turns, wire faults, a\n * request ledger, and an `/_emulate/*` control plane.\n *\n * It never imports SDK code: its wire shapes follow the verified live Gateway\n * conformance recordings (2026-09-30), copied as data: `chat.completion.chunk`\n * SSE with several events per network chunk, `delta.reasoning` plus\n * `reasoning_details` for DeepSeek reasoning, a finish event carrying\n * `provider_metadata`, `usage`, `system_fingerprint`, `service_tier`, and\n * `generationId`, then `data: [DONE]`, and the 404 `model_not_found`\n * envelope `{ error: { message, type, param: { modelId } } }` for unknown\n * models. Ids, costs, and routing metadata are synthetic stand-ins. The route\n * is linked to those conformance case ids in `gatewayEmulatorRoutes`\n * (`verified`). The Chat Completions machinery is shared with the OpenAI\n * emulator (`chat-completions.ts`); this module supplies the Gateway's paths,\n * models, envelope, reasoning, and wire profile.\n *\n * Not covered by a recording (synthetic): the 401 error, default fault\n * bodies, the non-streamed `chat.completion` body, and the answer to a request\n * without a `model` (404 `Model '' not found` with `param.modelId: null`).\n *\n * The same fetch handler also answers the classifier route `POST /v1/evaluate`\n * (AI Gateway calls classification \"evaluation\"; `emulator.evaluate`) with its\n * own manifest (`gatewayEvaluateEmulatorRoutes`, unverified), ledger, faults,\n * and turns. It is fixture-only: a request matching one of the four synthetic\n * classifier conformance recordings (boolean, choice, score, unknown-model\n * error envelope; `gateway-evaluate-recordings.ts`, copied as data) within the\n * shared request-shape latitude gets that recording's response; anything else\n * answers 400 not-emulated.\n *\n * Runtime-portable Web APIs only (`Request`, `Response`, `ReadableStream`,\n * `TextEncoder`, `URL`); no Effect runtime is required to use it.\n *\n * @experimental\n */\nimport { Data } from 'effect'\nimport type * as Schema from 'effect/Schema'\nimport {\n ChatFault,\n ChatFaultMatch,\n ChatScriptedReasoningCompletion,\n ChatScriptedError,\n ChatScriptedToolCall,\n ChatScriptedReasoningTurn,\n ChatScriptedUsage,\n makeChatCompletionsEmulator,\n type ChatCompletionsEmulator,\n type ChatCoverage,\n type ChatFaultKind,\n type ChatFaultState,\n type ChatLedgerEntry,\n type ChatResponseIdentity,\n type ChatRouteCoverage,\n type ChatUsageCounts,\n type ChatWireError,\n type ChatWireProfile\n} from './chat-completions.ts'\nimport { composeFetch } from './emulator-compose.ts'\nimport {\n FixtureRouteFault,\n makeFixtureRouteEmulator,\n type FixtureRouteEmulator,\n type FixtureRouteFaultKind,\n type FixtureRouteLedgerEntry,\n type FixtureRouteScriptedTurn\n} from './fixture-route.ts'\nimport { gatewayEvaluateRecordings } from './gateway-evaluate-recordings.ts'\nimport type { EmulatorRouteEvidence } from './route-evidence.ts'\n\nexport type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'\n\nexport { emulatorEvidenceHeader } from './route-evidence.ts'\n\nexport const gatewayChatCompletionsPath = '/v1/chat/completions'\n\n/** The AI Gateway classifier (\"evaluation\") path. */\nexport const gatewayEvaluatePath = '/v1/evaluate'\n\n/** Synthetic-safe default model ids, including the Gateway conformance defaults. */\nexport const gatewayEmulatorDefaultModels: ReadonlyArray<string> = [\n 'openai/gpt-4.1-nano',\n 'openai/gpt-4.1-mini',\n 'deepseek/deepseek-v3.2',\n 'deepseek/deepseek-v4.1-flash'\n]\n\n/** Models that stream reasoning deltas when reasoning is requested. */\nexport const gatewayEmulatorDefaultReasoningModels: ReadonlyArray<string> = [\n 'deepseek/deepseek-v3.2',\n 'deepseek/deepseek-v4.1-flash'\n]\n\n/**\n * SSE events per network chunk by default. The live Gateway packs several\n * events into one network chunk (the recordings carry one to four, always\n * with the finish event and `data: [DONE]` together in the last chunk).\n */\nexport const gatewayEmulatorDefaultEventsPerChunk = 2\n\n/**\n * Route evidence manifest: every emulated Gateway route and the conformance\n * cases whose verified live recordings (2026-09-30) its wire shapes follow.\n */\nexport const gatewayEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> = [\n {\n method: 'POST',\n path: gatewayChatCompletionsPath,\n kind: 'provider',\n write: false,\n caseIds: [\n 'vercel-ai-gateway.stream.plain-text',\n 'vercel-ai-gateway.stream.deepseek-reasoning',\n 'vercel-ai-gateway.stream.tool-call-deltas',\n 'vercel-ai-gateway.stream.error-envelope'\n ],\n evidence: 'verified',\n observedAt: '2026-09-30'\n }\n]\n\n/**\n * Route evidence manifest of the classifier route (served by the same fetch handler, with its own\n * ledger, faults, and coverage). Its recordings are synthetic placeholders, so it is unverified.\n */\nexport const gatewayEvaluateEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> = [\n {\n method: 'POST',\n path: gatewayEvaluatePath,\n kind: 'provider',\n write: false,\n caseIds: [\n 'vercel-ai-gateway.classify.boolean',\n 'vercel-ai-gateway.classify.choice',\n 'vercel-ai-gateway.classify.score',\n 'vercel-ai-gateway.classify.error-envelope'\n ],\n evidence: 'unverified',\n observedAt: undefined\n }\n]\n\n/** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */\nexport const GatewayFaultMatch = ChatFaultMatch\n\nexport type GatewayFaultMatch = ChatFaultMatch\n\n/**\n * Wire faults for emulated routes:\n *\n * - `status`: answer with this status, headers, and body instead of a\n * completion (for example 429 with `retry-after`). The body defaults to a\n * Gateway error envelope. Statuses that cannot carry a body (1xx, 204, 205,\n * 304) and redirects (3xx) are rejected, as are invalid header names or\n * values and a `location` header; scripted errors follow the same rules.\n * - `error-after-chunks`: send `chunks` body chunks, then error the body\n * stream (a dropped connection).\n * - `truncate-after-chunks`: send `chunks` body chunks, then close the body\n * cleanly (for example without `data: [DONE]`).\n *\n * Chunk faults count network chunks: a streamed chunk carries up to\n * `eventsPerChunk` SSE events (default 2), and a whole JSON body counts as one\n * chunk. A chunk fault that cannot take effect (`error-after-chunks` beyond\n * the chunk count, or `truncate-after-chunks` at or beyond it) answers 500\n * with an emulator error instead of silently doing nothing, and is not\n * consumed.\n */\nexport const GatewayFault = ChatFault\n\nexport type GatewayFault = ChatFault\n\nexport type GatewayFaultKind = ChatFaultKind\n\n/**\n * Usage for a scripted turn, sent as the Gateway `usage` object (token counts,\n * `completion_tokens_details.reasoning_tokens` defaulting to 0, and synthetic\n * zero costs).\n */\nexport const GatewayScriptedUsage = ChatScriptedUsage\n\nexport type GatewayScriptedUsage = ChatScriptedUsage\n\n/** One scripted tool call; its JSON arguments stream as these fragments. */\nexport const GatewayScriptedToolCall = ChatScriptedToolCall\n\nexport type GatewayScriptedToolCall = ChatScriptedToolCall\n\n/**\n * A scripted completion. Every field is exact (nothing is filled in) except:\n * `usage` omitted is synthesized when the request asks for usage, and `null`\n * drops it; `finishReason` omitted is `tool_calls` with tool calls, else\n * `stop`. `order` defaults to `reasoning-first`; `reasoningField` defaults to\n * `reasoning` (streamed with `reasoning_details`, as recorded live); the\n * DeepSeek-native `reasoning_content` is sent alone.\n */\nexport const GatewayScriptedCompletion = ChatScriptedReasoningCompletion\n\nexport type GatewayScriptedCompletion = ChatScriptedReasoningCompletion\n\n/** A scripted error response: status, body (a string is sent as is), and optional headers. */\nexport const GatewayScriptedError = ChatScriptedError\n\nexport type GatewayScriptedError = ChatScriptedError\n\n/** A turn queued for the next chat completion request. */\nexport const GatewayScriptedTurn = ChatScriptedReasoningTurn\n\nexport type GatewayScriptedTurn = ChatScriptedReasoningTurn\n\n/**\n * Thrown by the JS API (`faults.add`, `script.enqueue`) for invalid input, and\n * by `makeGatewayEmulator` for invalid options; a programmer error.\n */\nexport class GatewayEmulatorInputInvalid extends Data.TaggedError('GatewayEmulatorInputInvalid')<{\n readonly input: 'fault' | 'turn' | 'options'\n readonly reason: string\n}> {\n override get message(): string {\n return `Invalid Gateway emulator ${this.input}: ${this.reason}`\n }\n}\n\nexport type GatewayLedgerEntry = ChatLedgerEntry\n\nexport type GatewayFaultState = ChatFaultState\n\nexport type GatewayRouteCoverage = ChatRouteCoverage\n\nexport type GatewayCoverage = ChatCoverage\n\nexport type GatewayEmulatorOptions = {\n /** Model ids that exist. Defaults to `gatewayEmulatorDefaultModels`. */\n readonly knownModels?: ReadonlyArray<string>\n /** Model ids that stream reasoning. Defaults to `gatewayEmulatorDefaultReasoningModels`. */\n readonly reasoningModels?: ReadonlyArray<string>\n /**\n * SSE events per network chunk of a streamed body, a positive integer.\n * Defaults to `gatewayEmulatorDefaultEventsPerChunk` (2); 1 sends one event\n * per chunk. Events are packed counting from the end, so the last chunk\n * holds the finish event and `data: [DONE]` together. Chunk faults count\n * these network chunks.\n */\n readonly eventsPerChunk?: number\n}\n\n/** A classifier-route fault (`status` 400-599, `error-after-chunks`, `truncate-after-chunks`). */\nexport const GatewayEvaluateFault = FixtureRouteFault\n\nexport type GatewayEvaluateFault = FixtureRouteFault\n\nexport type GatewayEvaluateFaultKind = FixtureRouteFaultKind\n\n/** A classifier-route turn: a scripted error (status 400-599) only. */\nexport type GatewayEvaluateScriptedTurn = FixtureRouteScriptedTurn\n\nexport type GatewayEvaluateLedgerEntry = FixtureRouteLedgerEntry\n\nexport type GatewayEvaluateEmulator = FixtureRouteEmulator\n\n/** The chat emulator, plus `evaluate`: the classifier route's own emulator API. */\nexport type GatewayEmulator = ChatCompletionsEmulator<GatewayScriptedTurn> & {\n readonly evaluate: GatewayEvaluateEmulator\n}\n\nconst gatewayErrorEnvelope = (error: ChatWireError): Schema.Json => ({\n error: { message: error.message, type: error.type, code: error.code }\n})\n\n// Synthetic stand-ins for the metadata the live Gateway attaches to its chunks. Keys and value\n// types follow the recordings; every value is synthetic (no recorded id, fingerprint, or cost).\nconst syntheticFingerprint = 'fp_synthetic'\n\nconst syntheticAttemptTime = 1790000000000\n\nconst syntheticCost = '0'\n\nconst vendorOf = (model: string): string | undefined => {\n const [vendor] = model.split('/')\n\n return vendor === undefined || vendor.length === 0 || vendor === model ? undefined : vendor\n}\n\n// `service_tier` was recorded only for OpenAI models.\nconst isOpenAiModel = (model: string): boolean => vendorOf(model) === 'openai'\n\nconst syntheticResponseId = (identity: ChatResponseIdentity): string =>\n `resp_synthetic_${identity.id}`\n\ntype GatewayUpstream = { readonly provider: string; readonly entry?: Schema.JsonObject }\n\n/**\n * The upstream provider the Gateway routes a model to, and its `provider_metadata` entry (keyed by\n * that provider), per model family as recorded: `openai/*` routes to `openai` with\n * `{ responseId, serviceTier }`; `deepseek/*` routes to `baseten` with\n * `{ acceptedPredictionTokens, rejectedPredictionTokens }` (synthetic zero counts). Other families\n * were not recorded: they route to their vendor prefix without an upstream entry.\n */\nconst upstreamOf = (identity: ChatResponseIdentity): GatewayUpstream => {\n const vendor = vendorOf(identity.model)\n\n if (vendor === 'openai') {\n return {\n provider: 'openai',\n entry: { responseId: syntheticResponseId(identity), serviceTier: 'default' }\n }\n }\n\n if (vendor === 'deepseek') {\n return {\n provider: 'baseten',\n entry: { acceptedPredictionTokens: 0, rejectedPredictionTokens: 0 }\n }\n }\n\n return { provider: vendor ?? 'synthetic' }\n}\n\n/** The `gateway` entry of `provider_metadata`: routing, costs, and the generation id. */\nconst gatewayRoutingMetadata = (identity: ChatResponseIdentity): Schema.JsonObject => {\n const { provider } = upstreamOf(identity)\n const responseId = syntheticResponseId(identity)\n\n return {\n routing: {\n originalModelId: identity.model,\n resolvedProvider: provider,\n fallbacksAvailable: ['synthetic-fallback'],\n planningReasoning: 'Synthetic: routing planned by the emulator.',\n canonicalSlug: identity.model,\n finalProvider: provider,\n modelAttemptCount: 1,\n modelAttempts: [\n {\n canonicalSlug: identity.model,\n success: true,\n providerAttemptCount: 1,\n providerAttempts: [\n {\n provider,\n credentialType: 'system',\n success: true,\n startTime: syntheticAttemptTime,\n endTime: syntheticAttemptTime + 1,\n providerRequestId: `req_synthetic_${identity.id}`,\n statusCode: 200,\n providerResponseId: responseId\n }\n ]\n }\n ],\n totalProviderAttemptCount: 1,\n affinity: { outcome: 'skipped_below_min_prefix' },\n clientSessionId: 'synthetic-client-session',\n clientSessionIdSource: 'fingerprint'\n },\n cost: syntheticCost,\n marketCost: syntheticCost,\n surchargeCost: syntheticCost,\n gatewayCost: syntheticCost,\n inferenceCost: syntheticCost,\n inputInferenceCost: syntheticCost,\n outputInferenceCost: syntheticCost,\n generationId: identity.id\n }\n}\n\n/** `provider_metadata`: the upstream provider's entry first (when its family has one), then `gateway`. */\nconst gatewayProviderMetadata = (identity: ChatResponseIdentity): Schema.JsonObject => {\n const metadata: Record<string, Schema.Json> = {}\n const upstream = upstreamOf(identity)\n\n if (upstream.entry !== undefined) {\n metadata[upstream.provider] = upstream.entry\n }\n\n metadata.gateway = gatewayRoutingMetadata(identity)\n\n return metadata\n}\n\nconst gatewayUsage = (counts: ChatUsageCounts): Schema.JsonObject => ({\n prompt_tokens: counts.promptTokens,\n completion_tokens: counts.completionTokens,\n total_tokens: counts.promptTokens + counts.completionTokens,\n cost: 0,\n is_byok: false,\n prompt_tokens_details: { cached_tokens: 0, audio_tokens: 0, video_tokens: 0 },\n cost_details: {\n upstream_inference_cost: null,\n upstream_inference_prompt_cost: 0,\n upstream_inference_completions_cost: 0\n },\n completion_tokens_details: { reasoning_tokens: counts.reasoningTokens ?? 0, image_tokens: 0 },\n cache_creation_input_tokens: 0,\n market_cost: 0,\n gateway_cost: 0\n})\n\nconst gatewayWireProfile = (eventsPerChunk: number): ChatWireProfile => ({\n eventsPerChunk,\n streamUsage: 'finish-event',\n openingDelta: { role: 'assistant' },\n choiceFields: { logprobs: null },\n reasoningField: 'reasoning',\n reasoningDetails: true,\n chunkFields: () => ({ system_fingerprint: syntheticFingerprint }),\n finishFields: identity => {\n const chunk: Record<string, Schema.Json> = {}\n\n if (isOpenAiModel(identity.model)) {\n chunk.service_tier = 'default'\n }\n\n chunk.generationId = identity.id\n\n return { delta: { provider_metadata: gatewayProviderMetadata(identity) }, chunk }\n },\n usage: gatewayUsage\n})\n\nconst validEventsPerChunk = (eventsPerChunk: number | undefined): number => {\n const value = eventsPerChunk ?? gatewayEmulatorDefaultEventsPerChunk\n\n if (!Number.isSafeInteger(value) || value < 1) {\n throw new GatewayEmulatorInputInvalid({\n input: 'options',\n reason: `eventsPerChunk must be a positive integer, got ${String(eventsPerChunk)}`\n })\n }\n\n return value\n}\n\n/**\n * Create a Vercel AI Gateway emulator. Each call has independent ledger,\n * fault, and script state. Throws `GatewayEmulatorInputInvalid` for an\n * invalid `eventsPerChunk`.\n *\n * Without a script, `POST /v1/chat/completions` answers a known model with\n * synthetic text deltas. `stream: true` sends `chat.completion.chunk` SSE in\n * the recorded Gateway shape: a `{ role: 'assistant' }` opening delta, content\n * deltas, and a finish event whose delta carries `provider_metadata` and which\n * carries `usage` (when `stream_options.include_usage` is set),\n * `system_fingerprint`, `service_tier` (for `openai/*` models), and\n * `generationId`, then `data: [DONE]`; every chunk carries\n * `system_fingerprint` and `logprobs: null`, and events are packed\n * `eventsPerChunk` per network chunk. `stream: false` answers one\n * `chat.completion` JSON body. Reasoning models asked for reasoning\n * (`reasoning_effort`, or `thinking.type: 'enabled'`) stream\n * `delta.reasoning` with `delta.reasoning_details` before the text. A request\n * with `tools` gets one tool call whose arguments are synthesized from the\n * tool's JSON Schema and streamed in fragments, finishing with `tool_calls`.\n * Unknown models get the recorded 404 envelope (`type: 'model_not_found'`,\n * `param: { modelId }`, no `code`); a missing bearer credential gets a 401\n * envelope; unknown routes get a 404 JSON error. The ledger records the\n * `max_tokens` limit as `maxCompletionTokens`.\n *\n * Precedence per chat request: authentication and JSON validation, then the\n * first matching fault if it is a `status` fault, then the next scripted\n * turn, then model validation and defaults; a first matching chunk fault then\n * shapes the body. Only the first matching fault (in insertion order) applies. Credential headers are never\n * recorded, and the bearer value is never checked or stored.\n *\n * `POST /v1/evaluate` is fixture-only: a request with a non-empty bearer credential (never checked\n * or stored), the recorded `accept` and `content-type`, no query, and a body matching one of the\n * four classifier recordings (the discriminators `model` and `type` exact; any other string; the\n * same keys, so the recorded question ids, option keys, and level count) gets the recorded\n * response; anything else (another model, `providerOptions`, other questions) answers 400\n * not-emulated. Its ledger, faults (statuses 400-599), scripted errors, and coverage are\n * `emulator.evaluate` (control plane `/_emulate/evaluate/*`); `emulator.faults` and the other\n * top-level APIs stay the chat route's. `reset()` and `POST /_emulate/reset` reset both.\n */\nexport const makeGatewayEmulator = (options: GatewayEmulatorOptions = {}): GatewayEmulator => {\n const chat = makeChatCompletionsEmulator({\n path: gatewayChatCompletionsPath,\n routes: gatewayEmulatorRoutes,\n knownModels: options.knownModels ?? gatewayEmulatorDefaultModels,\n reasoningModels: options.reasoningModels ?? gatewayEmulatorDefaultReasoningModels,\n errorEnvelope: gatewayErrorEnvelope,\n // Copied data shape of the verified Gateway error-envelope recording (unknown model id): its\n // own envelope, with `param.modelId` and no `code`.\n unknownModel: {\n status: 404,\n body: model => ({\n error: {\n message: `Model '${model ?? ''}' not found`,\n type: 'model_not_found',\n param: { modelId: model ?? null }\n }\n })\n },\n auth: {\n unauthorized: {\n message: 'Synthetic: missing or invalid authorization.',\n type: 'authentication_error',\n code: 'unauthorized'\n }\n },\n completionTokenField: 'max_tokens',\n responseIdPrefix: 'gen-synthetic',\n defaultText: ['Hello', ' from the', ' synthetic gateway.'],\n turnSchema: GatewayScriptedTurn,\n inputInvalid: (input, reason) => new GatewayEmulatorInputInvalid({ input, reason }),\n wire: gatewayWireProfile(validEventsPerChunk(options.eventsPerChunk))\n })\n\n const evaluate = makeFixtureRouteEmulator({\n method: 'POST',\n path: gatewayEvaluatePath,\n routes: gatewayEvaluateEmulatorRoutes,\n recordings: gatewayEvaluateRecordings,\n credential: 'bearer',\n headers: [],\n inputInvalid: (input, reason) => new GatewayEmulatorInputInvalid({ input, reason })\n })\n\n return {\n ...chat,\n fetch: composeFetch({\n routes: [{ name: 'evaluate', paths: [gatewayEvaluatePath], part: evaluate }],\n fallback: chat,\n control: request => chat.fetch(request)\n }),\n reset: () => {\n chat.reset()\n evaluate.reset()\n },\n evaluate\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { StatefulRoute } from "../stateful-emulator.mjs";
|
|
2
|
+
import { GithubEmulatorState } from "./state.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/github/api.d.ts
|
|
5
|
+
/** The origin every GitHub fixture records; another origin is not emulated. */
|
|
6
|
+
declare const githubEmulatorOrigin = "https://api.github.com";
|
|
7
|
+
/** The only `X-GitHub-Api-Version` the emulator answers (every fixture sends it). */
|
|
8
|
+
declare const githubEmulatorApiVersion = "2026-03-10";
|
|
9
|
+
/** The only `Accept` the emulator answers (every fixture sends it). */
|
|
10
|
+
declare const githubEmulatorAccept = "application/vnd.github+json";
|
|
11
|
+
/** Drill knobs (tests only): each makes the emulator disagree with one conformance claim. */
|
|
12
|
+
type GithubEmulatorDrills = {
|
|
13
|
+
/** The first label page's `Link` lists no `rel="next"` while labels remain. */readonly linkOmitsNext?: boolean; /** The not-found body of an unreached issue number lacks `documentation_url`. */
|
|
14
|
+
readonly notFoundOmitsDocumentationUrl?: boolean; /** The overlong search's validation body carries an empty `errors` array. */
|
|
15
|
+
readonly validationWithoutErrors?: boolean; /** File contents answer base64 on one line (not folded). */
|
|
16
|
+
readonly contentUnfolded?: boolean; /** A comment listing `since` excludes a comment updated at exactly that instant. */
|
|
17
|
+
readonly sinceExcludesEqual?: boolean; /** The label add answers the issue labels without the added label. */
|
|
18
|
+
readonly addAnswerOmitsLabel?: boolean; /** Closing an issue answers `closed_at: null`. */
|
|
19
|
+
readonly closeWithoutClosedAt?: boolean;
|
|
20
|
+
};
|
|
21
|
+
declare const githubEmulatorDrillKnobs: ReadonlyArray<keyof GithubEmulatorDrills>;
|
|
22
|
+
type GithubApiEnv = {
|
|
23
|
+
/** Clock in epoch milliseconds (created issue and comment timestamps, close times). */readonly now: () => number;
|
|
24
|
+
readonly drills: Readonly<Record<keyof GithubEmulatorDrills, boolean>>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The fixtures' error bodies, byte for byte (`JSON.stringify` keeps this key order): the issue
|
|
28
|
+
* and comment 404s, the label-not-on-issue 404, and the overlong search's 422.
|
|
29
|
+
*/
|
|
30
|
+
declare const githubEmulatorErrorBodies: {
|
|
31
|
+
readonly issueNotFound: {
|
|
32
|
+
readonly message: "Not Found";
|
|
33
|
+
readonly documentation_url: "https://docs.github.com/rest/issues/issues#get-an-issue";
|
|
34
|
+
readonly status: "404";
|
|
35
|
+
};
|
|
36
|
+
readonly commentNotFound: {
|
|
37
|
+
readonly message: "Not Found";
|
|
38
|
+
readonly documentation_url: "https://docs.github.com/rest/issues/comments#delete-an-issue-comment";
|
|
39
|
+
readonly status: "404";
|
|
40
|
+
};
|
|
41
|
+
readonly labelNotOnIssue: {
|
|
42
|
+
readonly message: "Label does not exist";
|
|
43
|
+
readonly documentation_url: "https://docs.github.com/rest/issues/labels#remove-a-label-from-an-issue";
|
|
44
|
+
readonly status: "404";
|
|
45
|
+
};
|
|
46
|
+
readonly searchTooLong: {
|
|
47
|
+
readonly message: "Validation Failed";
|
|
48
|
+
readonly errors: readonly [{
|
|
49
|
+
readonly message: "The search is longer than 256 characters.";
|
|
50
|
+
readonly resource: "Search";
|
|
51
|
+
readonly field: "q";
|
|
52
|
+
readonly code: "invalid";
|
|
53
|
+
}];
|
|
54
|
+
readonly documentation_url: "https://docs.github.com/v3/search/";
|
|
55
|
+
readonly status: "422";
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
type Route = StatefulRoute<GithubEmulatorState, GithubApiEnv>;
|
|
59
|
+
/** The route table: evidence plus handlers. `githubEmulatorRoutes` is its evidence part. */
|
|
60
|
+
declare const githubApiRoutes: ReadonlyArray<Route>;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { GithubApiEnv, GithubEmulatorDrills, githubApiRoutes, githubEmulatorAccept, githubEmulatorApiVersion, githubEmulatorDrillKnobs, githubEmulatorErrorBodies, githubEmulatorOrigin };
|
|
63
|
+
//# sourceMappingURL=api.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.mts","names":[],"sources":["../../src/github/api.ts"],"mappings":";;;;;cAgDa,oBAAA;AAAb;AAAA,cAGa,wBAAA;;cAGA,oBAAA;AANoB;AAAA,KAerB,oBAAA;EAZyB,wFAc1B,aAAA,YAd0B;EAAA,SAgB1B,6BAAA,YAbE;EAAA,SAeF,uBAAA;WAEA,eAAA,YAjBsB;EAAA,SAmBtB,kBAAA,YAVqB;EAAA,SAYrB,mBAAA,YAZqB;EAAA,SAcrB,oBAAA;AAAA;AAAA,cAGE,wBAAA,EAA0B,aAAa,OAAO,oBAAA;AAAA,KAU/C,YAAA;EAjBD,gGAmBA,GAAA;EAAA,SACA,MAAA,EAAQ,QAAA,CAAS,MAAA,OAAa,oBAAA;AAAA;AAhBV;AAG/B;;;AAH+B,cAuBlB,yBAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;KA+BR,KAAA,GAAQ,aAAA,CAAc,mBAAA,EAAqB,YAAA;;cA00BnC,eAAA,EAAiB,aAAa,CAAC,KAAA"}
|