@crvouga/mockingbird-service-bedrock 3.1.0 → 3.3.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 +14 -2
- package/DISCOVERY.md +23 -0
- package/README.md +27 -4
- package/dist/chunk-6FWIT6U7.js +7606 -0
- package/dist/chunk-6FWIT6U7.js.map +7 -0
- package/dist/{chunk-LTTTTWN5.js → chunk-GBUDLGDK.js} +3 -2
- package/dist/{chunk-LTTTTWN5.js.map → chunk-GBUDLGDK.js.map} +2 -2
- package/dist/cli.js +142 -28
- package/dist/cli.js.map +3 -3
- package/dist/index.d.ts +435 -121
- package/dist/index.js +1 -1
- package/dist/server.d.ts +819 -188
- package/dist/server.js +2 -2
- package/package.json +3 -2
- package/dist/chunk-AWLKTLE4.js +0 -5726
- package/dist/chunk-AWLKTLE4.js.map +0 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createRuntime,
|
|
3
3
|
parseScript
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6FWIT6U7.js";
|
|
5
5
|
|
|
6
6
|
// src/h2c.ts
|
|
7
7
|
import {
|
|
@@ -279,6 +279,7 @@ var serveTarget = {
|
|
|
279
279
|
...defaultText !== void 0 ? { settings: { defaultText } } : {},
|
|
280
280
|
...common.adminPrefix !== void 0 ? { adminPrefix: common.adminPrefix } : {},
|
|
281
281
|
...common.adminKey !== void 0 ? { adminKey: common.adminKey } : {},
|
|
282
|
+
...common.adminControls !== void 0 ? { adminControls: common.adminControls } : {},
|
|
282
283
|
...common.seed !== void 0 ? { seed: common.seed } : {},
|
|
283
284
|
...common.onLog ? { onLog: common.onLog } : {}
|
|
284
285
|
});
|
|
@@ -297,4 +298,4 @@ export {
|
|
|
297
298
|
createServer,
|
|
298
299
|
serveTarget
|
|
299
300
|
};
|
|
300
|
-
//# sourceMappingURL=chunk-
|
|
301
|
+
//# sourceMappingURL=chunk-GBUDLGDK.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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.adminPrefix !== undefined ? { adminPrefix: common.adminPrefix } : {}),\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, /__admin/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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",
|
|
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.adminPrefix !== undefined ? { adminPrefix: common.adminPrefix } : {}),\n ...(common.adminKey !== undefined ? { adminKey: common.adminKey } : {}),\n ...(common.adminControls !== undefined ? { adminControls: common.adminControls } : {}),\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, /__admin/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
6
|
"names": []
|
|
7
7
|
}
|
package/dist/cli.js
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
import {
|
|
3
3
|
listenH2c,
|
|
4
4
|
serveTarget
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GBUDLGDK.js";
|
|
6
6
|
import {
|
|
7
7
|
createClock,
|
|
8
|
+
normalizeBootControls,
|
|
8
9
|
parseDuration,
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
parseFaultHeader,
|
|
11
|
+
resolveAdminPrefix,
|
|
12
|
+
restoreClock
|
|
13
|
+
} from "./chunk-6FWIT6U7.js";
|
|
11
14
|
|
|
12
15
|
// ../../adapters/node/dist/cli.js
|
|
13
16
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
@@ -54,6 +57,8 @@ var serve = async (api, options = {}) => {
|
|
|
54
57
|
req.socket.destroy();
|
|
55
58
|
return;
|
|
56
59
|
}
|
|
60
|
+
if (aborted.signal.aborted)
|
|
61
|
+
return;
|
|
57
62
|
res.writeHead(500, { "content-type": "application/json" });
|
|
58
63
|
res.end(JSON.stringify({
|
|
59
64
|
error: {
|
|
@@ -85,6 +90,7 @@ var serve = async (api, options = {}) => {
|
|
|
85
90
|
}
|
|
86
91
|
res.end();
|
|
87
92
|
} catch {
|
|
93
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
88
94
|
res.destroy();
|
|
89
95
|
} finally {
|
|
90
96
|
reader.releaseLock();
|
|
@@ -167,10 +173,12 @@ var httpChild = async (target, entry, options) => {
|
|
|
167
173
|
adminPrefix: resolveAdminPrefix(entry.adminPrefix),
|
|
168
174
|
adminKey: entry.adminKey,
|
|
169
175
|
seed: entry.seed,
|
|
170
|
-
onLog: options.onLog
|
|
176
|
+
onLog: options.onLog,
|
|
177
|
+
...entry.adminControls !== void 0 ? { adminControls: entry.adminControls } : {},
|
|
178
|
+
...entry.fixtures !== void 0 ? { fixtures: entry.fixtures } : {},
|
|
179
|
+
...entry.baseUrl !== void 0 ? { baseUrl: entry.baseUrl } : {}
|
|
171
180
|
});
|
|
172
181
|
const adminPrefix = resolveAdminPrefix(entry.adminPrefix);
|
|
173
|
-
runtime.isolateNamespaces();
|
|
174
182
|
const locked = /* @__PURE__ */ new Set();
|
|
175
183
|
const active = /* @__PURE__ */ new Map();
|
|
176
184
|
const api = {
|
|
@@ -432,19 +440,12 @@ async function startFleet(config, options) {
|
|
|
432
440
|
return json(400, { error: "freeze must be boolean" });
|
|
433
441
|
const response2 = await transaction(namespace, selected, async (_name, child) => {
|
|
434
442
|
const childClock = child.clock(namespace);
|
|
435
|
-
childClock
|
|
436
|
-
childClock.set(target);
|
|
437
|
-
if (!frozen)
|
|
438
|
-
childClock.unfreeze();
|
|
443
|
+
restoreClock(childClock, { now: target, frozen });
|
|
439
444
|
await child.settle?.(namespace);
|
|
440
445
|
return childClock.state();
|
|
441
446
|
});
|
|
442
|
-
if (response2.ok)
|
|
443
|
-
current
|
|
444
|
-
current.set(target);
|
|
445
|
-
if (!frozen)
|
|
446
|
-
current.unfreeze();
|
|
447
|
-
}
|
|
447
|
+
if (response2.ok)
|
|
448
|
+
restoreClock(current, { now: target, frozen });
|
|
448
449
|
return response2;
|
|
449
450
|
}
|
|
450
451
|
if (!route)
|
|
@@ -463,13 +464,8 @@ async function startFleet(config, options) {
|
|
|
463
464
|
return json(404, { error: "no snapshot in this namespace" });
|
|
464
465
|
const restored = new Map([...point.values].map(([name]) => [name, children.get(name)]));
|
|
465
466
|
const response2 = await transaction(namespace, restored, async (name, child) => child.restore(namespace, point.values.get(name)));
|
|
466
|
-
if (response2.ok)
|
|
467
|
-
|
|
468
|
-
current.freeze();
|
|
469
|
-
current.set(point.clock.now);
|
|
470
|
-
if (!point.clock.frozen)
|
|
471
|
-
current.unfreeze();
|
|
472
|
-
}
|
|
467
|
+
if (response2.ok)
|
|
468
|
+
restoreClock(clock(namespace), point.clock);
|
|
473
469
|
return response2;
|
|
474
470
|
}
|
|
475
471
|
const snapshotId = randomUUID();
|
|
@@ -495,6 +491,8 @@ async function startFleet(config, options) {
|
|
|
495
491
|
for (const [name, entry] of Object.entries(config.services)) {
|
|
496
492
|
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name))
|
|
497
493
|
throw new Error("invalid service name");
|
|
494
|
+
if (config.adminControls !== void 0 || entry.adminControls !== void 0)
|
|
495
|
+
normalizeBootControls({ ...config.adminControls, ...entry.adminControls }, `services.${name}.adminControls`);
|
|
498
496
|
try {
|
|
499
497
|
const target = await options.load(name);
|
|
500
498
|
if (entry.protocol && entry.protocol !== ("protocol" in target ? target.protocol : "http"))
|
|
@@ -503,7 +501,9 @@ async function startFleet(config, options) {
|
|
|
503
501
|
const child = "start" in target ? await target.start(entry) : await httpChild(target, {
|
|
504
502
|
...entry,
|
|
505
503
|
adminPrefix: resolveAdminPrefix(entry.adminPrefix ?? adminPrefix),
|
|
506
|
-
...adminKey !== void 0 ? { adminKey } : {}
|
|
504
|
+
...adminKey !== void 0 ? { adminKey } : {},
|
|
505
|
+
// Fleet-wide controls, with the service's own keys replacing them.
|
|
506
|
+
...config.adminControls !== void 0 || entry.adminControls !== void 0 ? { adminControls: { ...config.adminControls, ...entry.adminControls } } : {}
|
|
507
507
|
}, options);
|
|
508
508
|
children.set(name, child);
|
|
509
509
|
if (!await child.ready())
|
|
@@ -636,7 +636,51 @@ var COMMON_SERVE_OPTIONS = {
|
|
|
636
636
|
value: "<key>",
|
|
637
637
|
description: "Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)"
|
|
638
638
|
},
|
|
639
|
-
seed: {
|
|
639
|
+
seed: {
|
|
640
|
+
type: "string",
|
|
641
|
+
value: "<seed>",
|
|
642
|
+
description: "Seed for every random choice (env MOCKINGBIRD_SEED)"
|
|
643
|
+
},
|
|
644
|
+
"admin-controls": {
|
|
645
|
+
type: "string",
|
|
646
|
+
value: "<json|file>",
|
|
647
|
+
description: "Admin controls to boot with, as JSON or a JSON file: clock, latency, faults, snapshot, namespaces (env MOCKINGBIRD_ADMIN_CONTROLS)"
|
|
648
|
+
},
|
|
649
|
+
clock: {
|
|
650
|
+
type: "string",
|
|
651
|
+
value: "<instant>",
|
|
652
|
+
description: "Start the clock at this instant (epoch ms or ISO-8601)"
|
|
653
|
+
},
|
|
654
|
+
freeze: { type: "boolean", description: "Freeze the clock: it moves only when told to" },
|
|
655
|
+
"auto-advance": {
|
|
656
|
+
type: "string",
|
|
657
|
+
value: "<duration>",
|
|
658
|
+
description: "Step the clock this much before every request (e.g. 1s)"
|
|
659
|
+
},
|
|
660
|
+
latency: {
|
|
661
|
+
type: "string",
|
|
662
|
+
value: "<duration>",
|
|
663
|
+
description: "Baseline response time for every request (e.g. 250ms)"
|
|
664
|
+
},
|
|
665
|
+
jitter: {
|
|
666
|
+
type: "string",
|
|
667
|
+
value: "<duration>",
|
|
668
|
+
description: "Seeded extra latency between 0 and this"
|
|
669
|
+
},
|
|
670
|
+
"virtual-time": {
|
|
671
|
+
type: "boolean",
|
|
672
|
+
description: "Spend latency, delays and timeouts on the mock clock instead of really waiting"
|
|
673
|
+
},
|
|
674
|
+
fault: {
|
|
675
|
+
type: "string",
|
|
676
|
+
value: "<fault>",
|
|
677
|
+
description: 'A fault to boot with, as in x-mockingbird-fault: 503, rate_limited, "status=503; delay=2s"'
|
|
678
|
+
},
|
|
679
|
+
snapshot: {
|
|
680
|
+
type: "string",
|
|
681
|
+
value: "<file>",
|
|
682
|
+
description: "Start from a snapshot document (GET /__admin/snapshots/:id)"
|
|
683
|
+
},
|
|
640
684
|
log: {
|
|
641
685
|
type: "string",
|
|
642
686
|
value: "<pretty|json|off>",
|
|
@@ -683,6 +727,68 @@ var formatLog = (format) => {
|
|
|
683
727
|
};
|
|
684
728
|
};
|
|
685
729
|
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
730
|
+
var readJsonFile = async (path, label) => {
|
|
731
|
+
try {
|
|
732
|
+
return JSON.parse(await readFile2(path, "utf8"));
|
|
733
|
+
} catch (error) {
|
|
734
|
+
throw new Error(`${label}: cannot read ${path}: ${error instanceof Error ? error.message : error}`);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
738
|
+
var resolveServeControls = async (values, env = process.env, base) => {
|
|
739
|
+
const source = asString(values["admin-controls"]) ?? env.MOCKINGBIRD_ADMIN_CONTROLS;
|
|
740
|
+
let document = isObject2(base) ? { ...base } : {};
|
|
741
|
+
if (source !== void 0 && source.trim() !== "") {
|
|
742
|
+
let parsed;
|
|
743
|
+
if (source.trim().startsWith("{")) {
|
|
744
|
+
try {
|
|
745
|
+
parsed = JSON.parse(source);
|
|
746
|
+
} catch {
|
|
747
|
+
throw new Error("--admin-controls: not valid JSON");
|
|
748
|
+
}
|
|
749
|
+
} else
|
|
750
|
+
parsed = await readJsonFile(source, "--admin-controls");
|
|
751
|
+
if (!isObject2(parsed))
|
|
752
|
+
throw new Error("--admin-controls: expected a JSON object");
|
|
753
|
+
document = { ...document, ...parsed };
|
|
754
|
+
}
|
|
755
|
+
const clock = isObject2(document.clock) ? { ...document.clock } : {};
|
|
756
|
+
const now = asString(values.clock);
|
|
757
|
+
if (now !== void 0)
|
|
758
|
+
clock.now = now;
|
|
759
|
+
if (values.freeze === true)
|
|
760
|
+
clock.frozen = true;
|
|
761
|
+
const autoAdvance = asString(values["auto-advance"]);
|
|
762
|
+
if (autoAdvance !== void 0)
|
|
763
|
+
clock.autoAdvance = autoAdvance;
|
|
764
|
+
if (Object.keys(clock).length > 0)
|
|
765
|
+
document.clock = clock;
|
|
766
|
+
const delay = asString(values.latency);
|
|
767
|
+
const jitter = asString(values.jitter);
|
|
768
|
+
if (delay !== void 0 || jitter !== void 0) {
|
|
769
|
+
const latency = isObject2(document.latency) ? { ...document.latency } : document.latency !== void 0 ? { delay: document.latency } : {};
|
|
770
|
+
if (delay !== void 0)
|
|
771
|
+
latency.delay = delay;
|
|
772
|
+
if (jitter !== void 0)
|
|
773
|
+
latency.jitter = jitter;
|
|
774
|
+
document.latency = latency;
|
|
775
|
+
}
|
|
776
|
+
if (values["virtual-time"] === true)
|
|
777
|
+
document.virtualTime = true;
|
|
778
|
+
const fault = asString(values.fault);
|
|
779
|
+
if (fault !== void 0)
|
|
780
|
+
document.faults = [
|
|
781
|
+
...Array.isArray(document.faults) ? document.faults : [],
|
|
782
|
+
parseFaultHeader(fault, "--fault")
|
|
783
|
+
];
|
|
784
|
+
const snapshot = asString(values.snapshot);
|
|
785
|
+
if (snapshot !== void 0)
|
|
786
|
+
document.snapshot = await readJsonFile(snapshot, "--snapshot");
|
|
787
|
+
if (Object.keys(document).length === 0)
|
|
788
|
+
return void 0;
|
|
789
|
+
normalizeBootControls(document, "adminControls");
|
|
790
|
+
return document;
|
|
791
|
+
};
|
|
686
792
|
var loadTarget = async (name, own) => {
|
|
687
793
|
if (name === own.name)
|
|
688
794
|
return own;
|
|
@@ -709,7 +815,8 @@ var start = async (target, values, config) => {
|
|
|
709
815
|
adminPrefix,
|
|
710
816
|
adminKey: config.adminKey,
|
|
711
817
|
seed: config.seed,
|
|
712
|
-
onLog: formatLog(config.log)
|
|
818
|
+
onLog: formatLog(config.log),
|
|
819
|
+
...config.adminControls !== void 0 ? { adminControls: config.adminControls } : {}
|
|
713
820
|
});
|
|
714
821
|
const listening = await listen(runtime, { port: config.port, host: config.host });
|
|
715
822
|
target.listening?.(listening);
|
|
@@ -749,6 +856,9 @@ var serveCommand = (target) => ({
|
|
|
749
856
|
const adminKey2 = config.adminKey ?? asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
750
857
|
if (adminKey2 !== void 0)
|
|
751
858
|
config.adminKey = adminKey2;
|
|
859
|
+
const adminControls = await resolveServeControls(values, process.env, config.adminControls);
|
|
860
|
+
if (adminControls !== void 0)
|
|
861
|
+
config.adminControls = adminControls;
|
|
752
862
|
const fleet = await startFleet(config, {
|
|
753
863
|
load: (name) => loadTarget(name, target),
|
|
754
864
|
onLog: formatLog(config.log ?? log),
|
|
@@ -774,10 +884,12 @@ var serveCommand = (target) => ({
|
|
|
774
884
|
}
|
|
775
885
|
const port = asString(values.port);
|
|
776
886
|
const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
777
|
-
const seed = asString(values.seed);
|
|
887
|
+
const seed = asString(values.seed) ?? process.env.MOCKINGBIRD_SEED;
|
|
778
888
|
let listening;
|
|
779
889
|
try {
|
|
890
|
+
const adminControls = await resolveServeControls(values);
|
|
780
891
|
listening = await start(target, values, {
|
|
892
|
+
...adminControls !== void 0 ? { adminControls } : {},
|
|
781
893
|
adminPrefix: resolveAdminPrefix(asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX),
|
|
782
894
|
port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
|
|
783
895
|
host: asString(values.host) ?? "127.0.0.1",
|
|
@@ -821,14 +933,16 @@ var serve2 = {
|
|
|
821
933
|
text(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX
|
|
822
934
|
);
|
|
823
935
|
const adminKey = text(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
824
|
-
const seed = text(values.seed);
|
|
936
|
+
const seed = text(values.seed) ?? process.env.MOCKINGBIRD_SEED;
|
|
825
937
|
const port = text(values.port);
|
|
826
938
|
try {
|
|
939
|
+
const adminControls = await resolveServeControls(values);
|
|
827
940
|
const runtime = await serveTarget.create(values, {
|
|
828
941
|
adminPrefix,
|
|
829
942
|
adminKey,
|
|
830
943
|
seed,
|
|
831
|
-
onLog: logger(format)
|
|
944
|
+
onLog: logger(format),
|
|
945
|
+
...adminControls !== void 0 ? { adminControls } : {}
|
|
832
946
|
});
|
|
833
947
|
const listening = await listenH2c(runtime, {
|
|
834
948
|
port: port === void 0 ? serveTarget.defaultPort : Number.parseInt(port, 10),
|