@crvouga/mockingbird-service-bedrock 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog — @crvouga/mockingbird-service-bedrock
2
+
3
+ ## 0.1.0 (2026-09-22)
4
+
5
+ Initial release.
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # @crvouga/mockingbird-service-bedrock
2
+
3
+ Stateful, scriptable mock of **Amazon Bedrock Runtime** for test suites: `Converse`,
4
+ `ConverseStream` (byte-exact `application/vnd.amazon.eventstream` frames), `InvokeModel`
5
+ (Anthropic Messages bodies and Titan text embeddings), `InvokeModelWithBidirectionalStream`
6
+ (Nova Sonic over HTTP/2 duplex), and the AgentCore `InvokeHarness` event stream.
7
+
8
+ The mock never generates language. It replays **scripts**: a test says "when the chat model
9
+ sees *dizzy* with `report_rx_symptom` available, emit this `toolUse`; after the tool result
10
+ comes back, say this". Chat turns, approval cards, guardrail blocks, structured output and
11
+ throttles become deterministic and take milliseconds.
12
+
13
+ - Operation coverage: [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/bedrock/SUPPORT.md)
14
+ - Proven with the official clients our consumer pins: `@aws-sdk/client-bedrock-runtime@3.1132.0`,
15
+ `@aws-sdk/client-bedrock-agentcore@3.1074.0`, `@ai-sdk/amazon-bedrock@4.0.176` + `ai@6.0.283`.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install -D @crvouga/mockingbird-service-bedrock
21
+ ```
22
+
23
+ ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
24
+ `npx mockingbird-bedrock serve` (h2c + HTTP/1.1 on one port), `createServer` from `./server`,
25
+ or `createRuntime` with any Fetch server (HTTP/1.1 only — see below).
26
+
27
+ ## Usage
28
+
29
+ Point the app at it. No code change: every client honours the endpoint variables.
30
+
31
+ ```bash
32
+ npx mockingbird-bedrock serve --port 8796
33
+ export AWS_ENDPOINT_URL_BEDROCK_RUNTIME=http://127.0.0.1:8796 # SDK v3, AI SDK, botocore
34
+ export AWS_ENDPOINT_URL_BEDROCK_AGENTCORE=http://127.0.0.1:8796 # AgentCore InvokeHarness
35
+ ```
36
+
37
+ ```ts
38
+ import { createServer } from "@crvouga/mockingbird-service-bedrock/server"
39
+
40
+ const bedrock = await createServer({ port: 8796 })
41
+ await fetch(`${bedrock.url}/__admin/scripts`, {
42
+ method: "PUT",
43
+ headers: { "content-type": "application/json" },
44
+ body: JSON.stringify({
45
+ scripts: [
46
+ {
47
+ id: "rx-symptom-approval",
48
+ match: { modelId: "*sonnet*", lastUserText: { contains: "dizzy" }, toolsInclude: ["report_rx_symptom"] },
49
+ turns: [
50
+ {
51
+ toolUse: { name: "report_rx_symptom", input: { symptoms: [{ symptomDefinitionId: 7, severity: 3 }] } },
52
+ stopReason: "tool_use",
53
+ },
54
+ {
55
+ expectToolResult: { name: "report_rx_symptom" },
56
+ text: "I've flagged that for your care team.",
57
+ chunkSize: 12,
58
+ usage: { inputTokens: 1200, outputTokens: 40, cacheReadInputTokens: 900 },
59
+ },
60
+ ],
61
+ },
62
+ ],
63
+ }),
64
+ })
65
+ // …the backend's streamText turn now emits the approval card; approving resumes the turn.
66
+ await bedrock.close()
67
+ ```
68
+
69
+ ### Protocols
70
+
71
+ The AWS SDK v3 clients for Bedrock Runtime default to `NodeHttp2Handler`: against an
72
+ `http://` endpoint they speak **h2c** (cleartext HTTP/2 with prior knowledge), and Nova Sonic
73
+ needs HTTP/2 duplex. The AI SDK and AgentCore use HTTP/1.1. `mockingbird-bedrock serve` and
74
+ `createServer` sniff each connection's first bytes and serve both on one port. (`serve
75
+ --config` from another service's CLI, and `createRuntime` behind a plain Fetch server, speak
76
+ HTTP/1.1 only — fine for the AI SDK, not for the SDK v3 Bedrock client.)
77
+
78
+ ### Routes
79
+
80
+ | Route | Behaviour |
81
+ | --- | --- |
82
+ | `POST /model/{modelId}/converse` | Converse JSON: `output.message.content[]` (`text`, `toolUse`, `reasoningContent`), `stopReason`, `usage` (with `cacheRead/WriteInputTokens` when a `cachePoint` is present), `metrics`, `trace.guardrail` (with `guardrailConfig.trace: "enabled"`), `x-amzn-RequestId`. |
83
+ | `POST /model/{modelId}/converse-stream` | The same answer as event frames: `messageStart`, `contentBlockStart` (tool use), `contentBlockDelta` (`text`, `toolUse.input` partial JSON, `reasoningContent`), `contentBlockStop`, `messageStop`, `metadata`; exception frames mid-stream. |
84
+ | `POST /model/{modelId}/invoke` | `amazon.titan-embed-text-*`: `{embedding, inputTextTokenCount}` — a deterministic unit vector from SHA-256(`inputText`) (1024-d by default; `dimensions` 256/512/1024). Claude models: an Anthropic Messages body in, a Messages response out. |
85
+ | `POST /model/{modelId}/invoke-with-bidirectional-stream` | Nova Sonic (`*sonic*`), HTTP/2 duplex: reads `chunk` events as they arrive (SigV4 envelopes unwrapped), answers each user turn with `textOutput` + 24 kHz PCM tone `audioOutput` + `toolUse`, then `usageEvent`; `completionEnd` after `sessionEnd`. |
86
+ | `POST /harnesses/invoke?harnessArn=` | AgentCore harness stream: `messageStart`, `contentBlockDelta` (`text` or `toolResult[]`), `contentBlockStop`, `messageStop`, `metadata`; `validationException` / `internalServerException` / `runtimeClientError` frames. |
87
+
88
+ `modelId` is any model id, inference-profile id (`global.` / `us.`) or URL-encoded ARN. SigV4 is
89
+ accepted without verification. Request checks Bedrock makes and our code branches on are
90
+ enforced: role alternation, first/last message is the user (assistant **prefill** is rejected
91
+ for Claude 4.5+), tool-use/tool-result pairing, `toolConfig` required with tool blocks, a
92
+ document needs a sibling text block, `temperature` + `top_p` together on Claude 4.5+. Errors are
93
+ `x-amzn-ErrorType: <Name>:http://internal.amazon.com/coral/com.amazon.bedrock/` + `{"message"}`.
94
+ Output longer than `maxTokens` (≈4 chars/token) is cut with `stopReason: "max_tokens"`.
95
+
96
+ ### Scripts (`PUT /__admin/scripts`)
97
+
98
+ A script is `{id, match?, turns, times?}`. The first script (in insertion order) whose `match`
99
+ accepts a call **and** has a turn for that point in the conversation answers it; `times` caps
100
+ how many calls it answers.
101
+
102
+ - **Match keys:** `modelId` (glob), `operation` (`Converse`, `ConverseStream`, `InvokeModel`,
103
+ `InvokeModelWithBidirectionalStream`, `InvokeHarness`), `lastUserText` (string = contains, or
104
+ `{contains, regex, flags}`), `systemHash` (SHA-256 hex of the system text blocks joined with
105
+ `\n`), `toolsInclude`, `toolChoice` (`auto` / `any` / a tool name), `hasDocument`, `hasImage`,
106
+ `callIndex` (0-based index of the call in the namespace).
107
+ - **Turn selection** reads the conversation, not server state: turn *n* answers the call that
108
+ comes after *n* assistant messages since the member last said something. So turn 0 is the
109
+ first call of a user turn, turn 1 is the call that resumes after a tool result, and every new
110
+ conversation starts over. `expectToolResult: {name}` makes a turn answer only when the last
111
+ user message carries that tool's result. Nova Sonic counts answers within the session.
112
+ - **A turn** is any of: `text` (streamed in `chunkSize`-character deltas, `delayMsPerChunk`
113
+ mock-clock ms apart), `reasoning`, `toolUse` (`{name, input, toolUseId?}` or a list), `json`
114
+ (structured output, rendered in the form the request asked for — see below), `guardrail`
115
+ (`true` or `{text, trace}`: `guardrail_intervened` with Bedrock's refusal text and a trace),
116
+ `toolResult` (harness), `userTranscript` (Nova Sonic), `stopReason`, `usage`, `fault`.
117
+ - **Structured output** (`json`) goes out as text JSON for
118
+ `outputConfig.textFormat.structure.jsonSchema` (Makor) and
119
+ `additionalModelRequestFields.output_config.format` (AI SDK native), and as a `toolUse` of the
120
+ forced tool for `toolChoice: {tool}` or `{any}` (the AI SDK's synthetic `json` tool, insight
121
+ reports' `record_chat_*`).
122
+ - **Unscripted defaults**, each counted as `unscripted` (`GET /__admin/scripts` → `stats`):
123
+ chat → `"OK."` (`PUT /__admin/settings {"defaultText"}`); structured output → the minimal
124
+ object valid against the request's schema; our intent classifier (its system prompt asks for
125
+ `{"category","confidence"}`) → `{"category":"general","confidence":0.9}`; InvokeModel with a
126
+ Claude body (the EMR scribe) → a 4-section SOAP JSON; Titan → the SHA-256 vector; InvokeHarness
127
+ → an `eligible_for_clinician_review` prescreen summary; Nova Sonic → `"OK."` spoken.
128
+
129
+ ### Faults
130
+
131
+ A turn's `fault` (or a preset, `POST /__admin/faults {"preset": "<name>", "count"?: n}`, which
132
+ applies to every model and harness call in the calling namespace):
133
+
134
+ | Preset / fault | Effect |
135
+ | --- | --- |
136
+ | `throttling` | 429 `ThrottlingException` before the first chunk |
137
+ | `mid_stream_exception` (`afterChunks`, `exceptionType`) | content chunks, then a `modelStreamErrorException` frame (harness: `internalServerException`) |
138
+ | `mid_stream_throttling` | the same with a `throttlingException` frame |
139
+ | `validation_exception` / `validation` | 400 `ValidationException` |
140
+ | `max_tokens` | output cut in half, `stopReason: "max_tokens"` |
141
+ | `latency` (`latencyMs`) | the response starts after 2 s on the mock clock |
142
+ | `truncated_frame` | the stream stops half-way through a frame (both decoders throw) |
143
+ | `model_timeout`, `service_unavailable`, `access_denied`, `internal_server` | 408 / 503 / 403 / 500 with the matching `x-amzn-ErrorType` |
144
+
145
+ Chunk pacing and latency wait on the **mock clock**: freeze it (`POST /__admin/clock
146
+ {"freeze": true}`) and advance it to release each chunk, so time-to-first-token tests are exact.
147
+
148
+ ### Admin (beyond the standard contract)
149
+
150
+ | Route | Effect |
151
+ | --- | --- |
152
+ | `PUT /__admin/scripts` | Replace the namespace's scripts (`{"scripts": [...]}`); validated. |
153
+ | `POST /__admin/scripts` | Add scripts (same ids overwrite). |
154
+ | `GET /__admin/scripts` | Scripts plus `stats` (`calls`, `scripted`, `unscripted`, `byScript`, `byFallback`, `byOperation`). |
155
+ | `DELETE /__admin/scripts[?id=]` | Remove one or all. |
156
+ | `GET /__admin/model-metrics` | Just the stats. |
157
+ | `GET/PUT /__admin/settings` | `defaultText`, `chunkSize` (16), `delayMsPerChunk` (0), `audioTurnChunks` (0: a Nova Sonic spoken turn ends at the audio `contentEnd`; n: after n audio frames). |
158
+
159
+ The request journal (`GET /__admin/requests`) records per call only `modelId`, `script` (or
160
+ `unscripted:<default>`), tool names, flags (`cachePoint`, `guardrail`, `document`, `image`,
161
+ `structured:<form>`), `stopReason` and token counts — never prompt or message text.
162
+
163
+ ### Namespaces
164
+
165
+ `x-mockingbird-namespace`, a `/ns/<name>` prefix on the endpoint URL, or by credential: the SDKs
166
+ cannot add headers, so map each worker's access key id:
167
+ `PUT /__admin/credentials {"credentials": {"<AWS_ACCESS_KEY_ID>": "<namespace>"}}`.
168
+
169
+ ### Deliberately not modelled
170
+
171
+ - Language: output only ever comes from scripts or the fixed defaults.
172
+ - Real speech: Nova Sonic audio out is a 440 Hz PCM tone whose length follows the text; audio
173
+ in is counted, never transcribed (a spoken turn matches with `lastUserText: ""`).
174
+ - `InvokeModelWithResponseStream` (no consumer calls it), guardrail evaluation itself
175
+ (`ApplyGuardrail`; scripts decide when the guardrail intervenes), prompt caching arithmetic
176
+ (cache token counts are 0 unless scripted), model-specific output token limits.
177
+ - SigV4 signatures are not verified; the access key id only selects a namespace.
178
+
179
+ ## API
180
+
181
+ | Export | Kind | Description |
182
+ | --- | --- | --- |
183
+ | `BedrockAPI` | class | The in-process mock: `fetch`, `reset`, `scripts()`, `putScripts(scripts, replace?)`, `removeScripts(id?)`, `stats()`. Options: `sqlite`, `now`, `namespace`, `settings`, `scripts`, `sleep`. |
184
+ | `createRuntime` | function | The mock with the full service contract (health, admin, namespaces, SigV4 credentials, presets, scripts). Options: `settings`, `scripts`, `clock`, `seed`, `adminKey`, `onLog`, `sqlite`. |
185
+ | `BEDROCK_PRESETS` | object | Every named fault preset. |
186
+ | `BEDROCK_NAMESPACE` | string | The service name, `"bedrock"`. |
187
+ | `bedrockError` | function | A Bedrock error response (`status`, `x-amzn-ErrorType`, `{message}`). |
188
+ | `accessKeyCredential` | function | The SigV4 access key id of a request (how credentials map to namespaces). |
189
+ | `clockSleep` | function | A sleep that waits on a (possibly frozen) mock clock. |
190
+ | `titanEmbedding` | function | The deterministic unit vector Titan answers with. |
191
+ | `sampleSchema` | function | The minimal instance of a JSON Schema (the unscripted structured output). |
192
+ | `parseScript` | function | Validate one script (what `PUT /__admin/scripts` runs). |
193
+ | `MODEL_OPERATIONS`, `STOP_REASONS`, `TURN_FAULTS` | arrays | The values `operation`, `stopReason` and `fault` accept. |
194
+ | `DEFAULT_SETTINGS`, `DEFAULT_CHAT_TEXT`, `DEFAULT_CLASSIFIER`, `DEFAULT_SOAP_NOTE`, `GUARDRAIL_BLOCKED_TEXT` | values | The defaults. |
195
+ | `encodeMessage`, `decodeMessage`, `FrameReader`, `readFrames`, `eventFrame`, `exceptionFrame`, `unwrapSigned`, `crc32`, `EventStreamError` | codec | The event-stream codec (exact prelude, headers and CRC32s), in both directions. |
196
+ | `document`, `operationIds`, `supportedOperationIds` | values | The vendored OpenAPI contract and its operation ids. |
197
+ | `createServer`, `serveTarget`, `DEFAULT_PORT`, `listenH2c` (`./server`) | Node | Serve h2c + HTTP/1.1 on one port; the `serve` CLI target; port 8796; the dual-protocol listener for any Fetch handler. |
198
+
199
+ Part of [mockingbird](https://github.com/crvouga/mockingbird).
@@ -0,0 +1,299 @@
1
+ import {
2
+ createRuntime,
3
+ parseScript
4
+ } from "./chunk-O5ZO5KMR.js";
5
+
6
+ // src/h2c.ts
7
+ import {
8
+ createServer as createHttp1Server
9
+ } from "node:http";
10
+ import {
11
+ createServer as createHttp2Server,
12
+ constants as http2Constants
13
+ } from "node:http2";
14
+ import { connect, createServer as createNetServer } from "node:net";
15
+ import { Readable } from "node:stream";
16
+ var PREFACE = "PRI * HTTP/2.0";
17
+ var CONNECTION_HEADERS = /* @__PURE__ */ new Set([
18
+ "connection",
19
+ "keep-alive",
20
+ "proxy-connection",
21
+ "transfer-encoding",
22
+ "upgrade",
23
+ "host"
24
+ ]);
25
+ var toWebBody = (stream) => Readable.toWeb(stream);
26
+ var isDrop = (error) => error?.code === "MOCKINGBIRD_DROP";
27
+ var internalError = (error) => JSON.stringify({
28
+ error: {
29
+ type: "mockingbird_internal",
30
+ message: error instanceof Error ? error.message : String(error)
31
+ }
32
+ });
33
+ var pump = async (body, write, drained, closed) => {
34
+ const reader = body.getReader();
35
+ try {
36
+ for (; ; ) {
37
+ if (closed()) {
38
+ await reader.cancel().catch(() => void 0);
39
+ return;
40
+ }
41
+ const { done, value } = await reader.read();
42
+ if (done) return;
43
+ if (!write(value)) await drained();
44
+ }
45
+ } finally {
46
+ reader.releaseLock();
47
+ }
48
+ };
49
+ var handleHttp1 = async (api, base, req, res) => {
50
+ const method = req.method ?? "GET";
51
+ const url = new URL((req.url ?? "/").replace(/^\/+/, "/"), `http://${req.headers.host ?? base}`);
52
+ const headers = new Headers();
53
+ for (const [name, value] of Object.entries(req.headers)) {
54
+ if (value === void 0) continue;
55
+ for (const each of Array.isArray(value) ? value : [value]) headers.append(name, each);
56
+ }
57
+ const aborted = new AbortController();
58
+ res.once("close", () => {
59
+ if (!res.writableFinished) aborted.abort();
60
+ });
61
+ const hasBody = method !== "GET" && method !== "HEAD";
62
+ const request = new Request(url, {
63
+ method,
64
+ headers,
65
+ signal: aborted.signal,
66
+ ...hasBody ? { body: toWebBody(req), duplex: "half" } : {}
67
+ });
68
+ let response;
69
+ try {
70
+ response = await api.fetch(request);
71
+ } catch (error) {
72
+ if (isDrop(error)) {
73
+ req.socket.destroy();
74
+ return;
75
+ }
76
+ res.writeHead(500, { "content-type": "application/json" });
77
+ res.end(internalError(error));
78
+ return;
79
+ }
80
+ const out = Object.fromEntries(response.headers);
81
+ const cookies = response.headers.getSetCookie();
82
+ if (cookies.length > 0) out["set-cookie"] = cookies;
83
+ res.writeHead(response.status, out);
84
+ if (!response.body) {
85
+ res.end();
86
+ return;
87
+ }
88
+ res.flushHeaders();
89
+ try {
90
+ await pump(
91
+ response.body,
92
+ (chunk) => res.write(chunk),
93
+ () => new Promise((resolve) => res.once("drain", resolve)),
94
+ () => res.destroyed
95
+ );
96
+ res.end();
97
+ } catch {
98
+ res.destroy();
99
+ }
100
+ };
101
+ var handleHttp2 = async (api, base, stream, incoming) => {
102
+ const method = String(incoming[":method"] ?? "GET");
103
+ const authority = String(incoming[":authority"] ?? incoming.host ?? base);
104
+ const url = new URL(String(incoming[":path"] ?? "/").replace(/^\/+/, "/"), `http://${authority}`);
105
+ const headers = new Headers();
106
+ for (const [name, value] of Object.entries(incoming)) {
107
+ if (name.startsWith(":") || value === void 0) continue;
108
+ for (const each of Array.isArray(value) ? value : [value]) headers.append(name, String(each));
109
+ }
110
+ const aborted = new AbortController();
111
+ stream.once("close", () => {
112
+ if (!stream.writableFinished) aborted.abort();
113
+ });
114
+ stream.on("error", () => void 0);
115
+ const hasBody = method !== "GET" && method !== "HEAD";
116
+ const request = new Request(url, {
117
+ method,
118
+ headers,
119
+ signal: aborted.signal,
120
+ ...hasBody ? { body: toWebBody(stream), duplex: "half" } : {}
121
+ });
122
+ let response;
123
+ try {
124
+ response = await api.fetch(request);
125
+ } catch (error) {
126
+ if (isDrop(error)) {
127
+ stream.close(http2Constants.NGHTTP2_INTERNAL_ERROR);
128
+ return;
129
+ }
130
+ if (stream.destroyed) return;
131
+ stream.respond({ ":status": 500, "content-type": "application/json" });
132
+ stream.end(internalError(error));
133
+ return;
134
+ }
135
+ if (stream.destroyed || stream.closed) return;
136
+ const out = { ":status": response.status };
137
+ for (const [name, value] of response.headers) {
138
+ if (!CONNECTION_HEADERS.has(name)) out[name] = value;
139
+ }
140
+ if (!response.body) {
141
+ stream.respond(out, { endStream: true });
142
+ return;
143
+ }
144
+ stream.respond(out);
145
+ try {
146
+ await pump(
147
+ response.body,
148
+ (chunk) => stream.write(chunk),
149
+ () => new Promise((resolve) => stream.once("drain", resolve)),
150
+ () => stream.destroyed || stream.closed
151
+ );
152
+ if (!stream.destroyed) stream.end();
153
+ } catch {
154
+ if (!stream.destroyed) stream.close(http2Constants.NGHTTP2_INTERNAL_ERROR);
155
+ }
156
+ };
157
+ var listenInternal = (server) => new Promise((resolve, reject) => {
158
+ ;
159
+ server.once("error", reject);
160
+ server.listen(0, "127.0.0.1", () => resolve(server.address().port));
161
+ });
162
+ var listenH2c = async (api, options = {}) => {
163
+ const host = options.host ?? "127.0.0.1";
164
+ const shown = host.includes(":") ? `[${host}]` : host;
165
+ let base = `${shown}:${options.port ?? 0}`;
166
+ const h1 = createHttp1Server((req, res) => {
167
+ void handleHttp1(api, base, req, res);
168
+ });
169
+ const h2 = createHttp2Server();
170
+ h2.on("stream", (stream, headers) => {
171
+ void handleHttp2(api, base, stream, headers);
172
+ });
173
+ h2.on("sessionError", () => void 0);
174
+ const h1Port = await listenInternal(h1);
175
+ const h2Port = await listenInternal(h2);
176
+ const sockets = /* @__PURE__ */ new Set();
177
+ const track = (socket) => {
178
+ sockets.add(socket);
179
+ socket.once("close", () => sockets.delete(socket));
180
+ };
181
+ const front = createNetServer((socket) => {
182
+ track(socket);
183
+ socket.setNoDelay(true);
184
+ let seen = Buffer.alloc(0);
185
+ const onData = (chunk) => {
186
+ seen = Buffer.concat([seen, chunk]);
187
+ if (seen.length < 3) return;
188
+ socket.off("data", onData);
189
+ socket.pause();
190
+ const http2 = seen.subarray(0, 3).toString("latin1") === PREFACE.slice(0, 3);
191
+ const upstream = connect(http2 ? h2Port : h1Port, "127.0.0.1");
192
+ track(upstream);
193
+ upstream.setNoDelay(true);
194
+ const destroy = () => {
195
+ socket.destroy();
196
+ upstream.destroy();
197
+ };
198
+ socket.on("error", destroy);
199
+ upstream.on("error", destroy);
200
+ socket.once("close", () => upstream.destroy());
201
+ upstream.once("close", () => socket.destroy());
202
+ upstream.write(seen);
203
+ socket.pipe(upstream);
204
+ upstream.pipe(socket);
205
+ socket.resume();
206
+ };
207
+ socket.on("data", onData);
208
+ socket.on("error", () => socket.destroy());
209
+ });
210
+ await new Promise((resolve, reject) => {
211
+ front.once("error", reject);
212
+ front.listen(options.port ?? 0, host, () => resolve());
213
+ });
214
+ const port = front.address().port;
215
+ base = `${shown}:${port}`;
216
+ const closeServer = (server) => new Promise((resolve) => {
217
+ server.close(() => resolve());
218
+ });
219
+ return {
220
+ url: `http://${shown}:${port}`,
221
+ port,
222
+ host,
223
+ close: async () => {
224
+ const closing = [
225
+ closeServer(front),
226
+ closeServer(h1),
227
+ closeServer(h2)
228
+ ];
229
+ for (const socket of sockets) socket.destroy();
230
+ h1.closeAllConnections?.();
231
+ await Promise.all(closing);
232
+ }
233
+ };
234
+ };
235
+
236
+ // src/server.ts
237
+ var DEFAULT_PORT = 8796;
238
+ var createServer = async (options = {}) => {
239
+ const { port, host, ...rest } = options;
240
+ const runtime = createRuntime(rest);
241
+ const listening = await listenH2c(runtime, {
242
+ port: port ?? 0,
243
+ ...host !== void 0 ? { host } : {}
244
+ });
245
+ return { ...listening, runtime };
246
+ };
247
+ var text = (value) => typeof value === "string" ? value : void 0;
248
+ var loadScripts = async (path) => {
249
+ const { readFile } = await import("node:fs/promises");
250
+ const raw = JSON.parse(await readFile(path, "utf8"));
251
+ const list = Array.isArray(raw) ? raw : raw.scripts;
252
+ if (!Array.isArray(list)) throw new Error(`${path}: expected {"scripts": [...]}`);
253
+ return list.map((each, index) => {
254
+ const parsed = parseScript(each, index);
255
+ if (typeof parsed === "string") throw new Error(`${path}: ${parsed}`);
256
+ return parsed;
257
+ });
258
+ };
259
+ var serveTarget = {
260
+ name: "bedrock",
261
+ defaultPort: DEFAULT_PORT,
262
+ options: {
263
+ scripts: {
264
+ type: "string",
265
+ value: "<file.json>",
266
+ description: 'Scripts every namespace starts with ({"scripts": [...]}, as PUT /__admin/scripts)'
267
+ },
268
+ "default-text": {
269
+ type: "string",
270
+ value: "<text>",
271
+ description: 'What an unscripted chat call answers (default "OK.")'
272
+ }
273
+ },
274
+ create: async (values, common) => {
275
+ const scriptsPath = text(values.scripts);
276
+ const defaultText = text(values["default-text"]);
277
+ return createRuntime({
278
+ ...scriptsPath ? { scripts: await loadScripts(scriptsPath) } : {},
279
+ ...defaultText !== void 0 ? { settings: { defaultText } } : {},
280
+ ...common.adminKey !== void 0 ? { adminKey: common.adminKey } : {},
281
+ ...common.seed !== void 0 ? { seed: common.seed } : {},
282
+ ...common.onLog ? { onLog: common.onLog } : {}
283
+ });
284
+ },
285
+ banner: () => [
286
+ "point the app at it: AWS_ENDPOINT_URL_BEDROCK_RUNTIME / AWS_ENDPOINT_URL_BEDROCK_AGENTCORE = this URL",
287
+ "protocols: h2c (prior knowledge) and HTTP/1.1 on the same port",
288
+ "namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<AWS_ACCESS_KEY_ID>: <ns>}",
289
+ "scripts: PUT /__admin/scripts {scripts: [{id, match, turns}]}"
290
+ ]
291
+ };
292
+
293
+ export {
294
+ listenH2c,
295
+ DEFAULT_PORT,
296
+ createServer,
297
+ serveTarget
298
+ };
299
+ //# sourceMappingURL=chunk-BMGWOFIJ.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/h2c.ts", "../src/server.ts"],
4
+ "sourcesContent": ["/// <reference types=\"node\" />\n/**\n * One port that speaks both cleartext HTTP/2 (prior knowledge, \"h2c\") and HTTP/1.1.\n *\n * AWS SDK v3 clients for Bedrock Runtime, Polly and Transcribe Streaming default to\n * `NodeHttp2Handler`, so against an `http://` endpoint they open an h2c connection and\n * send the HTTP/2 connection preface straight away; bidirectional operations (Nova Sonic,\n * `StartSpeechSynthesisStream`, `StartStreamTranscription`) need HTTP/2 duplex. The AI SDK,\n * AgentCore and Transcribe batch clients speak HTTP/1.1. A front `node:net` server reads\n * the first bytes of each connection and hands it to an internal HTTP/2 or HTTP/1.1\n * server; both turn requests into Fetch `Request`s whose bodies stream in as they arrive,\n * and stream responses back chunk by chunk.\n */\nimport {\n createServer as createHttp1Server,\n type IncomingMessage,\n type ServerResponse,\n} from \"node:http\"\nimport {\n createServer as createHttp2Server,\n constants as http2Constants,\n type IncomingHttpHeaders,\n type ServerHttp2Stream,\n} from \"node:http2\"\nimport { type AddressInfo, connect, createServer as createNetServer, type Socket } from \"node:net\"\nimport { Readable } from \"node:stream\"\nimport type { FetchAPI } from \"@crvouga/mockingbird-core\"\n\nexport type H2cListenOptions = {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`. */\n host?: string\n}\n\n/** A running dual-protocol listener. */\nexport type H2cListening = {\n url: string\n port: number\n host: string\n close(): Promise<void>\n}\n\nconst PREFACE = \"PRI * HTTP/2.0\"\n\n/** Headers HTTP/2 forbids on a response (RFC 9113 \u00A78.2.2). */\nconst CONNECTION_HEADERS = new Set([\n \"connection\",\n \"keep-alive\",\n \"proxy-connection\",\n \"transfer-encoding\",\n \"upgrade\",\n \"host\",\n])\n\nconst toWebBody = (stream: Readable): ReadableStream<Uint8Array> =>\n Readable.toWeb(stream) as unknown as ReadableStream<Uint8Array>\n\nconst isDrop = (error: unknown) => (error as { code?: string } | null)?.code === \"MOCKINGBIRD_DROP\"\n\nconst internalError = (error: unknown) =>\n JSON.stringify({\n error: {\n type: \"mockingbird_internal\",\n message: error instanceof Error ? error.message : String(error),\n },\n })\n\n/** Write a Fetch response body to a Node writable, respecting backpressure. */\nconst pump = async (\n body: ReadableStream<Uint8Array>,\n write: (chunk: Uint8Array) => boolean,\n drained: () => Promise<void>,\n closed: () => boolean,\n): Promise<void> => {\n const reader = body.getReader()\n try {\n for (;;) {\n if (closed()) {\n await reader.cancel().catch(() => undefined)\n return\n }\n const { done, value } = await reader.read()\n if (done) return\n if (!write(value)) await drained()\n }\n } finally {\n reader.releaseLock()\n }\n}\n\nconst handleHttp1 = async (\n api: FetchAPI,\n base: string,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> => {\n const method = req.method ?? \"GET\"\n const url = new URL((req.url ?? \"/\").replace(/^\\/+/, \"/\"), `http://${req.headers.host ?? base}`)\n const headers = new Headers()\n for (const [name, value] of Object.entries(req.headers)) {\n if (value === undefined) continue\n for (const each of Array.isArray(value) ? value : [value]) headers.append(name, each)\n }\n const aborted = new AbortController()\n res.once(\"close\", () => {\n if (!res.writableFinished) aborted.abort()\n })\n const hasBody = method !== \"GET\" && method !== \"HEAD\"\n const request = new Request(url, {\n method,\n headers,\n signal: aborted.signal,\n ...(hasBody ? { body: toWebBody(req), duplex: \"half\" } : {}),\n } as RequestInit)\n let response: Response\n try {\n response = await api.fetch(request)\n } catch (error) {\n if (isDrop(error)) {\n req.socket.destroy()\n return\n }\n res.writeHead(500, { \"content-type\": \"application/json\" })\n res.end(internalError(error))\n return\n }\n const out: Record<string, string | string[]> = Object.fromEntries(response.headers)\n const cookies = response.headers.getSetCookie()\n if (cookies.length > 0) out[\"set-cookie\"] = cookies\n res.writeHead(response.status, out)\n if (!response.body) {\n res.end()\n return\n }\n res.flushHeaders()\n try {\n await pump(\n response.body,\n (chunk) => res.write(chunk),\n () => new Promise((resolve) => res.once(\"drain\", resolve)),\n () => res.destroyed,\n )\n res.end()\n } catch {\n res.destroy()\n }\n}\n\nconst handleHttp2 = async (\n api: FetchAPI,\n base: string,\n stream: ServerHttp2Stream,\n incoming: IncomingHttpHeaders,\n): Promise<void> => {\n const method = String(incoming[\":method\"] ?? \"GET\")\n const authority = String(incoming[\":authority\"] ?? incoming.host ?? base)\n const url = new URL(String(incoming[\":path\"] ?? \"/\").replace(/^\\/+/, \"/\"), `http://${authority}`)\n const headers = new Headers()\n for (const [name, value] of Object.entries(incoming)) {\n if (name.startsWith(\":\") || value === undefined) continue\n for (const each of Array.isArray(value) ? value : [value]) headers.append(name, String(each))\n }\n const aborted = new AbortController()\n stream.once(\"close\", () => {\n if (!stream.writableFinished) aborted.abort()\n })\n stream.on(\"error\", () => undefined)\n const hasBody = method !== \"GET\" && method !== \"HEAD\"\n const request = new Request(url, {\n method,\n headers,\n signal: aborted.signal,\n ...(hasBody ? { body: toWebBody(stream), duplex: \"half\" } : {}),\n } as RequestInit)\n let response: Response\n try {\n response = await api.fetch(request)\n } catch (error) {\n if (isDrop(error)) {\n stream.close(http2Constants.NGHTTP2_INTERNAL_ERROR)\n return\n }\n if (stream.destroyed) return\n stream.respond({ \":status\": 500, \"content-type\": \"application/json\" })\n stream.end(internalError(error))\n return\n }\n if (stream.destroyed || stream.closed) return\n const out: Record<string, string | string[] | number> = { \":status\": response.status }\n for (const [name, value] of response.headers) {\n if (!CONNECTION_HEADERS.has(name)) out[name] = value\n }\n if (!response.body) {\n stream.respond(out, { endStream: true })\n return\n }\n stream.respond(out)\n try {\n await pump(\n response.body,\n (chunk) => stream.write(chunk),\n () => new Promise((resolve) => stream.once(\"drain\", resolve)),\n () => stream.destroyed || stream.closed,\n )\n if (!stream.destroyed) stream.end()\n } catch {\n if (!stream.destroyed) stream.close(http2Constants.NGHTTP2_INTERNAL_ERROR)\n }\n}\n\nconst listenInternal = (server: {\n listen: (...args: unknown[]) => unknown\n once: (...args: never[]) => unknown\n address(): AddressInfo | string | null\n}) =>\n new Promise<number>((resolve, reject) => {\n ;(server.once as (event: string, listener: (error: Error) => void) => void)(\"error\", reject)\n server.listen(0, \"127.0.0.1\", () => resolve((server.address() as AddressInfo).port))\n })\n\n/**\n * Serve `api` over h2c and HTTP/1.1 on one port. Each accepted connection is sniffed for\n * the HTTP/2 preface and relayed to an internal loopback server for that protocol.\n */\nexport const listenH2c = async (\n api: FetchAPI,\n options: H2cListenOptions = {},\n): Promise<H2cListening> => {\n const host = options.host ?? \"127.0.0.1\"\n const shown = host.includes(\":\") ? `[${host}]` : host\n let base = `${shown}:${options.port ?? 0}`\n const h1 = createHttp1Server((req, res) => {\n void handleHttp1(api, base, req, res)\n })\n const h2 = createHttp2Server()\n h2.on(\"stream\", (stream, headers) => {\n void handleHttp2(api, base, stream, headers)\n })\n h2.on(\"sessionError\", () => undefined)\n const h1Port = await listenInternal(h1 as never)\n const h2Port = await listenInternal(h2 as never)\n const sockets = new Set<Socket>()\n const track = (socket: Socket) => {\n sockets.add(socket)\n socket.once(\"close\", () => sockets.delete(socket))\n }\n const front = createNetServer((socket) => {\n track(socket)\n socket.setNoDelay(true)\n let seen = Buffer.alloc(0)\n const onData = (chunk: Buffer) => {\n seen = Buffer.concat([seen, chunk])\n // \"PRI\" opens only the HTTP/2 preface; no HTTP/1.1 method starts that way.\n if (seen.length < 3) return\n socket.off(\"data\", onData)\n socket.pause()\n const http2 = seen.subarray(0, 3).toString(\"latin1\") === PREFACE.slice(0, 3)\n const upstream = connect(http2 ? h2Port : h1Port, \"127.0.0.1\")\n track(upstream)\n upstream.setNoDelay(true)\n const destroy = () => {\n socket.destroy()\n upstream.destroy()\n }\n socket.on(\"error\", destroy)\n upstream.on(\"error\", destroy)\n socket.once(\"close\", () => upstream.destroy())\n upstream.once(\"close\", () => socket.destroy())\n upstream.write(seen)\n socket.pipe(upstream)\n upstream.pipe(socket)\n socket.resume()\n }\n socket.on(\"data\", onData)\n socket.on(\"error\", () => socket.destroy())\n })\n await new Promise<void>((resolve, reject) => {\n front.once(\"error\", reject)\n front.listen(options.port ?? 0, host, () => resolve())\n })\n const port = (front.address() as AddressInfo).port\n base = `${shown}:${port}`\n const closeServer = (server: { close: (cb: (error?: Error) => void) => unknown }) =>\n new Promise<void>((resolve) => {\n server.close(() => resolve())\n })\n return {\n url: `http://${shown}:${port}`,\n port,\n host,\n close: async () => {\n const closing = [\n closeServer(front as never),\n closeServer(h1 as never),\n closeServer(h2 as never),\n ]\n for (const socket of sockets) socket.destroy()\n h1.closeAllConnections?.()\n await Promise.all(closing)\n },\n }\n}\n", "/// <reference types=\"node\" />\nimport type { ServeTarget } from \"@crvouga/mockingbird-adapter-node\"\nimport { type H2cListening, listenH2c } from \"./h2c.js\"\nimport { type BedrockRuntime, type BedrockRuntimeOptions, createRuntime } from \"./runtime.js\"\nimport { parseScript, type Script } from \"./scripts.js\"\n\n/** Port `mockingbird-bedrock serve` listens on when none is given. */\nexport const DEFAULT_PORT = 8796\n\nexport type BedrockServerOptions = BedrockRuntimeOptions & {\n /** Default `0`: the OS picks a free port. */\n port?: number\n /** Default `127.0.0.1`. */\n host?: string\n}\n\nexport type BedrockServer = H2cListening & { runtime: BedrockRuntime }\n\n/**\n * Serve the Bedrock mock on one port that speaks both h2c (the AWS SDK's default\n * `NodeHttp2Handler`, and Nova Sonic's duplex stream) and HTTP/1.1 (the AI SDK, AgentCore).\n */\nexport const createServer = async (options: BedrockServerOptions = {}): Promise<BedrockServer> => {\n const { port, host, ...rest } = options\n const runtime = createRuntime(rest)\n const listening = await listenH2c(runtime, {\n port: port ?? 0,\n ...(host !== undefined ? { host } : {}),\n })\n return { ...listening, runtime }\n}\n\nconst text = (value: string | boolean | undefined) =>\n typeof value === \"string\" ? value : undefined\n\n/** Scripts from `--scripts <file.json>` (Node only). */\nconst loadScripts = async (path: string): Promise<Script[]> => {\n const { readFile } = await import(\"node:fs/promises\")\n const raw = JSON.parse(await readFile(path, \"utf8\")) as unknown\n const list = Array.isArray(raw) ? raw : (raw as { scripts?: unknown[] }).scripts\n if (!Array.isArray(list)) throw new Error(`${path}: expected {\"scripts\": [...]}`)\n return list.map((each, index) => {\n const parsed = parseScript(each, index)\n if (typeof parsed === \"string\") throw new Error(`${path}: ${parsed}`)\n return parsed\n })\n}\n\n/**\n * How `serve` builds the Bedrock mock from flags. `serve --config` in another service's CLI\n * listens over HTTP/1.1 only; `mockingbird-bedrock serve` listens with h2c as well.\n */\nexport const serveTarget: ServeTarget = {\n name: \"bedrock\",\n defaultPort: DEFAULT_PORT,\n options: {\n scripts: {\n type: \"string\",\n value: \"<file.json>\",\n description:\n 'Scripts every namespace starts with ({\"scripts\": [...]}, as PUT /__admin/scripts)',\n },\n \"default-text\": {\n type: \"string\",\n value: \"<text>\",\n description: 'What an unscripted chat call answers (default \"OK.\")',\n },\n },\n create: async (values, common) => {\n const scriptsPath = text(values.scripts)\n const defaultText = text(values[\"default-text\"])\n return createRuntime({\n ...(scriptsPath ? { scripts: await loadScripts(scriptsPath) } : {}),\n ...(defaultText !== undefined ? { settings: { defaultText } } : {}),\n ...(common.adminKey !== undefined ? { adminKey: common.adminKey } : {}),\n ...(common.seed !== undefined ? { seed: common.seed } : {}),\n ...(common.onLog ? { onLog: common.onLog } : {}),\n }) as never\n },\n banner: () => [\n \"point the app at it: AWS_ENDPOINT_URL_BEDROCK_RUNTIME / AWS_ENDPOINT_URL_BEDROCK_AGENTCORE = this URL\",\n \"protocols: h2c (prior knowledge) and HTTP/1.1 on the same port\",\n \"namespaces: x-mockingbird-namespace, /ns/<name>/\u2026, or PUT /__admin/credentials {<AWS_ACCESS_KEY_ID>: <ns>}\",\n \"scripts: PUT /__admin/scripts {scripts: [{id, match, turns}]}\",\n ],\n}\n\nexport type { H2cListening, H2cListenOptions } from \"./h2c.js\"\nexport { listenH2c } from \"./h2c.js\"\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }