@copilotkit/aimock 1.46.0 → 1.47.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +12 -1
- package/README.md +4 -1
- package/dist/aws-event-stream.cjs +20 -23
- package/dist/aws-event-stream.cjs.map +1 -1
- package/dist/aws-event-stream.d.cts +0 -1
- package/dist/aws-event-stream.d.ts +0 -1
- package/dist/aws-event-stream.js +20 -24
- package/dist/aws-event-stream.js.map +1 -1
- package/dist/bedrock-converse.cjs +79 -82
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +80 -83
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +112 -121
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +113 -122
- package/dist/bedrock.js.map +1 -1
- package/dist/cohere.cjs +58 -106
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +60 -108
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/fixture-loader.cjs +103 -2
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +104 -4
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +63 -121
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +65 -123
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +242 -155
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +244 -157
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +400 -7
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +51 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +51 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +391 -8
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/mcp-fakes.d.cts.map +1 -1
- package/dist/messages.cjs +159 -141
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +161 -143
- package/dist/messages.js.map +1 -1
- package/dist/misbehavior.cjs +62 -3
- package/dist/misbehavior.cjs.map +1 -1
- package/dist/misbehavior.d.cts.map +1 -1
- package/dist/misbehavior.d.ts.map +1 -1
- package/dist/misbehavior.js +63 -4
- package/dist/misbehavior.js.map +1 -1
- package/dist/ndjson-writer.cjs +21 -23
- package/dist/ndjson-writer.cjs.map +1 -1
- package/dist/ndjson-writer.d.cts +0 -1
- package/dist/ndjson-writer.d.cts.map +1 -1
- package/dist/ndjson-writer.d.ts +0 -1
- package/dist/ndjson-writer.d.ts.map +1 -1
- package/dist/ndjson-writer.js +21 -24
- package/dist/ndjson-writer.js.map +1 -1
- package/dist/ollama.cjs +153 -147
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +155 -149
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-chat.cjs +1 -1
- package/dist/openrouter-chat.js +1 -1
- package/dist/raw-validation.cjs +177 -0
- package/dist/raw-validation.cjs.map +1 -0
- package/dist/raw-validation.js +176 -0
- package/dist/raw-validation.js.map +1 -0
- package/dist/response/deliver.cjs +144 -0
- package/dist/response/deliver.cjs.map +1 -0
- package/dist/response/deliver.js +143 -0
- package/dist/response/deliver.js.map +1 -0
- package/dist/response/frame-loop.cjs +102 -0
- package/dist/response/frame-loop.cjs.map +1 -0
- package/dist/response/frame-loop.js +100 -0
- package/dist/response/frame-loop.js.map +1 -0
- package/dist/response/lifecycle.cjs +223 -0
- package/dist/response/lifecycle.cjs.map +1 -0
- package/dist/response/lifecycle.js +211 -0
- package/dist/response/lifecycle.js.map +1 -0
- package/dist/response/proxy.cjs +71 -0
- package/dist/response/proxy.cjs.map +1 -0
- package/dist/response/proxy.js +70 -0
- package/dist/response/proxy.js.map +1 -0
- package/dist/response/raw.cjs +228 -0
- package/dist/response/raw.cjs.map +1 -0
- package/dist/response/raw.js +227 -0
- package/dist/response/raw.js.map +1 -0
- package/dist/response/timing.cjs +157 -0
- package/dist/response/timing.cjs.map +1 -0
- package/dist/response/timing.js +150 -0
- package/dist/response/timing.js.map +1 -0
- package/dist/responses.cjs +269 -148
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +270 -149
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +20 -4
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +7 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts +7 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +21 -5
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +89 -120
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +91 -122
- package/dist/server.js.map +1 -1
- package/dist/sse-writer.cjs +49 -30
- package/dist/sse-writer.cjs.map +1 -1
- package/dist/sse-writer.d.cts +0 -1
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts +0 -1
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/sse-writer.js +49 -31
- package/dist/sse-writer.js.map +1 -1
- package/dist/transcription.cjs +19 -33
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +20 -34
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +173 -10
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +173 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ws-framing.cjs +12 -6
- package/dist/ws-framing.cjs.map +1 -1
- package/dist/ws-framing.d.cts +2 -0
- package/dist/ws-framing.d.cts.map +1 -1
- package/dist/ws-framing.d.ts +2 -0
- package/dist/ws-framing.d.ts.map +1 -1
- package/dist/ws-framing.js +12 -6
- package/dist/ws-framing.js.map +1 -1
- package/dist/ws-gemini-live.cjs +120 -93
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.js +120 -93
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +80 -70
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +80 -70
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +72 -53
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +73 -54
- package/dist/ws-responses.js.map +1 -1
- package/package.json +1 -1
- package/skills/write-fixtures/SKILL.md +34 -2
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"source": {
|
|
10
10
|
"source": "npm",
|
|
11
11
|
"package": "@copilotkit/aimock",
|
|
12
|
-
"version": "^1.
|
|
12
|
+
"version": "^1.47.0"
|
|
13
13
|
},
|
|
14
14
|
"description": "Fixture authoring skill for @copilotkit/aimock — LLM, multimedia (image/TTS/transcription/video), MCP, A2A, AG-UI, vector, embeddings, structured output, sequential responses, streaming physics, record/replay, agent loop patterns, and debugging"
|
|
15
15
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# @copilotkit/aimock
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [1.47.0] - 2026-10-26
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Fixture `citations` serve sources on Responses, Chat Completions, Claude and Gemini (#550, #578)
|
|
8
|
+
- Fixture `files` serve generated images on Gemini and OpenAI Responses (#550, #578)
|
|
9
|
+
- Raw passthrough fixtures send provider SSE events or JSON exactly as written (#580)
|
|
10
|
+
- Opt-in `nonStreamingTimingAllProviders` delays non-streaming replies on every provider (#579)
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Client disconnects stop and journal every LLM reply; Docker image on Node 22.23.4 (#579, #576)
|
|
4
15
|
|
|
5
16
|
## [1.46.0] - 2026-10-20
|
|
6
17
|
|
package/README.md
CHANGED
|
@@ -91,13 +91,16 @@ Run them all on one port with `npx @copilotkit/aimock --config aimock.json`, or
|
|
|
91
91
|
- **[Native model catalogs and token counts](https://aimock.copilotkit.dev/claude-messages#model-catalog)** — Anthropic `GET /v1/models` (selected by `anthropic-version`, or `x-api-key` without `Authorization`; the list route needs `policy.modelCatalog: "native"` and otherwise stays the OpenAI list) and Gemini `GET /v1beta/models` list and detail routes with provider paging, built from fixture model IDs with synthetic metadata; `POST /v1/messages/count_tokens` and Gemini `:countTokens` return a deterministic estimate of about 1 token per 4 characters of messages (plus tool definitions), minimum 1. These routes never proxy or record
|
|
92
92
|
- **Context-Based Fixture Routing** — `X-AIMock-Context` header scopes fixtures per integration; fixtures with `match.context` only match requests carrying that context, fixtures without it remain shared
|
|
93
93
|
- **[Drift Detection](https://aimock.copilotkit.dev/drift-detection)** — Daily CI validation against real APIs
|
|
94
|
-
- **[Streaming Physics](https://aimock.copilotkit.dev/streaming-physics)** — Configurable `ttft`, `tps`, and `jitter`
|
|
94
|
+
- **[Streaming Physics](https://aimock.copilotkit.dev/streaming-physics)** — Configurable `ttft`, `tps`, and `jitter`; opt in with `nonStreamingTimingAllProviders: true` to give non-streaming replies the same timing on every LLM provider
|
|
95
|
+
- **[Client Disconnects](https://aimock.copilotkit.dev/error-injection#client-disconnects)** — When the client hangs up, every LLM provider (HTTP and WebSocket) stops sending and journals the request as `interrupted`, so you can test cancel and abort paths
|
|
95
96
|
- **[WebSocket APIs](https://aimock.copilotkit.dev/websocket)** — OpenAI Realtime (GA protocol with models: gpt-realtime, gpt-realtime-2, gpt-realtime-1.5, gpt-realtime-mini; transcription/translation via gpt-4o-transcribe, gpt-4o-mini-transcribe, whisper-1; image input; commentary phase), Responses WS, Gemini Live
|
|
96
97
|
- **[GPT-Live](https://aimock.copilotkit.dev/websocket#openai-live)** — Mock, record, and replay primary WebSocket sessions at `/v1/live/sessions` in client and managed modes, with fixture-backed audio and backend tool continuation. Saved fixtures replay offline; aimock does not synthesize speech. This voice surface is separate from OpenAI text-model family support; WebRTC/SDP and sideband connections are outside this scope.
|
|
97
98
|
- **[Prometheus Metrics](https://aimock.copilotkit.dev/metrics)** — Request counts, latencies, fixture match rates
|
|
98
99
|
- **[Docker + Helm](https://aimock.copilotkit.dev/docker)** — Container image and Helm chart for CI/CD
|
|
99
100
|
- **[Vitest & Jest Plugins](https://aimock.copilotkit.dev/test-plugins)** — Zero-config `useAimock()` with auto lifecycle and env patching
|
|
100
101
|
- **[Response Overrides](https://aimock.copilotkit.dev/fixtures)** — Control `id`, `model`, `usage`, `finishReason` in fixture responses
|
|
102
|
+
- **[Citations and Generated Files](https://aimock.copilotkit.dev/fixtures#citations-and-files)** — Fixture `citations` serve source citations on OpenAI Responses (URL and file), Chat Completions, Claude and Gemini; fixture `files` serve generated images on the Responses API and Gemini. Each uses the provider's own format, streaming and non-streaming
|
|
103
|
+
- **[Raw Passthrough](https://aimock.copilotkit.dev/raw-passthrough)** — A `raw` fixture sends provider SSE events or JSON exactly as written on any HTTP LLM wire, for fields aimock does not model yet; timing, chaos and disconnects still apply
|
|
101
104
|
- **[Ordered Blocks](https://aimock.copilotkit.dev/fixtures#ordered-blocks)** — A `blocks` array streams text and tool calls in any order (tool-first or interleaved); blocks-only fixtures are first-class, and the recorder captures order from genuinely tool-first/interleaved streams
|
|
102
105
|
- **[Namespaced and Custom Tool Calls](https://aimock.copilotkit.dev/responses-api#namespaced-and-custom-tool-calls)** — Responses API fixtures can return namespaced function calls and custom tool calls (`custom_tool_call`) over HTTP streaming, non-streaming and WebSocket, and can match on `toolNamespace`. This lets you script OpenAI Codex CLI's namespaced MCP and sub-agent tools and its freeform `apply_patch` tool (with the `responsesTools: "extended"` server option)
|
|
103
106
|
- **[Streaming Usage Chunks](https://aimock.copilotkit.dev/streaming-physics)** — `stream_options.include_usage` support emits a final chunk with token counts, matching OpenAI's streaming usage protocol
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
|
-
const
|
|
2
|
+
const require_frame_loop = require('./response/frame-loop.cjs');
|
|
3
3
|
let node_zlib = require("node:zlib");
|
|
4
4
|
|
|
5
5
|
//#region src/aws-event-stream.ts
|
|
@@ -87,32 +87,29 @@ function encodeEventStreamMessage(eventType, jsonPayload) {
|
|
|
87
87
|
*/
|
|
88
88
|
async function writeEventStream(res, events, options) {
|
|
89
89
|
const opts = options ?? {};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
chunkIndex++;
|
|
109
|
-
}
|
|
110
|
-
if (!res.writableEnded) res.end();
|
|
111
|
-
return true;
|
|
90
|
+
return require_frame_loop.runFrameLoop(require_frame_loop.httpSink(res), events, eventStreamCodec(), {
|
|
91
|
+
latency: opts.latency,
|
|
92
|
+
streamingProfile: opts.streamingProfile,
|
|
93
|
+
recordedTimings: opts.recordedTimings,
|
|
94
|
+
replaySpeed: opts.replaySpeed
|
|
95
|
+
}, {
|
|
96
|
+
signal: events.length > 0 ? opts.signal : void 0,
|
|
97
|
+
onChunkSent: opts.onChunkSent
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function eventStreamCodec(encode) {
|
|
101
|
+
return {
|
|
102
|
+
headers: {
|
|
103
|
+
"Content-Type": "application/vnd.amazon.eventstream",
|
|
104
|
+
"Transfer-Encoding": "chunked"
|
|
105
|
+
},
|
|
106
|
+
encode: encode ?? ((event) => encodeEventStreamMessage(event.eventType, event.payload))
|
|
107
|
+
};
|
|
112
108
|
}
|
|
113
109
|
|
|
114
110
|
//#endregion
|
|
115
111
|
exports.encodeEventStreamFrame = encodeEventStreamFrame;
|
|
116
112
|
exports.encodeEventStreamMessage = encodeEventStreamMessage;
|
|
113
|
+
exports.eventStreamCodec = eventStreamCodec;
|
|
117
114
|
exports.writeEventStream = writeEventStream;
|
|
118
115
|
//# sourceMappingURL=aws-event-stream.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aws-event-stream.cjs","names":["
|
|
1
|
+
{"version":3,"file":"aws-event-stream.cjs","names":["runFrameLoop","httpSink"],"sources":["../src/aws-event-stream.ts"],"sourcesContent":["/**\n * AWS Event Stream binary frame encoder.\n *\n * Implements the AWS binary event stream framing protocol used by Bedrock's\n * streaming (invoke-with-response-stream) endpoint. Each frame carries a set of\n * string headers and a raw-bytes payload, wrapped in a prelude with CRC32\n * checksums for integrity.\n *\n * Binary frame layout:\n * [total_length: 4B uint32-BE]\n * [headers_length: 4B uint32-BE]\n * [prelude_crc32: 4B CRC32 of first 8 bytes]\n * [headers: variable]\n * [payload: variable, raw JSON bytes]\n * [message_crc32: 4B CRC32 of entire frame minus last 4 bytes]\n */\n\nimport { crc32 } from \"node:zlib\";\nimport type * as http from \"node:http\";\nimport type { StreamingProfile, RecordedTimings } from \"./types.js\";\nimport { httpSink, runFrameLoop, type FrameCodec } from \"./response/frame-loop.js\";\n\n// ─── Header encoding ────────────────────────────────────────────────────────\n\nfunction encodeHeaders(headers: Record<string, string>): Buffer {\n const parts: Buffer[] = [];\n for (const [name, value] of Object.entries(headers)) {\n const nameBytes = Buffer.from(name, \"utf8\");\n const valueBytes = Buffer.from(value, \"utf8\");\n\n // name_length (1 byte) + name + type (1 byte, 7 = STRING) +\n // value_length (2 bytes BE) + value\n const header = Buffer.alloc(1 + nameBytes.length + 1 + 2 + valueBytes.length);\n let offset = 0;\n header.writeUInt8(nameBytes.length, offset);\n offset += 1;\n nameBytes.copy(header, offset);\n offset += nameBytes.length;\n header.writeUInt8(7, offset); // STRING type\n offset += 1;\n header.writeUInt16BE(valueBytes.length, offset);\n offset += 2;\n valueBytes.copy(header, offset);\n\n parts.push(header);\n }\n return Buffer.concat(parts);\n}\n\n// ─── Frame encoding ─────────────────────────────────────────────────────────\n\n/**\n * Encode a single AWS Event Stream binary frame with the given headers and\n * payload buffer.\n */\nexport function encodeEventStreamFrame(headers: Record<string, string>, payload: Buffer): Buffer {\n const headersBuffer = encodeHeaders(headers);\n const headersLength = headersBuffer.length;\n\n // prelude (8) + prelude_crc (4) + headers + payload + message_crc (4)\n const totalLength = 4 + 4 + 4 + headersLength + payload.length + 4;\n\n const frame = Buffer.alloc(totalLength);\n let offset = 0;\n\n // Prelude\n frame.writeUInt32BE(totalLength, offset);\n offset += 4;\n frame.writeUInt32BE(headersLength, offset);\n offset += 4;\n\n // Prelude CRC32 (covers first 8 bytes)\n const preludeCrc = crc32(frame.subarray(0, 8));\n frame.writeUInt32BE(preludeCrc >>> 0, offset);\n offset += 4;\n\n // Headers\n headersBuffer.copy(frame, offset);\n offset += headersLength;\n\n // Payload\n payload.copy(frame, offset);\n offset += payload.length;\n\n // Message CRC32 (covers entire frame minus last 4 bytes)\n const messageCrc = crc32(frame.subarray(0, totalLength - 4));\n frame.writeUInt32BE(messageCrc >>> 0, offset);\n\n return frame;\n}\n\n// ─── Convenience wrappers ───────────────────────────────────────────────────\n\n/**\n * Encode an event-stream message with standard AWS headers for a JSON event.\n *\n * Sets `:content-type` = `application/json`, `:event-type` = eventType,\n * `:message-type` = `event`.\n */\nexport function encodeEventStreamMessage(eventType: string, jsonPayload: object): Buffer {\n const headers: Record<string, string> = {\n \":content-type\": \"application/json\",\n \":event-type\": eventType,\n \":message-type\": \"event\",\n };\n const payload = Buffer.from(JSON.stringify(jsonPayload), \"utf8\");\n return encodeEventStreamFrame(headers, payload);\n}\n\n/**\n * Write a sequence of event-stream frames to an HTTP response with optional\n * timing control. Mirrors the writeSSEStream pattern from sse-writer.ts.\n *\n * Returns `true` when all events are written (including when the response\n * was already ended before writing began), or `false` if interrupted by\n * the provided abort signal.\n */\nexport async function writeEventStream(\n res: http.ServerResponse,\n events: Array<{ eventType: string; payload: object }>,\n options?: {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n },\n): Promise<boolean> {\n const opts = options ?? {};\n // Base contract (public API): with no frames, an aborted signal does not\n // stop the writer; it still finishes the stream (any trailer, then end())\n // and returns true. The frame loop returns false there, so pass it no\n // signal when there are no frames (it reads the signal only per frame and\n // after the loop).\n return runFrameLoop(\n httpSink(res),\n events,\n eventStreamCodec(),\n {\n latency: opts.latency,\n streamingProfile: opts.streamingProfile,\n recordedTimings: opts.recordedTimings,\n replaySpeed: opts.replaySpeed,\n },\n { signal: events.length > 0 ? opts.signal : undefined, onChunkSent: opts.onChunkSent },\n );\n}\n\n// Internal (not part of the public API): the AWS binary event-stream codec.\n// Each event becomes one complete frame, encoded by encodeEventStreamMessage\n// unless an `encode` argument replaces it. There is no prelude or trailer.\n// These are line comments, not JSDoc, so the bundled .d.ts does not attach\n// them to a public declaration.\nexport function eventStreamCodec<F extends { eventType: string; payload: object }>(\n encode?: (event: F) => Buffer,\n): FrameCodec<F> {\n return {\n headers: {\n \"Content-Type\": \"application/vnd.amazon.eventstream\",\n \"Transfer-Encoding\": \"chunked\",\n },\n encode: encode ?? ((event) => encodeEventStreamMessage(event.eventType, event.payload)),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwBA,SAAS,cAAc,SAAyC;CAC9D,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,QAAQ,EAAE;EACnD,MAAM,YAAY,OAAO,KAAK,MAAM,OAAO;EAC3C,MAAM,aAAa,OAAO,KAAK,OAAO,OAAO;EAI7C,MAAM,SAAS,OAAO,MAAM,IAAI,UAAU,SAAS,IAAI,IAAI,WAAW,OAAO;EAC7E,IAAI,SAAS;AACb,SAAO,WAAW,UAAU,QAAQ,OAAO;AAC3C,YAAU;AACV,YAAU,KAAK,QAAQ,OAAO;AAC9B,YAAU,UAAU;AACpB,SAAO,WAAW,GAAG,OAAO;AAC5B,YAAU;AACV,SAAO,cAAc,WAAW,QAAQ,OAAO;AAC/C,YAAU;AACV,aAAW,KAAK,QAAQ,OAAO;AAE/B,QAAM,KAAK,OAAO;;AAEpB,QAAO,OAAO,OAAO,MAAM;;;;;;AAS7B,SAAgB,uBAAuB,SAAiC,SAAyB;CAC/F,MAAM,gBAAgB,cAAc,QAAQ;CAC5C,MAAM,gBAAgB,cAAc;CAGpC,MAAM,cAAc,KAAY,gBAAgB,QAAQ,SAAS;CAEjE,MAAM,QAAQ,OAAO,MAAM,YAAY;CACvC,IAAI,SAAS;AAGb,OAAM,cAAc,aAAa,OAAO;AACxC,WAAU;AACV,OAAM,cAAc,eAAe,OAAO;AAC1C,WAAU;CAGV,MAAM,kCAAmB,MAAM,SAAS,GAAG,EAAE,CAAC;AAC9C,OAAM,cAAc,eAAe,GAAG,OAAO;AAC7C,WAAU;AAGV,eAAc,KAAK,OAAO,OAAO;AACjC,WAAU;AAGV,SAAQ,KAAK,OAAO,OAAO;AAC3B,WAAU,QAAQ;CAGlB,MAAM,kCAAmB,MAAM,SAAS,GAAG,cAAc,EAAE,CAAC;AAC5D,OAAM,cAAc,eAAe,GAAG,OAAO;AAE7C,QAAO;;;;;;;;AAWT,SAAgB,yBAAyB,WAAmB,aAA6B;AAOvF,QAAO,uBANiC;EACtC,iBAAiB;EACjB,eAAe;EACf,iBAAiB;EAClB,EACe,OAAO,KAAK,KAAK,UAAU,YAAY,EAAE,OAAO,CACjB;;;;;;;;;;AAWjD,eAAsB,iBACpB,KACA,QACA,SAQkB;CAClB,MAAM,OAAO,WAAW,EAAE;AAM1B,QAAOA,gCACLC,4BAAS,IAAI,EACb,QACA,kBAAkB,EAClB;EACE,SAAS,KAAK;EACd,kBAAkB,KAAK;EACvB,iBAAiB,KAAK;EACtB,aAAa,KAAK;EACnB,EACD;EAAE,QAAQ,OAAO,SAAS,IAAI,KAAK,SAAS;EAAW,aAAa,KAAK;EAAa,CACvF;;AAQH,SAAgB,iBACd,QACe;AACf,QAAO;EACL,SAAS;GACP,gBAAgB;GAChB,qBAAqB;GACtB;EACD,QAAQ,YAAY,UAAU,yBAAyB,MAAM,WAAW,MAAM,QAAQ;EACvF"}
|
|
@@ -34,7 +34,6 @@ declare function writeEventStream(res: http$1.ServerResponse, events: Array<{
|
|
|
34
34
|
signal?: AbortSignal;
|
|
35
35
|
onChunkSent?: () => void;
|
|
36
36
|
}): Promise<boolean>;
|
|
37
|
-
//# sourceMappingURL=aws-event-stream.d.ts.map
|
|
38
37
|
//#endregion
|
|
39
38
|
export { encodeEventStreamFrame, encodeEventStreamMessage, writeEventStream };
|
|
40
39
|
//# sourceMappingURL=aws-event-stream.d.cts.map
|
|
@@ -34,7 +34,6 @@ declare function writeEventStream(res: http$1.ServerResponse, events: Array<{
|
|
|
34
34
|
signal?: AbortSignal;
|
|
35
35
|
onChunkSent?: () => void;
|
|
36
36
|
}): Promise<boolean>;
|
|
37
|
-
//# sourceMappingURL=aws-event-stream.d.ts.map
|
|
38
37
|
//#endregion
|
|
39
38
|
export { encodeEventStreamFrame, encodeEventStreamMessage, writeEventStream };
|
|
40
39
|
//# sourceMappingURL=aws-event-stream.d.ts.map
|
package/dist/aws-event-stream.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { httpSink, runFrameLoop } from "./response/frame-loop.js";
|
|
2
2
|
import { crc32 } from "node:zlib";
|
|
3
3
|
|
|
4
4
|
//#region src/aws-event-stream.ts
|
|
@@ -86,30 +86,26 @@ function encodeEventStreamMessage(eventType, jsonPayload) {
|
|
|
86
86
|
*/
|
|
87
87
|
async function writeEventStream(res, events, options) {
|
|
88
88
|
const opts = options ?? {};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
chunkIndex++;
|
|
108
|
-
}
|
|
109
|
-
if (!res.writableEnded) res.end();
|
|
110
|
-
return true;
|
|
89
|
+
return runFrameLoop(httpSink(res), events, eventStreamCodec(), {
|
|
90
|
+
latency: opts.latency,
|
|
91
|
+
streamingProfile: opts.streamingProfile,
|
|
92
|
+
recordedTimings: opts.recordedTimings,
|
|
93
|
+
replaySpeed: opts.replaySpeed
|
|
94
|
+
}, {
|
|
95
|
+
signal: events.length > 0 ? opts.signal : void 0,
|
|
96
|
+
onChunkSent: opts.onChunkSent
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function eventStreamCodec(encode) {
|
|
100
|
+
return {
|
|
101
|
+
headers: {
|
|
102
|
+
"Content-Type": "application/vnd.amazon.eventstream",
|
|
103
|
+
"Transfer-Encoding": "chunked"
|
|
104
|
+
},
|
|
105
|
+
encode: encode ?? ((event) => encodeEventStreamMessage(event.eventType, event.payload))
|
|
106
|
+
};
|
|
111
107
|
}
|
|
112
108
|
|
|
113
109
|
//#endregion
|
|
114
|
-
export { encodeEventStreamFrame, encodeEventStreamMessage, writeEventStream };
|
|
110
|
+
export { encodeEventStreamFrame, encodeEventStreamMessage, eventStreamCodec, writeEventStream };
|
|
115
111
|
//# sourceMappingURL=aws-event-stream.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aws-event-stream.js","names":[],"sources":["../src/aws-event-stream.ts"],"sourcesContent":["/**\n * AWS Event Stream binary frame encoder.\n *\n * Implements the AWS binary event stream framing protocol used by Bedrock's\n * streaming (invoke-with-response-stream) endpoint. Each frame carries a set of\n * string headers and a raw-bytes payload, wrapped in a prelude with CRC32\n * checksums for integrity.\n *\n * Binary frame layout:\n * [total_length: 4B uint32-BE]\n * [headers_length: 4B uint32-BE]\n * [prelude_crc32: 4B CRC32 of first 8 bytes]\n * [headers: variable]\n * [payload: variable, raw JSON bytes]\n * [message_crc32: 4B CRC32 of entire frame minus last 4 bytes]\n */\n\nimport { crc32 } from \"node:zlib\";\nimport type * as http from \"node:http\";\nimport type { StreamingProfile, RecordedTimings } from \"./types.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"aws-event-stream.js","names":[],"sources":["../src/aws-event-stream.ts"],"sourcesContent":["/**\n * AWS Event Stream binary frame encoder.\n *\n * Implements the AWS binary event stream framing protocol used by Bedrock's\n * streaming (invoke-with-response-stream) endpoint. Each frame carries a set of\n * string headers and a raw-bytes payload, wrapped in a prelude with CRC32\n * checksums for integrity.\n *\n * Binary frame layout:\n * [total_length: 4B uint32-BE]\n * [headers_length: 4B uint32-BE]\n * [prelude_crc32: 4B CRC32 of first 8 bytes]\n * [headers: variable]\n * [payload: variable, raw JSON bytes]\n * [message_crc32: 4B CRC32 of entire frame minus last 4 bytes]\n */\n\nimport { crc32 } from \"node:zlib\";\nimport type * as http from \"node:http\";\nimport type { StreamingProfile, RecordedTimings } from \"./types.js\";\nimport { httpSink, runFrameLoop, type FrameCodec } from \"./response/frame-loop.js\";\n\n// ─── Header encoding ────────────────────────────────────────────────────────\n\nfunction encodeHeaders(headers: Record<string, string>): Buffer {\n const parts: Buffer[] = [];\n for (const [name, value] of Object.entries(headers)) {\n const nameBytes = Buffer.from(name, \"utf8\");\n const valueBytes = Buffer.from(value, \"utf8\");\n\n // name_length (1 byte) + name + type (1 byte, 7 = STRING) +\n // value_length (2 bytes BE) + value\n const header = Buffer.alloc(1 + nameBytes.length + 1 + 2 + valueBytes.length);\n let offset = 0;\n header.writeUInt8(nameBytes.length, offset);\n offset += 1;\n nameBytes.copy(header, offset);\n offset += nameBytes.length;\n header.writeUInt8(7, offset); // STRING type\n offset += 1;\n header.writeUInt16BE(valueBytes.length, offset);\n offset += 2;\n valueBytes.copy(header, offset);\n\n parts.push(header);\n }\n return Buffer.concat(parts);\n}\n\n// ─── Frame encoding ─────────────────────────────────────────────────────────\n\n/**\n * Encode a single AWS Event Stream binary frame with the given headers and\n * payload buffer.\n */\nexport function encodeEventStreamFrame(headers: Record<string, string>, payload: Buffer): Buffer {\n const headersBuffer = encodeHeaders(headers);\n const headersLength = headersBuffer.length;\n\n // prelude (8) + prelude_crc (4) + headers + payload + message_crc (4)\n const totalLength = 4 + 4 + 4 + headersLength + payload.length + 4;\n\n const frame = Buffer.alloc(totalLength);\n let offset = 0;\n\n // Prelude\n frame.writeUInt32BE(totalLength, offset);\n offset += 4;\n frame.writeUInt32BE(headersLength, offset);\n offset += 4;\n\n // Prelude CRC32 (covers first 8 bytes)\n const preludeCrc = crc32(frame.subarray(0, 8));\n frame.writeUInt32BE(preludeCrc >>> 0, offset);\n offset += 4;\n\n // Headers\n headersBuffer.copy(frame, offset);\n offset += headersLength;\n\n // Payload\n payload.copy(frame, offset);\n offset += payload.length;\n\n // Message CRC32 (covers entire frame minus last 4 bytes)\n const messageCrc = crc32(frame.subarray(0, totalLength - 4));\n frame.writeUInt32BE(messageCrc >>> 0, offset);\n\n return frame;\n}\n\n// ─── Convenience wrappers ───────────────────────────────────────────────────\n\n/**\n * Encode an event-stream message with standard AWS headers for a JSON event.\n *\n * Sets `:content-type` = `application/json`, `:event-type` = eventType,\n * `:message-type` = `event`.\n */\nexport function encodeEventStreamMessage(eventType: string, jsonPayload: object): Buffer {\n const headers: Record<string, string> = {\n \":content-type\": \"application/json\",\n \":event-type\": eventType,\n \":message-type\": \"event\",\n };\n const payload = Buffer.from(JSON.stringify(jsonPayload), \"utf8\");\n return encodeEventStreamFrame(headers, payload);\n}\n\n/**\n * Write a sequence of event-stream frames to an HTTP response with optional\n * timing control. Mirrors the writeSSEStream pattern from sse-writer.ts.\n *\n * Returns `true` when all events are written (including when the response\n * was already ended before writing began), or `false` if interrupted by\n * the provided abort signal.\n */\nexport async function writeEventStream(\n res: http.ServerResponse,\n events: Array<{ eventType: string; payload: object }>,\n options?: {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n },\n): Promise<boolean> {\n const opts = options ?? {};\n // Base contract (public API): with no frames, an aborted signal does not\n // stop the writer; it still finishes the stream (any trailer, then end())\n // and returns true. The frame loop returns false there, so pass it no\n // signal when there are no frames (it reads the signal only per frame and\n // after the loop).\n return runFrameLoop(\n httpSink(res),\n events,\n eventStreamCodec(),\n {\n latency: opts.latency,\n streamingProfile: opts.streamingProfile,\n recordedTimings: opts.recordedTimings,\n replaySpeed: opts.replaySpeed,\n },\n { signal: events.length > 0 ? opts.signal : undefined, onChunkSent: opts.onChunkSent },\n );\n}\n\n// Internal (not part of the public API): the AWS binary event-stream codec.\n// Each event becomes one complete frame, encoded by encodeEventStreamMessage\n// unless an `encode` argument replaces it. There is no prelude or trailer.\n// These are line comments, not JSDoc, so the bundled .d.ts does not attach\n// them to a public declaration.\nexport function eventStreamCodec<F extends { eventType: string; payload: object }>(\n encode?: (event: F) => Buffer,\n): FrameCodec<F> {\n return {\n headers: {\n \"Content-Type\": \"application/vnd.amazon.eventstream\",\n \"Transfer-Encoding\": \"chunked\",\n },\n encode: encode ?? ((event) => encodeEventStreamMessage(event.eventType, event.payload)),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwBA,SAAS,cAAc,SAAyC;CAC9D,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,QAAQ,EAAE;EACnD,MAAM,YAAY,OAAO,KAAK,MAAM,OAAO;EAC3C,MAAM,aAAa,OAAO,KAAK,OAAO,OAAO;EAI7C,MAAM,SAAS,OAAO,MAAM,IAAI,UAAU,SAAS,IAAI,IAAI,WAAW,OAAO;EAC7E,IAAI,SAAS;AACb,SAAO,WAAW,UAAU,QAAQ,OAAO;AAC3C,YAAU;AACV,YAAU,KAAK,QAAQ,OAAO;AAC9B,YAAU,UAAU;AACpB,SAAO,WAAW,GAAG,OAAO;AAC5B,YAAU;AACV,SAAO,cAAc,WAAW,QAAQ,OAAO;AAC/C,YAAU;AACV,aAAW,KAAK,QAAQ,OAAO;AAE/B,QAAM,KAAK,OAAO;;AAEpB,QAAO,OAAO,OAAO,MAAM;;;;;;AAS7B,SAAgB,uBAAuB,SAAiC,SAAyB;CAC/F,MAAM,gBAAgB,cAAc,QAAQ;CAC5C,MAAM,gBAAgB,cAAc;CAGpC,MAAM,cAAc,KAAY,gBAAgB,QAAQ,SAAS;CAEjE,MAAM,QAAQ,OAAO,MAAM,YAAY;CACvC,IAAI,SAAS;AAGb,OAAM,cAAc,aAAa,OAAO;AACxC,WAAU;AACV,OAAM,cAAc,eAAe,OAAO;AAC1C,WAAU;CAGV,MAAM,aAAa,MAAM,MAAM,SAAS,GAAG,EAAE,CAAC;AAC9C,OAAM,cAAc,eAAe,GAAG,OAAO;AAC7C,WAAU;AAGV,eAAc,KAAK,OAAO,OAAO;AACjC,WAAU;AAGV,SAAQ,KAAK,OAAO,OAAO;AAC3B,WAAU,QAAQ;CAGlB,MAAM,aAAa,MAAM,MAAM,SAAS,GAAG,cAAc,EAAE,CAAC;AAC5D,OAAM,cAAc,eAAe,GAAG,OAAO;AAE7C,QAAO;;;;;;;;AAWT,SAAgB,yBAAyB,WAAmB,aAA6B;AAOvF,QAAO,uBANiC;EACtC,iBAAiB;EACjB,eAAe;EACf,iBAAiB;EAClB,EACe,OAAO,KAAK,KAAK,UAAU,YAAY,EAAE,OAAO,CACjB;;;;;;;;;;AAWjD,eAAsB,iBACpB,KACA,QACA,SAQkB;CAClB,MAAM,OAAO,WAAW,EAAE;AAM1B,QAAO,aACL,SAAS,IAAI,EACb,QACA,kBAAkB,EAClB;EACE,SAAS,KAAK;EACd,kBAAkB,KAAK;EACvB,iBAAiB,KAAK;EACtB,aAAa,KAAK;EACnB,EACD;EAAE,QAAQ,OAAO,SAAS,IAAI,KAAK,SAAS;EAAW,aAAa,KAAK;EAAa,CACvF;;AAQH,SAAgB,iBACd,QACe;AACf,QAAO;EACL,SAAS;GACP,gBAAgB;GAChB,qBAAqB;GACtB;EACD,QAAQ,YAAY,UAAU,yBAAyB,MAAM,WAAW,MAAM,QAAQ;EACvF"}
|
|
@@ -6,9 +6,12 @@ const require_chaos = require('./chaos.cjs');
|
|
|
6
6
|
const require_misbehavior = require('./misbehavior.cjs');
|
|
7
7
|
const require_router = require('./router.cjs');
|
|
8
8
|
const require_recorder = require('./recorder.cjs');
|
|
9
|
-
const
|
|
10
|
-
const
|
|
9
|
+
const require_timing = require('./response/timing.cjs');
|
|
10
|
+
const require_deliver = require('./response/deliver.cjs');
|
|
11
|
+
const require_proxy = require('./response/proxy.cjs');
|
|
11
12
|
const require_aws_event_stream = require('./aws-event-stream.cjs');
|
|
13
|
+
const require_raw = require('./response/raw.cjs');
|
|
14
|
+
const require_finish = require('./response/finish.cjs');
|
|
12
15
|
const require_converse = require('./response/usage/codecs/converse.cjs');
|
|
13
16
|
|
|
14
17
|
//#region src/bedrock-converse.ts
|
|
@@ -685,7 +688,10 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
685
688
|
completionReq._endpointType = "chat";
|
|
686
689
|
completionReq._context = require_helpers.getContext(req);
|
|
687
690
|
const testId = require_helpers.getTestId(req);
|
|
688
|
-
const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform,
|
|
691
|
+
const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, {
|
|
692
|
+
...require_router.recordMatchOptions(!!defaults.record, defaults.logger),
|
|
693
|
+
wire: "bedrock-converse"
|
|
694
|
+
});
|
|
689
695
|
if (fixture) logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
|
|
690
696
|
else logger.debug(`No fixture matched for request`);
|
|
691
697
|
if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
@@ -718,7 +724,7 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
718
724
|
return;
|
|
719
725
|
}
|
|
720
726
|
if (defaults.record) {
|
|
721
|
-
const outcome = await require_recorder.proxyAndRecord(req, res, completionReq, "bedrock", urlPath, fixtures, defaults, raw);
|
|
727
|
+
const { outcome, clientHungUp } = await require_proxy.withHangUpDetection(req, res, () => require_recorder.proxyAndRecord(req, res, completionReq, "bedrock", urlPath, fixtures, defaults, raw));
|
|
722
728
|
if (outcome === "handled_by_hook") return;
|
|
723
729
|
if (outcome !== "not_configured") {
|
|
724
730
|
journal.add({
|
|
@@ -729,7 +735,8 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
729
735
|
response: {
|
|
730
736
|
status: res.statusCode ?? 200,
|
|
731
737
|
fixture: null,
|
|
732
|
-
source: "proxy"
|
|
738
|
+
source: "proxy",
|
|
739
|
+
...require_proxy.proxyInterruption(res, clientHungUp)
|
|
733
740
|
}
|
|
734
741
|
});
|
|
735
742
|
return;
|
|
@@ -796,11 +803,19 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
796
803
|
}).body);
|
|
797
804
|
return;
|
|
798
805
|
}
|
|
806
|
+
const jsonOpts = (payloadLength, duplicateId = false) => ({
|
|
807
|
+
surface: "other",
|
|
808
|
+
payloadLength,
|
|
809
|
+
chunkSize: Math.max(1, fixture.chunkSize ?? defaults.chunkSize),
|
|
810
|
+
recordedTimings: fixture.recordedTimings,
|
|
811
|
+
duplicateId,
|
|
812
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
813
|
+
});
|
|
799
814
|
if (misbehavior.kind === "applied") {
|
|
800
815
|
const prepared = prepareConverseMisbehavior(misbehavior, completionReq, false, defaults, req.headers);
|
|
801
816
|
misbehavior = prepared.plan;
|
|
802
817
|
const body = buildConverseMisbehaviorResponse(prepared);
|
|
803
|
-
addResponseEntry({
|
|
818
|
+
await require_deliver.deliverJson(res, addResponseEntry({
|
|
804
819
|
method: req.method ?? "POST",
|
|
805
820
|
path: urlPath,
|
|
806
821
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
@@ -809,9 +824,24 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
809
824
|
status: 200,
|
|
810
825
|
fixture
|
|
811
826
|
}
|
|
827
|
+
}), fixture, () => body, jsonOpts(require_timing.misbehaviorPayloadLength(prepared.plan, prepared.plan.reasoning === void 0 ? prepared.reasoning : void 0), !!prepared.plan.duplicateId));
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (require_helpers.isRawResponse(response)) {
|
|
831
|
+
await require_raw.serveRawResponse({
|
|
832
|
+
wire: "bedrock-converse",
|
|
833
|
+
stream: false,
|
|
834
|
+
req,
|
|
835
|
+
res,
|
|
836
|
+
fixture,
|
|
837
|
+
response,
|
|
838
|
+
body: completionReq,
|
|
839
|
+
journal,
|
|
840
|
+
defaults,
|
|
841
|
+
testId,
|
|
842
|
+
misbehaviorSummary: misbehavior.summary,
|
|
843
|
+
errorWire: "bedrock-converse"
|
|
812
844
|
});
|
|
813
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
814
|
-
res.end(JSON.stringify(body));
|
|
815
845
|
return;
|
|
816
846
|
}
|
|
817
847
|
if (require_helpers.isErrorResponse(response)) {
|
|
@@ -836,7 +866,7 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
836
866
|
if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
|
|
837
867
|
const overrides = require_helpers.extractOverrides(response);
|
|
838
868
|
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
|
|
839
|
-
addResponseEntry({
|
|
869
|
+
const journalEntry = addResponseEntry({
|
|
840
870
|
method: req.method ?? "POST",
|
|
841
871
|
path: urlPath,
|
|
842
872
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
@@ -847,16 +877,14 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
847
877
|
}
|
|
848
878
|
});
|
|
849
879
|
const functionToolCalls = require_helpers.requireEmittedFunctionToolCalls(response, "Bedrock Converse");
|
|
850
|
-
|
|
851
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
852
|
-
res.end(JSON.stringify(body));
|
|
880
|
+
await require_deliver.deliverJson(res, journalEntry, fixture, () => buildConverseContentWithToolCallsResponse(response.content ?? "", functionToolCalls, logger, effReasoning, overrides, response.blocks), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
|
|
853
881
|
return;
|
|
854
882
|
}
|
|
855
883
|
if (require_helpers.isTextResponse(response)) {
|
|
856
884
|
if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
|
|
857
885
|
const overrides = require_helpers.extractOverrides(response);
|
|
858
886
|
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
|
|
859
|
-
addResponseEntry({
|
|
887
|
+
await require_deliver.deliverJson(res, addResponseEntry({
|
|
860
888
|
method: req.method ?? "POST",
|
|
861
889
|
path: urlPath,
|
|
862
890
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
@@ -865,17 +893,14 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
865
893
|
status: 200,
|
|
866
894
|
fixture
|
|
867
895
|
}
|
|
868
|
-
});
|
|
869
|
-
const body = buildConverseTextResponse(response.content, effReasoning, overrides);
|
|
870
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
871
|
-
res.end(JSON.stringify(body));
|
|
896
|
+
}), fixture, () => buildConverseTextResponse(response.content, effReasoning, overrides), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
|
|
872
897
|
return;
|
|
873
898
|
}
|
|
874
899
|
if (require_helpers.isToolCallResponse(response)) {
|
|
875
900
|
if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
|
|
876
901
|
const overrides = require_helpers.extractOverrides(response);
|
|
877
902
|
const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
|
|
878
|
-
addResponseEntry({
|
|
903
|
+
const journalEntry = addResponseEntry({
|
|
879
904
|
method: req.method ?? "POST",
|
|
880
905
|
path: urlPath,
|
|
881
906
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
@@ -885,9 +910,8 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
|
|
|
885
910
|
fixture
|
|
886
911
|
}
|
|
887
912
|
});
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
res.end(JSON.stringify(body));
|
|
913
|
+
const functionToolCalls = require_helpers.requireEmittedFunctionToolCalls(response, "Bedrock Converse");
|
|
914
|
+
await require_deliver.deliverJson(res, journalEntry, fixture, () => buildConverseToolCallResponse(functionToolCalls, logger, effReasoning, overrides), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
|
|
891
915
|
return;
|
|
892
916
|
}
|
|
893
917
|
addResponseEntry({
|
|
@@ -970,7 +994,10 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
970
994
|
completionReq._endpointType = "chat";
|
|
971
995
|
completionReq._context = require_helpers.getContext(req);
|
|
972
996
|
const testId = require_helpers.getTestId(req);
|
|
973
|
-
const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform,
|
|
997
|
+
const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, {
|
|
998
|
+
...require_router.recordMatchOptions(!!defaults.record, defaults.logger),
|
|
999
|
+
wire: "bedrock-converse"
|
|
1000
|
+
});
|
|
974
1001
|
if (fixture) logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
|
|
975
1002
|
else logger.debug(`No fixture matched for request`);
|
|
976
1003
|
if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
@@ -1003,7 +1030,7 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1003
1030
|
return;
|
|
1004
1031
|
}
|
|
1005
1032
|
if (defaults.record) {
|
|
1006
|
-
const outcome = await require_recorder.proxyAndRecord(req, res, completionReq, "bedrock", urlPath, fixtures, defaults, raw);
|
|
1033
|
+
const { outcome, clientHungUp } = await require_proxy.withHangUpDetection(req, res, () => require_recorder.proxyAndRecord(req, res, completionReq, "bedrock", urlPath, fixtures, defaults, raw));
|
|
1007
1034
|
if (outcome === "handled_by_hook") return;
|
|
1008
1035
|
if (outcome !== "not_configured") {
|
|
1009
1036
|
journal.add({
|
|
@@ -1014,7 +1041,8 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1014
1041
|
response: {
|
|
1015
1042
|
status: res.statusCode ?? 200,
|
|
1016
1043
|
fixture: null,
|
|
1017
|
-
source: "proxy"
|
|
1044
|
+
source: "proxy",
|
|
1045
|
+
...require_proxy.proxyInterruption(res, clientHungUp)
|
|
1018
1046
|
}
|
|
1019
1047
|
});
|
|
1020
1048
|
return;
|
|
@@ -1081,13 +1109,18 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1081
1109
|
}).body);
|
|
1082
1110
|
return;
|
|
1083
1111
|
}
|
|
1084
|
-
const latency = fixture.latency ?? defaults.latency;
|
|
1085
1112
|
const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
|
|
1113
|
+
const streamTiming = {
|
|
1114
|
+
latency: fixture.latency ?? defaults.latency,
|
|
1115
|
+
streamingProfile: fixture.streamingProfile,
|
|
1116
|
+
recordedTimings: fixture.recordedTimings,
|
|
1117
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
1118
|
+
};
|
|
1086
1119
|
if (misbehavior.kind === "applied") {
|
|
1087
1120
|
const prepared = prepareConverseMisbehavior(misbehavior, completionReq, true, defaults, req.headers);
|
|
1088
1121
|
misbehavior = prepared.plan;
|
|
1089
1122
|
const events = buildConverseMisbehaviorEvents(prepared, chunkSize);
|
|
1090
|
-
|
|
1123
|
+
await require_deliver.deliverStream(res, addResponseEntry({
|
|
1091
1124
|
method: req.method ?? "POST",
|
|
1092
1125
|
path: urlPath,
|
|
1093
1126
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
@@ -1096,21 +1129,24 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1096
1129
|
status: 200,
|
|
1097
1130
|
fixture
|
|
1098
1131
|
}
|
|
1132
|
+
}), fixture, require_aws_event_stream.eventStreamCodec(), events, streamTiming);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (require_helpers.isRawResponse(response)) {
|
|
1136
|
+
await require_raw.serveRawResponse({
|
|
1137
|
+
wire: "bedrock-converse",
|
|
1138
|
+
stream: true,
|
|
1139
|
+
req,
|
|
1140
|
+
res,
|
|
1141
|
+
fixture,
|
|
1142
|
+
response,
|
|
1143
|
+
body: completionReq,
|
|
1144
|
+
journal,
|
|
1145
|
+
defaults,
|
|
1146
|
+
testId,
|
|
1147
|
+
misbehaviorSummary: misbehavior.summary,
|
|
1148
|
+
errorWire: "bedrock-converse"
|
|
1099
1149
|
});
|
|
1100
|
-
const interruption = require_interruption.createInterruptionSignal(fixture);
|
|
1101
|
-
if (!await require_aws_event_stream.writeEventStream(res, events, {
|
|
1102
|
-
latency,
|
|
1103
|
-
streamingProfile: fixture.streamingProfile,
|
|
1104
|
-
recordedTimings: fixture.recordedTimings,
|
|
1105
|
-
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1106
|
-
signal: interruption?.signal,
|
|
1107
|
-
onChunkSent: interruption?.tick
|
|
1108
|
-
})) {
|
|
1109
|
-
if (!res.writableEnded) res.destroy();
|
|
1110
|
-
entry.response.interrupted = true;
|
|
1111
|
-
entry.response.interruptReason = interruption?.reason();
|
|
1112
|
-
}
|
|
1113
|
-
interruption?.cleanup();
|
|
1114
1150
|
return;
|
|
1115
1151
|
}
|
|
1116
1152
|
if (require_helpers.isErrorResponse(response)) {
|
|
@@ -1147,20 +1183,7 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1147
1183
|
});
|
|
1148
1184
|
const functionToolCalls = require_helpers.requireEmittedFunctionToolCalls(response, "Bedrock Converse");
|
|
1149
1185
|
const events = buildBedrockStreamContentWithToolCallsEvents(response.content ?? "", functionToolCalls, chunkSize, logger, effReasoning, overrides, response.blocks);
|
|
1150
|
-
|
|
1151
|
-
if (!await require_aws_event_stream.writeEventStream(res, events, {
|
|
1152
|
-
latency,
|
|
1153
|
-
streamingProfile: fixture.streamingProfile,
|
|
1154
|
-
recordedTimings: fixture.recordedTimings,
|
|
1155
|
-
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1156
|
-
signal: interruption?.signal,
|
|
1157
|
-
onChunkSent: interruption?.tick
|
|
1158
|
-
})) {
|
|
1159
|
-
if (!res.writableEnded) res.destroy();
|
|
1160
|
-
journalEntry.response.interrupted = true;
|
|
1161
|
-
journalEntry.response.interruptReason = interruption?.reason();
|
|
1162
|
-
}
|
|
1163
|
-
interruption?.cleanup();
|
|
1186
|
+
await require_deliver.deliverStream(res, journalEntry, fixture, require_aws_event_stream.eventStreamCodec(), events, streamTiming);
|
|
1164
1187
|
return;
|
|
1165
1188
|
}
|
|
1166
1189
|
if (require_helpers.isTextResponse(response)) {
|
|
@@ -1178,20 +1201,7 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1178
1201
|
}
|
|
1179
1202
|
});
|
|
1180
1203
|
const events = buildBedrockStreamTextEvents(response.content, chunkSize, effReasoning, overrides);
|
|
1181
|
-
|
|
1182
|
-
if (!await require_aws_event_stream.writeEventStream(res, events, {
|
|
1183
|
-
latency,
|
|
1184
|
-
streamingProfile: fixture.streamingProfile,
|
|
1185
|
-
recordedTimings: fixture.recordedTimings,
|
|
1186
|
-
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1187
|
-
signal: interruption?.signal,
|
|
1188
|
-
onChunkSent: interruption?.tick
|
|
1189
|
-
})) {
|
|
1190
|
-
if (!res.writableEnded) res.destroy();
|
|
1191
|
-
journalEntry.response.interrupted = true;
|
|
1192
|
-
journalEntry.response.interruptReason = interruption?.reason();
|
|
1193
|
-
}
|
|
1194
|
-
interruption?.cleanup();
|
|
1204
|
+
await require_deliver.deliverStream(res, journalEntry, fixture, require_aws_event_stream.eventStreamCodec(), events, streamTiming);
|
|
1195
1205
|
return;
|
|
1196
1206
|
}
|
|
1197
1207
|
if (require_helpers.isToolCallResponse(response)) {
|
|
@@ -1209,20 +1219,7 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
|
|
|
1209
1219
|
}
|
|
1210
1220
|
});
|
|
1211
1221
|
const events = buildBedrockStreamToolCallEvents(require_helpers.requireEmittedFunctionToolCalls(response, "Bedrock Converse"), chunkSize, logger, effReasoning, overrides);
|
|
1212
|
-
|
|
1213
|
-
if (!await require_aws_event_stream.writeEventStream(res, events, {
|
|
1214
|
-
latency,
|
|
1215
|
-
streamingProfile: fixture.streamingProfile,
|
|
1216
|
-
recordedTimings: fixture.recordedTimings,
|
|
1217
|
-
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
1218
|
-
signal: interruption?.signal,
|
|
1219
|
-
onChunkSent: interruption?.tick
|
|
1220
|
-
})) {
|
|
1221
|
-
if (!res.writableEnded) res.destroy();
|
|
1222
|
-
journalEntry.response.interrupted = true;
|
|
1223
|
-
journalEntry.response.interruptReason = interruption?.reason();
|
|
1224
|
-
}
|
|
1225
|
-
interruption?.cleanup();
|
|
1222
|
+
await require_deliver.deliverStream(res, journalEntry, fixture, require_aws_event_stream.eventStreamCodec(), events, streamTiming);
|
|
1226
1223
|
return;
|
|
1227
1224
|
}
|
|
1228
1225
|
addResponseEntry({
|