@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
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { EmulatorEvidence, EmulatorRouteEvidence } from "./route-evidence.mjs";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
|
|
4
|
+
//#region src/emulator-kernel.d.ts
|
|
5
|
+
declare const ChunkCount: Schema.Int;
|
|
6
|
+
/** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
|
|
7
|
+
declare const EmulatorFaultMatch: Schema.Struct<{
|
|
8
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
9
|
+
readonly model: Schema.optionalKey<Schema.String>;
|
|
10
|
+
}>;
|
|
11
|
+
type EmulatorFaultMatch = typeof EmulatorFaultMatch.Type;
|
|
12
|
+
/** Fields every fault kind carries. */
|
|
13
|
+
declare const emulatorFaultFields: {
|
|
14
|
+
match: Schema.optionalKey<Schema.Struct<{
|
|
15
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
16
|
+
readonly model: Schema.optionalKey<Schema.String>;
|
|
17
|
+
}>>; /** How many matching requests the fault answers. Omitted: every matching request. */
|
|
18
|
+
count: Schema.optionalKey<Schema.Int>;
|
|
19
|
+
};
|
|
20
|
+
/** Answer with this status, headers, and body instead of the planned response. */
|
|
21
|
+
declare const EmulatorStatusFault: Schema.Struct<{
|
|
22
|
+
readonly match: Schema.optionalKey<Schema.Struct<{
|
|
23
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
24
|
+
readonly model: Schema.optionalKey<Schema.String>;
|
|
25
|
+
}>>;
|
|
26
|
+
readonly count: Schema.optionalKey<Schema.Int>;
|
|
27
|
+
readonly kind: Schema.Literal<"status">;
|
|
28
|
+
readonly status: Schema.Int;
|
|
29
|
+
readonly headers: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
30
|
+
readonly body: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
31
|
+
}>;
|
|
32
|
+
/** Send `chunks` body chunks, then error the body stream (a dropped connection). */
|
|
33
|
+
declare const EmulatorErrorAfterChunksFault: Schema.Struct<{
|
|
34
|
+
readonly match: Schema.optionalKey<Schema.Struct<{
|
|
35
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
36
|
+
readonly model: Schema.optionalKey<Schema.String>;
|
|
37
|
+
}>>;
|
|
38
|
+
readonly count: Schema.optionalKey<Schema.Int>;
|
|
39
|
+
readonly kind: Schema.Literal<"error-after-chunks">;
|
|
40
|
+
readonly chunks: Schema.Int;
|
|
41
|
+
}>;
|
|
42
|
+
/** Send `chunks` body chunks, then close the body cleanly (a truncated response). */
|
|
43
|
+
declare const EmulatorTruncateAfterChunksFault: Schema.Struct<{
|
|
44
|
+
readonly match: Schema.optionalKey<Schema.Struct<{
|
|
45
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
46
|
+
readonly model: Schema.optionalKey<Schema.String>;
|
|
47
|
+
}>>;
|
|
48
|
+
readonly count: Schema.optionalKey<Schema.Int>;
|
|
49
|
+
readonly kind: Schema.Literal<"truncate-after-chunks">;
|
|
50
|
+
readonly chunks: Schema.Int;
|
|
51
|
+
}>;
|
|
52
|
+
/** A scripted error response: status, body (a string is sent as is), and optional headers. */
|
|
53
|
+
declare const EmulatorScriptedError: Schema.Struct<{
|
|
54
|
+
readonly error: Schema.Struct<{
|
|
55
|
+
readonly status: Schema.Int;
|
|
56
|
+
readonly body: Schema.Codec<Schema.Json, Schema.Json, never, never>;
|
|
57
|
+
readonly headers: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
58
|
+
}>;
|
|
59
|
+
}>;
|
|
60
|
+
type EmulatorScriptedError = typeof EmulatorScriptedError.Type;
|
|
61
|
+
/** What the kernel needs from every fault: its kind and optional filter and count. */
|
|
62
|
+
type KernelFault = {
|
|
63
|
+
readonly kind: string;
|
|
64
|
+
readonly match?: EmulatorFaultMatch | undefined;
|
|
65
|
+
readonly count?: number | undefined;
|
|
66
|
+
};
|
|
67
|
+
type EmulatorFaultState<Fault> = {
|
|
68
|
+
readonly id: number;
|
|
69
|
+
readonly fault: Fault; /** Remaining matching requests; `undefined` for an unlimited fault. */
|
|
70
|
+
readonly remaining: number | undefined;
|
|
71
|
+
readonly applied: number;
|
|
72
|
+
};
|
|
73
|
+
type MutableFaultState<Fault> = {
|
|
74
|
+
readonly id: number;
|
|
75
|
+
readonly fault: Fault;
|
|
76
|
+
remaining: number | undefined;
|
|
77
|
+
applied: number;
|
|
78
|
+
};
|
|
79
|
+
type EmulatorRouteCoverage = EmulatorRouteEvidence & {
|
|
80
|
+
/** Ledger requests answered by this route since the last ledger clear or reset. */readonly requests: number;
|
|
81
|
+
};
|
|
82
|
+
type EmulatorCoverage = {
|
|
83
|
+
readonly routes: ReadonlyArray<EmulatorRouteCoverage>; /** Ledger requests to unknown routes (failed closed). */
|
|
84
|
+
readonly unknownRouteRequests: number;
|
|
85
|
+
};
|
|
86
|
+
/** Fields an emulator lists first in `/_emulate/state`. */
|
|
87
|
+
type EmulatorStateFields = {
|
|
88
|
+
readonly knownModels: ReadonlyArray<string>; /** Only for emulators that emulate streamed reasoning. */
|
|
89
|
+
readonly reasoningModels?: ReadonlyArray<string>;
|
|
90
|
+
};
|
|
91
|
+
/** The ledger fields the kernel owns; each wire core extends it with its request fields. */
|
|
92
|
+
type KernelLedgerEntry<FaultKind extends string> = {
|
|
93
|
+
seq: number;
|
|
94
|
+
method: string;
|
|
95
|
+
path: string;
|
|
96
|
+
fault?: FaultKind;
|
|
97
|
+
faultError?: string;
|
|
98
|
+
responseError?: string;
|
|
99
|
+
evidence: EmulatorEvidence | 'unknown-route';
|
|
100
|
+
status: number;
|
|
101
|
+
bodyChunks: number;
|
|
102
|
+
};
|
|
103
|
+
/** The public shape of every emulator built on the kernel. */
|
|
104
|
+
type EmulatorApi<Turn, Fault, Entry> = {
|
|
105
|
+
/** The fetch handler (emulated API plus the `/_emulate/*` control plane). Never rejects. */readonly fetch: (request: Request) => Promise<Response>;
|
|
106
|
+
readonly ledger: {
|
|
107
|
+
readonly entries: () => ReadonlyArray<Entry>;
|
|
108
|
+
readonly clear: () => void;
|
|
109
|
+
}; /** Clear the ledger, faults, and scripted turns. */
|
|
110
|
+
readonly reset: () => void;
|
|
111
|
+
readonly faults: {
|
|
112
|
+
/** Add a fault; throws the emulator's input-invalid error for an invalid fault. */readonly add: (fault: Fault) => EmulatorFaultState<Fault>;
|
|
113
|
+
readonly list: () => ReadonlyArray<EmulatorFaultState<Fault>>;
|
|
114
|
+
readonly clear: () => void;
|
|
115
|
+
};
|
|
116
|
+
readonly script: {
|
|
117
|
+
/** Queue a turn for the next emulated request; throws for an invalid turn. */readonly enqueue: (turn: Turn) => void;
|
|
118
|
+
readonly pending: () => number;
|
|
119
|
+
readonly clear: () => void;
|
|
120
|
+
};
|
|
121
|
+
readonly coverage: () => EmulatorCoverage;
|
|
122
|
+
};
|
|
123
|
+
type EmulatorKernelConfig<Fault extends KernelFault, Turn, Entry extends KernelLedgerEntry<Fault['kind']>, Snapshot> = {
|
|
124
|
+
/** Route evidence manifest; `serve` needs exactly one handler per route. */readonly routes: ReadonlyArray<EmulatorRouteEvidence>; /** Fault schema (decoded strictly). */
|
|
125
|
+
readonly faultSchema: Schema.Decoder<Fault>; /** Scripted-turn schema (decoded strictly). */
|
|
126
|
+
readonly turnSchema: Schema.Decoder<Turn>; /** Adds the wire core's own ledger fields to a fresh kernel entry. */
|
|
127
|
+
readonly newEntry: (base: KernelLedgerEntry<Fault['kind']>) => Entry; /** Copies an entry for the ledger API (never shares mutable state). */
|
|
128
|
+
readonly snapshotEntry: (entry: Entry) => Snapshot;
|
|
129
|
+
/**
|
|
130
|
+
* Response for an unknown emulated route (fails closed, still written to the ledger with the
|
|
131
|
+
* status it answers); the core may add its own ledger fields to `entry`.
|
|
132
|
+
*/
|
|
133
|
+
readonly unknownRoute: (entry: Entry) => Response; /** Fields listed first in `/_emulate/state`. */
|
|
134
|
+
readonly stateFields: () => EmulatorStateFields; /** Builds the error thrown by the JS API (`faults.add`, `script.enqueue`) for invalid input. */
|
|
135
|
+
readonly inputInvalid: (input: 'fault' | 'turn', reason: string) => Error;
|
|
136
|
+
};
|
|
137
|
+
type EmulatorRouteHandler<Entry> = (request: Request, entry: Entry, path: string) => Promise<Response>;
|
|
138
|
+
declare const isJsonObject: (value: Schema.Json | undefined) => value is Schema.JsonObject;
|
|
139
|
+
declare const jsonField: (value: Schema.Json | undefined, key: string) => Schema.Json | undefined;
|
|
140
|
+
declare const stringField: (value: Schema.Json | undefined, key: string) => string | undefined;
|
|
141
|
+
/** A JSON response; `body` is any JSON-serializable value (ledger snapshots, fault states, envelopes). */
|
|
142
|
+
declare const jsonResponse: (status: number, body: unknown, headers?: HeadersInit) => Response;
|
|
143
|
+
/** An emulator (not emulated-service) error: control-plane problems and unbuildable responses. */
|
|
144
|
+
declare const controlError: (status: number, message: string, headers?: HeadersInit) => Response;
|
|
145
|
+
/** A scripted or fault body: a string is sent as is, anything else as JSON. */
|
|
146
|
+
declare const bodyText: (body: Schema.Json) => string;
|
|
147
|
+
declare const readText: (request: Request) => Promise<string | undefined>;
|
|
148
|
+
declare const parseJson: (text: string) => Schema.Json | undefined;
|
|
149
|
+
/**
|
|
150
|
+
* A synthetic value for a JSON Schema: required object properties only,
|
|
151
|
+
* non-empty strings (or the first `enum` / `const` value), the minimum for
|
|
152
|
+
* numbers, `true` for booleans, and `minItems` synthetic items for arrays.
|
|
153
|
+
*/
|
|
154
|
+
declare const synthesizeValue: (schema: Schema.Json | undefined, name: string, depth: number) => Schema.Json;
|
|
155
|
+
/** Split text into up to `parts` non-empty fragments (at least one). */
|
|
156
|
+
declare const splitFragments: (text: string, parts: number) => ReadonlyArray<string>;
|
|
157
|
+
/** A rough synthetic token count (about four characters per token, at least one). */
|
|
158
|
+
declare const approximateTokens: (text: string) => number;
|
|
159
|
+
/**
|
|
160
|
+
* Create the kernel of an emulator. Each call has independent ledger, fault,
|
|
161
|
+
* and script state. Handlers use the returned helpers, then `serve` binds
|
|
162
|
+
* them to the manifest (throwing `EmulatorRouteUnmapped` on a mismatch) and
|
|
163
|
+
* returns the emulator API.
|
|
164
|
+
*/
|
|
165
|
+
declare const makeEmulatorKernel: <Fault extends KernelFault, Turn, Entry extends KernelLedgerEntry<Fault["kind"]>, Snapshot>(config: EmulatorKernelConfig<Fault, Turn, Entry, Snapshot>) => {
|
|
166
|
+
takeFault: (path: string, model: string | undefined) => MutableFaultState<Fault> | undefined;
|
|
167
|
+
consumeFault: (state: MutableFaultState<Fault>, entry: Entry) => void;
|
|
168
|
+
nextTurn: () => Turn | undefined;
|
|
169
|
+
respondWithBody: (entry: Entry, status: number, headers: HeadersInit, chunks: ReadonlyArray<string>, fault: MutableFaultState<Fault> | undefined) => Response;
|
|
170
|
+
respondWithStatusFault: (entry: Entry, state: MutableFaultState<Fault>, fault: typeof EmulatorStatusFault.Type, defaultBody: (status: number) => Schema.Json) => Response;
|
|
171
|
+
respondWithScriptedError: (entry: Entry, error: EmulatorScriptedError["error"], fault: MutableFaultState<Fault> | undefined) => Response;
|
|
172
|
+
serve: (handlers: ReadonlyMap<string, EmulatorRouteHandler<Entry>>) => EmulatorApi<Turn, Fault, Snapshot>;
|
|
173
|
+
};
|
|
174
|
+
//#endregion
|
|
175
|
+
export { ChunkCount, EmulatorApi, EmulatorCoverage, EmulatorErrorAfterChunksFault, EmulatorFaultMatch, EmulatorFaultState, EmulatorKernelConfig, EmulatorRouteCoverage, EmulatorRouteHandler, EmulatorScriptedError, EmulatorStateFields, EmulatorStatusFault, EmulatorTruncateAfterChunksFault, KernelFault, KernelLedgerEntry, approximateTokens, bodyText, controlError, emulatorFaultFields, isJsonObject, jsonField, jsonResponse, makeEmulatorKernel, parseJson, readText, splitFragments, stringField, synthesizeValue };
|
|
176
|
+
//# sourceMappingURL=emulator-kernel.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emulator-kernel.d.mts","names":[],"sources":["../src/emulator-kernel.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { EmulatorHeaderRecord, EmulatorResponseStatus } from "./emulator-http.mjs";
|
|
2
|
+
import { bindRouteHandlers, emulatorEvidenceHeader } from "./route-evidence.mjs";
|
|
3
|
+
import { Predicate, Result } from "effect";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
//#region src/emulator-kernel.ts
|
|
6
|
+
/**
|
|
7
|
+
* Shared emulator kernel (internal; not a package export).
|
|
8
|
+
*
|
|
9
|
+
* The wire-independent half of every fetch-handler emulator: fault and scripted-turn state, the
|
|
10
|
+
* request ledger, pull-driven bodies with chunk faults, evidence tagging, route binding, and the
|
|
11
|
+
* `/_emulate/*` control plane. Each wire core (`chat-completions.ts`, `messages.ts`) supplies its
|
|
12
|
+
* route handlers, fault and turn schemas, ledger entry shape, and error bodies.
|
|
13
|
+
*
|
|
14
|
+
* Also holds small wire-independent helpers both cores use: JSON field access, synthetic values
|
|
15
|
+
* for a tool's JSON Schema, fragment splitting, and a rough token estimate.
|
|
16
|
+
*
|
|
17
|
+
* Runtime-portable Web APIs only (`Request`, `Response`, `ReadableStream`, `TextEncoder`, `URL`);
|
|
18
|
+
* no Node builtins and no SDK imports.
|
|
19
|
+
*/
|
|
20
|
+
const ChunkCount = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
21
|
+
const FaultCount = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
|
|
22
|
+
/** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
|
|
23
|
+
const EmulatorFaultMatch = Schema.Struct({
|
|
24
|
+
path: Schema.optionalKey(Schema.String),
|
|
25
|
+
model: Schema.optionalKey(Schema.String)
|
|
26
|
+
});
|
|
27
|
+
/** Fields every fault kind carries. */
|
|
28
|
+
const emulatorFaultFields = {
|
|
29
|
+
match: Schema.optionalKey(EmulatorFaultMatch),
|
|
30
|
+
/** How many matching requests the fault answers. Omitted: every matching request. */
|
|
31
|
+
count: Schema.optionalKey(FaultCount)
|
|
32
|
+
};
|
|
33
|
+
/** Answer with this status, headers, and body instead of the planned response. */
|
|
34
|
+
const EmulatorStatusFault = Schema.Struct({
|
|
35
|
+
kind: Schema.Literal("status"),
|
|
36
|
+
status: EmulatorResponseStatus,
|
|
37
|
+
headers: Schema.optionalKey(EmulatorHeaderRecord),
|
|
38
|
+
body: Schema.optionalKey(Schema.Json),
|
|
39
|
+
...emulatorFaultFields
|
|
40
|
+
});
|
|
41
|
+
/** Send `chunks` body chunks, then error the body stream (a dropped connection). */
|
|
42
|
+
const EmulatorErrorAfterChunksFault = Schema.Struct({
|
|
43
|
+
kind: Schema.Literal("error-after-chunks"),
|
|
44
|
+
chunks: ChunkCount,
|
|
45
|
+
...emulatorFaultFields
|
|
46
|
+
});
|
|
47
|
+
/** Send `chunks` body chunks, then close the body cleanly (a truncated response). */
|
|
48
|
+
const EmulatorTruncateAfterChunksFault = Schema.Struct({
|
|
49
|
+
kind: Schema.Literal("truncate-after-chunks"),
|
|
50
|
+
chunks: ChunkCount,
|
|
51
|
+
...emulatorFaultFields
|
|
52
|
+
});
|
|
53
|
+
/** A scripted error response: status, body (a string is sent as is), and optional headers. */
|
|
54
|
+
const EmulatorScriptedError = Schema.Struct({ error: Schema.Struct({
|
|
55
|
+
status: EmulatorResponseStatus,
|
|
56
|
+
body: Schema.Json,
|
|
57
|
+
headers: Schema.optionalKey(EmulatorHeaderRecord)
|
|
58
|
+
}) });
|
|
59
|
+
const strict = { onExcessProperty: "error" };
|
|
60
|
+
const decodeJsonText = Schema.decodeUnknownResult(Schema.fromJsonString(Schema.Json));
|
|
61
|
+
const issueMessage = (issue) => new Schema.SchemaError(issue).message;
|
|
62
|
+
const isJsonObject = (value) => value !== null && value !== void 0 && Predicate.isObject(value) && !Array.isArray(value);
|
|
63
|
+
const jsonField = (value, key) => isJsonObject(value) ? value[key] : void 0;
|
|
64
|
+
const stringField = (value, key) => {
|
|
65
|
+
const field = jsonField(value, key);
|
|
66
|
+
return Predicate.isString(field) ? field : void 0;
|
|
67
|
+
};
|
|
68
|
+
const textEncoder = new TextEncoder();
|
|
69
|
+
/** A JSON response; `body` is any JSON-serializable value (ledger snapshots, fault states, envelopes). */
|
|
70
|
+
const jsonResponse = (status, body, headers = {}) => {
|
|
71
|
+
const responseHeaders = new Headers(headers);
|
|
72
|
+
responseHeaders.set("content-type", "application/json");
|
|
73
|
+
return new Response(JSON.stringify(body), {
|
|
74
|
+
status,
|
|
75
|
+
headers: responseHeaders
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
/** An emulator (not emulated-service) error: control-plane problems and unbuildable responses. */
|
|
79
|
+
const controlError = (status, message, headers = {}) => jsonResponse(status, { error: {
|
|
80
|
+
message,
|
|
81
|
+
type: "emulator_error"
|
|
82
|
+
} }, headers);
|
|
83
|
+
/** A scripted or fault body: a string is sent as is, anything else as JSON. */
|
|
84
|
+
const bodyText = (body) => Predicate.isString(body) ? body : JSON.stringify(body);
|
|
85
|
+
/** Headers for a scripted or fault body, with a `content-type` matching the body when unset. */
|
|
86
|
+
const bodyHeaders = (headers, body) => {
|
|
87
|
+
const result = new Headers(headers ?? {});
|
|
88
|
+
if (!result.has("content-type")) result.set("content-type", Predicate.isString(body) ? "text/plain" : "application/json");
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
const readText = (request) => request.text().then((text) => text, () => void 0);
|
|
92
|
+
const parseJson = (text) => {
|
|
93
|
+
const result = decodeJsonText(text);
|
|
94
|
+
return Result.isSuccess(result) ? result.success : void 0;
|
|
95
|
+
};
|
|
96
|
+
const pathMatches = (pattern, path) => pattern.endsWith("*") ? path.startsWith(pattern.slice(0, -1)) : pattern === path;
|
|
97
|
+
const faultMatches = (fault, path, model) => (fault.match?.path === void 0 || pathMatches(fault.match.path, path)) && (fault.match?.model === void 0 || fault.match.model === model);
|
|
98
|
+
const isBodyChunkFault = (fault) => fault.kind === "error-after-chunks" || fault.kind === "truncate-after-chunks";
|
|
99
|
+
const chunkFaultProblem = (fault, chunks) => {
|
|
100
|
+
return (fault.kind === "error-after-chunks" ? fault.chunks <= chunks : fault.chunks < chunks) ? void 0 : `${fault.kind} after ${fault.chunks} chunk(s) cannot apply to a response with ${chunks} chunk(s)`;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Strictly pull-driven body: chunk `k` is produced only when the consumer
|
|
104
|
+
* pulls it, and `entry.bodyChunks` counts the chunks handed over. The fault
|
|
105
|
+
* (already validated against the chunk count) applies when the consumer pulls
|
|
106
|
+
* past `fault.chunks` chunks.
|
|
107
|
+
*/
|
|
108
|
+
const chunkedBody = (chunks, fault, entry) => {
|
|
109
|
+
let next = 0;
|
|
110
|
+
return new ReadableStream({ pull: (controller) => {
|
|
111
|
+
if (fault !== void 0 && next === fault.chunks) {
|
|
112
|
+
if (fault.kind === "error-after-chunks") controller.error(/* @__PURE__ */ new Error(`synthetic mid-stream failure after ${next} chunk(s)`));
|
|
113
|
+
else controller.close();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const chunk = chunks[next];
|
|
117
|
+
if (chunk === void 0) {
|
|
118
|
+
controller.close();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
next += 1;
|
|
122
|
+
entry.bodyChunks += 1;
|
|
123
|
+
controller.enqueue(textEncoder.encode(chunk));
|
|
124
|
+
} }, { highWaterMark: 0 });
|
|
125
|
+
};
|
|
126
|
+
const withEvidence = (response, evidence) => {
|
|
127
|
+
if (evidence === "unverified") response.headers.set(emulatorEvidenceHeader, "unverified");
|
|
128
|
+
return response;
|
|
129
|
+
};
|
|
130
|
+
const maxSchemaDepth = 8;
|
|
131
|
+
/**
|
|
132
|
+
* A synthetic value for a JSON Schema: required object properties only,
|
|
133
|
+
* non-empty strings (or the first `enum` / `const` value), the minimum for
|
|
134
|
+
* numbers, `true` for booleans, and `minItems` synthetic items for arrays.
|
|
135
|
+
*/
|
|
136
|
+
const synthesizeValue = (schema, name, depth) => {
|
|
137
|
+
const constant = jsonField(schema, "const");
|
|
138
|
+
if (constant !== void 0) return constant;
|
|
139
|
+
const choices = jsonField(schema, "enum");
|
|
140
|
+
if (Array.isArray(choices) && choices.length > 0) return choices[0] ?? null;
|
|
141
|
+
for (const key of [
|
|
142
|
+
"anyOf",
|
|
143
|
+
"oneOf",
|
|
144
|
+
"allOf"
|
|
145
|
+
]) {
|
|
146
|
+
const options = jsonField(schema, key);
|
|
147
|
+
if (Array.isArray(options) && options.length > 0) return synthesizeValue(options.find((option) => stringField(option, "type") !== "null") ?? options[0], name, depth + 1);
|
|
148
|
+
}
|
|
149
|
+
const declared = jsonField(schema, "type");
|
|
150
|
+
const type = Array.isArray(declared) ? declared.find((entry) => Predicate.isString(entry) && entry !== "null") : declared;
|
|
151
|
+
if (depth > maxSchemaDepth) return null;
|
|
152
|
+
switch (type) {
|
|
153
|
+
case "object": {
|
|
154
|
+
const properties = jsonField(schema, "properties");
|
|
155
|
+
const required = jsonField(schema, "required");
|
|
156
|
+
const value = {};
|
|
157
|
+
for (const key of Array.isArray(required) ? required : []) if (Predicate.isString(key)) value[key] = synthesizeValue(jsonField(properties, key), key, depth + 1);
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
case "array": {
|
|
161
|
+
const minItems = jsonField(schema, "minItems");
|
|
162
|
+
const count = Predicate.isNumber(minItems) && minItems > 0 ? Math.min(minItems, 3) : 0;
|
|
163
|
+
return Array.from({ length: count }, () => synthesizeValue(jsonField(schema, "items"), name, depth + 1));
|
|
164
|
+
}
|
|
165
|
+
case "integer":
|
|
166
|
+
case "number": {
|
|
167
|
+
const minimum = jsonField(schema, "minimum");
|
|
168
|
+
return Predicate.isNumber(minimum) ? minimum : 1;
|
|
169
|
+
}
|
|
170
|
+
case "boolean": return true;
|
|
171
|
+
case "null": return null;
|
|
172
|
+
default: return `synthetic ${name}`;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
/** Split text into up to `parts` non-empty fragments (at least one). */
|
|
176
|
+
const splitFragments = (text, parts) => {
|
|
177
|
+
if (text.length <= 1) return [text];
|
|
178
|
+
const size = Math.ceil(text.length / Math.min(parts, text.length));
|
|
179
|
+
const fragments = [];
|
|
180
|
+
for (let index = 0; index < text.length; index += size) fragments.push(text.slice(index, index + size));
|
|
181
|
+
return fragments;
|
|
182
|
+
};
|
|
183
|
+
/** A rough synthetic token count (about four characters per token, at least one). */
|
|
184
|
+
const approximateTokens = (text) => Math.max(1, Math.ceil(text.length / 4));
|
|
185
|
+
/**
|
|
186
|
+
* Create the kernel of an emulator. Each call has independent ledger, fault,
|
|
187
|
+
* and script state. Handlers use the returned helpers, then `serve` binds
|
|
188
|
+
* them to the manifest (throwing `EmulatorRouteUnmapped` on a mismatch) and
|
|
189
|
+
* returns the emulator API.
|
|
190
|
+
*/
|
|
191
|
+
const makeEmulatorKernel = (config) => {
|
|
192
|
+
const decodeFault = Schema.decodeUnknownResult(config.faultSchema, strict);
|
|
193
|
+
const decodeFaultList = Schema.decodeUnknownResult(Schema.Union([config.faultSchema, Schema.Struct({ faults: Schema.Array(config.faultSchema) })]), strict);
|
|
194
|
+
const decodeTurn = Schema.decodeUnknownResult(config.turnSchema, strict);
|
|
195
|
+
const decodeTurnList = Schema.decodeUnknownResult(Schema.Union([Schema.Struct({ turns: Schema.Array(config.turnSchema) }), config.turnSchema]), strict);
|
|
196
|
+
let entries = [];
|
|
197
|
+
let faultStates = [];
|
|
198
|
+
let turns = [];
|
|
199
|
+
let nextSeq = 1;
|
|
200
|
+
let nextFaultId = 1;
|
|
201
|
+
const snapshotFault = (state) => ({ ...state });
|
|
202
|
+
const clearLedger = () => {
|
|
203
|
+
entries = [];
|
|
204
|
+
nextSeq = 1;
|
|
205
|
+
};
|
|
206
|
+
const addFault = (input) => {
|
|
207
|
+
const decoded = decodeFault(input);
|
|
208
|
+
if (Result.isFailure(decoded)) return issueMessage(decoded.failure.issue);
|
|
209
|
+
const state = {
|
|
210
|
+
id: nextFaultId++,
|
|
211
|
+
fault: decoded.success,
|
|
212
|
+
remaining: decoded.success.count,
|
|
213
|
+
applied: 0
|
|
214
|
+
};
|
|
215
|
+
faultStates.push(state);
|
|
216
|
+
return snapshotFault(state);
|
|
217
|
+
};
|
|
218
|
+
const enqueueTurn = (input) => {
|
|
219
|
+
const decoded = decodeTurn(input);
|
|
220
|
+
if (Result.isFailure(decoded)) return issueMessage(decoded.failure.issue);
|
|
221
|
+
turns.push(decoded.success);
|
|
222
|
+
};
|
|
223
|
+
/** The first matching fault with uses left, in insertion order (not yet consumed). */
|
|
224
|
+
const takeFault = (path, model) => faultStates.find((state) => (state.remaining === void 0 || state.remaining > 0) && faultMatches(state.fault, path, model));
|
|
225
|
+
/** Use up one application of a fault and record its kind in the ledger entry. */
|
|
226
|
+
const consumeFault = (state, entry) => {
|
|
227
|
+
state.applied += 1;
|
|
228
|
+
entry.fault = state.fault.kind;
|
|
229
|
+
if (state.remaining !== void 0) state.remaining -= 1;
|
|
230
|
+
};
|
|
231
|
+
/** The next scripted turn, used up when its request arrives. */
|
|
232
|
+
const nextTurn = () => turns.shift();
|
|
233
|
+
const coverage = () => ({
|
|
234
|
+
routes: config.routes.map((route) => ({
|
|
235
|
+
...route,
|
|
236
|
+
requests: entries.filter((entry) => entry.evidence !== "unknown-route" && entry.method === route.method && entry.path === route.path).length
|
|
237
|
+
})),
|
|
238
|
+
unknownRouteRequests: entries.filter((entry) => entry.evidence === "unknown-route").length
|
|
239
|
+
});
|
|
240
|
+
/**
|
|
241
|
+
* Answer with a pull-driven body. A matching `error-after-chunks` or
|
|
242
|
+
* `truncate-after-chunks` fault shapes it; one that cannot take effect
|
|
243
|
+
* answers 500 and is not consumed. Other fault kinds are left to the caller.
|
|
244
|
+
*/
|
|
245
|
+
const respondWithBody = (entry, status, headers, chunks, fault) => {
|
|
246
|
+
const chunkFault = fault === void 0 || !isBodyChunkFault(fault.fault) ? void 0 : fault.fault;
|
|
247
|
+
if (chunkFault !== void 0) {
|
|
248
|
+
const problem = chunkFaultProblem(chunkFault, chunks.length);
|
|
249
|
+
if (problem !== void 0) {
|
|
250
|
+
entry.faultError = problem;
|
|
251
|
+
entry.status = 500;
|
|
252
|
+
return controlError(500, `emulator fault cannot apply: ${problem}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const response = new Response(chunkedBody(chunks, chunkFault, entry), {
|
|
256
|
+
status,
|
|
257
|
+
headers
|
|
258
|
+
});
|
|
259
|
+
if (chunkFault !== void 0 && fault !== void 0) consumeFault(fault, entry);
|
|
260
|
+
entry.status = response.status;
|
|
261
|
+
return response;
|
|
262
|
+
};
|
|
263
|
+
/** Answer a `status` fault with its status, headers, and body (or `defaultBody`). */
|
|
264
|
+
const respondWithStatusFault = (entry, state, fault, defaultBody) => {
|
|
265
|
+
const faultBody = fault.body ?? defaultBody(fault.status);
|
|
266
|
+
const response = new Response(bodyText(faultBody), {
|
|
267
|
+
status: fault.status,
|
|
268
|
+
headers: bodyHeaders(fault.headers, faultBody)
|
|
269
|
+
});
|
|
270
|
+
consumeFault(state, entry);
|
|
271
|
+
entry.status = response.status;
|
|
272
|
+
return response;
|
|
273
|
+
};
|
|
274
|
+
/** Answer a scripted error turn; a matching chunk fault still shapes its body. */
|
|
275
|
+
const respondWithScriptedError = (entry, error, fault) => respondWithBody(entry, error.status, bodyHeaders(error.headers, error.body), [bodyText(error.body)], fault);
|
|
276
|
+
const reset = () => {
|
|
277
|
+
clearLedger();
|
|
278
|
+
faultStates = [];
|
|
279
|
+
turns = [];
|
|
280
|
+
};
|
|
281
|
+
const state = () => ({
|
|
282
|
+
...config.stateFields(),
|
|
283
|
+
pendingTurns: turns.length,
|
|
284
|
+
faults: faultStates.map(snapshotFault),
|
|
285
|
+
ledgerEntries: entries.length
|
|
286
|
+
});
|
|
287
|
+
const controlPlane = async (request, path) => {
|
|
288
|
+
const method = request.method;
|
|
289
|
+
const allow = (methods) => controlError(405, "method not allowed", { allow: methods });
|
|
290
|
+
const jsonBody = async () => {
|
|
291
|
+
const text = await readText(request);
|
|
292
|
+
return text === void 0 ? void 0 : parseJson(text);
|
|
293
|
+
};
|
|
294
|
+
switch (path) {
|
|
295
|
+
case "/_emulate/ledger":
|
|
296
|
+
if (method === "GET") return jsonResponse(200, { entries: entries.map(config.snapshotEntry) });
|
|
297
|
+
if (method === "DELETE") {
|
|
298
|
+
const cleared = entries.length;
|
|
299
|
+
clearLedger();
|
|
300
|
+
return jsonResponse(200, { cleared });
|
|
301
|
+
}
|
|
302
|
+
return allow("GET, DELETE");
|
|
303
|
+
case "/_emulate/faults": {
|
|
304
|
+
if (method === "GET") return jsonResponse(200, { faults: faultStates.map(snapshotFault) });
|
|
305
|
+
if (method === "DELETE") {
|
|
306
|
+
const cleared = faultStates.length;
|
|
307
|
+
faultStates = [];
|
|
308
|
+
return jsonResponse(200, { cleared });
|
|
309
|
+
}
|
|
310
|
+
if (method !== "POST") return allow("GET, POST, DELETE");
|
|
311
|
+
const decoded = decodeFaultList(await jsonBody());
|
|
312
|
+
if (Result.isFailure(decoded)) return controlError(400, `invalid fault: ${issueMessage(decoded.failure.issue)}`);
|
|
313
|
+
return jsonResponse(201, { faults: ("faults" in decoded.success ? decoded.success.faults : [decoded.success]).map((fault) => addFault(fault)) });
|
|
314
|
+
}
|
|
315
|
+
case "/_emulate/script": {
|
|
316
|
+
if (method !== "POST") return allow("POST");
|
|
317
|
+
const decoded = decodeTurnList(await jsonBody());
|
|
318
|
+
if (Result.isFailure(decoded)) return controlError(400, `invalid turn: ${issueMessage(decoded.failure.issue)}`);
|
|
319
|
+
const queued = Predicate.isObject(decoded.success) && "turns" in decoded.success ? decoded.success.turns : [decoded.success];
|
|
320
|
+
for (const turn of queued) enqueueTurn(turn);
|
|
321
|
+
return jsonResponse(201, { pending: turns.length });
|
|
322
|
+
}
|
|
323
|
+
case "/_emulate/reset":
|
|
324
|
+
if (method !== "POST") return allow("POST");
|
|
325
|
+
reset();
|
|
326
|
+
return jsonResponse(200, { reset: true });
|
|
327
|
+
case "/_emulate/state":
|
|
328
|
+
if (method !== "GET") return allow("GET");
|
|
329
|
+
return jsonResponse(200, state());
|
|
330
|
+
case "/_emulate/coverage":
|
|
331
|
+
if (method !== "GET") return allow("GET");
|
|
332
|
+
return jsonResponse(200, coverage());
|
|
333
|
+
default: return controlError(404, "unknown control-plane route");
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* Bind one handler per manifest route (keyed by `emulatorRouteKey`) and
|
|
338
|
+
* return the emulator API. Throws `EmulatorRouteUnmapped` when a manifest
|
|
339
|
+
* route has no handler or a handler has no manifest route.
|
|
340
|
+
*/
|
|
341
|
+
const serve = (handlers) => {
|
|
342
|
+
const routes = bindRouteHandlers(config.routes, handlers);
|
|
343
|
+
const emulatedApi = async (request, path) => {
|
|
344
|
+
const bound = routes.find((candidate) => candidate.route.method === request.method && candidate.route.path === path);
|
|
345
|
+
const entry = config.newEntry({
|
|
346
|
+
seq: nextSeq++,
|
|
347
|
+
method: request.method,
|
|
348
|
+
path,
|
|
349
|
+
evidence: bound?.route.evidence ?? "unknown-route",
|
|
350
|
+
status: 0,
|
|
351
|
+
bodyChunks: 0
|
|
352
|
+
});
|
|
353
|
+
entries.push(entry);
|
|
354
|
+
if (bound === void 0) {
|
|
355
|
+
const response = config.unknownRoute(entry);
|
|
356
|
+
entry.status = response.status;
|
|
357
|
+
return response;
|
|
358
|
+
}
|
|
359
|
+
return withEvidence(await bound.handler(request, entry, path).catch(() => {
|
|
360
|
+
entry.status = 500;
|
|
361
|
+
entry.responseError = "the emulator could not build the planned response";
|
|
362
|
+
return controlError(500, "emulator could not build the response");
|
|
363
|
+
}), bound.route.evidence);
|
|
364
|
+
};
|
|
365
|
+
const handle = (request) => {
|
|
366
|
+
const path = URL.canParse(request.url) ? new URL(request.url).pathname : "/";
|
|
367
|
+
return path === "/_emulate" || path.startsWith("/_emulate/") ? controlPlane(request, path) : emulatedApi(request, path);
|
|
368
|
+
};
|
|
369
|
+
return {
|
|
370
|
+
fetch: (request) => handle(request).catch(() => controlError(500, "emulator failed to handle the request")),
|
|
371
|
+
ledger: {
|
|
372
|
+
entries: () => entries.map(config.snapshotEntry),
|
|
373
|
+
clear: clearLedger
|
|
374
|
+
},
|
|
375
|
+
reset,
|
|
376
|
+
faults: {
|
|
377
|
+
add: (fault) => {
|
|
378
|
+
const added = addFault(fault);
|
|
379
|
+
if (Predicate.isString(added)) throw config.inputInvalid("fault", added);
|
|
380
|
+
return added;
|
|
381
|
+
},
|
|
382
|
+
list: () => faultStates.map(snapshotFault),
|
|
383
|
+
clear: () => {
|
|
384
|
+
faultStates = [];
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
script: {
|
|
388
|
+
enqueue: (turn) => {
|
|
389
|
+
const problem = enqueueTurn(turn);
|
|
390
|
+
if (problem !== void 0) throw config.inputInvalid("turn", problem);
|
|
391
|
+
},
|
|
392
|
+
pending: () => turns.length,
|
|
393
|
+
clear: () => {
|
|
394
|
+
turns = [];
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
coverage
|
|
398
|
+
};
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
takeFault,
|
|
402
|
+
consumeFault,
|
|
403
|
+
nextTurn,
|
|
404
|
+
respondWithBody,
|
|
405
|
+
respondWithStatusFault,
|
|
406
|
+
respondWithScriptedError,
|
|
407
|
+
serve
|
|
408
|
+
};
|
|
409
|
+
};
|
|
410
|
+
//#endregion
|
|
411
|
+
export { ChunkCount, EmulatorErrorAfterChunksFault, EmulatorFaultMatch, EmulatorScriptedError, EmulatorStatusFault, EmulatorTruncateAfterChunksFault, approximateTokens, bodyText, controlError, emulatorFaultFields, isJsonObject, jsonField, jsonResponse, makeEmulatorKernel, parseJson, readText, splitFragments, stringField, synthesizeValue };
|
|
412
|
+
|
|
413
|
+
//# sourceMappingURL=emulator-kernel.mjs.map
|