@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.
Files changed (183) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +12 -1
  4. package/README.md +4 -1
  5. package/dist/aws-event-stream.cjs +20 -23
  6. package/dist/aws-event-stream.cjs.map +1 -1
  7. package/dist/aws-event-stream.d.cts +0 -1
  8. package/dist/aws-event-stream.d.ts +0 -1
  9. package/dist/aws-event-stream.js +20 -24
  10. package/dist/aws-event-stream.js.map +1 -1
  11. package/dist/bedrock-converse.cjs +79 -82
  12. package/dist/bedrock-converse.cjs.map +1 -1
  13. package/dist/bedrock-converse.d.cts.map +1 -1
  14. package/dist/bedrock-converse.d.ts.map +1 -1
  15. package/dist/bedrock-converse.js +80 -83
  16. package/dist/bedrock-converse.js.map +1 -1
  17. package/dist/bedrock.cjs +112 -121
  18. package/dist/bedrock.cjs.map +1 -1
  19. package/dist/bedrock.d.cts.map +1 -1
  20. package/dist/bedrock.d.ts.map +1 -1
  21. package/dist/bedrock.js +113 -122
  22. package/dist/bedrock.js.map +1 -1
  23. package/dist/cohere.cjs +58 -106
  24. package/dist/cohere.cjs.map +1 -1
  25. package/dist/cohere.d.cts.map +1 -1
  26. package/dist/cohere.d.ts.map +1 -1
  27. package/dist/cohere.js +60 -108
  28. package/dist/cohere.js.map +1 -1
  29. package/dist/config-loader.d.cts.map +1 -1
  30. package/dist/config-loader.d.ts.map +1 -1
  31. package/dist/fixture-loader.cjs +103 -2
  32. package/dist/fixture-loader.cjs.map +1 -1
  33. package/dist/fixture-loader.d.cts.map +1 -1
  34. package/dist/fixture-loader.d.ts.map +1 -1
  35. package/dist/fixture-loader.js +104 -4
  36. package/dist/fixture-loader.js.map +1 -1
  37. package/dist/gemini-interactions.cjs +63 -121
  38. package/dist/gemini-interactions.cjs.map +1 -1
  39. package/dist/gemini-interactions.d.cts.map +1 -1
  40. package/dist/gemini-interactions.d.ts.map +1 -1
  41. package/dist/gemini-interactions.js +65 -123
  42. package/dist/gemini-interactions.js.map +1 -1
  43. package/dist/gemini.cjs +242 -155
  44. package/dist/gemini.cjs.map +1 -1
  45. package/dist/gemini.d.cts.map +1 -1
  46. package/dist/gemini.d.ts.map +1 -1
  47. package/dist/gemini.js +244 -157
  48. package/dist/gemini.js.map +1 -1
  49. package/dist/helpers.cjs +400 -7
  50. package/dist/helpers.cjs.map +1 -1
  51. package/dist/helpers.d.cts +51 -1
  52. package/dist/helpers.d.cts.map +1 -1
  53. package/dist/helpers.d.ts +51 -1
  54. package/dist/helpers.d.ts.map +1 -1
  55. package/dist/helpers.js +391 -8
  56. package/dist/helpers.js.map +1 -1
  57. package/dist/index.cjs +4 -2
  58. package/dist/index.d.cts +4 -4
  59. package/dist/index.d.ts +4 -4
  60. package/dist/index.js +4 -4
  61. package/dist/mcp-fakes.d.cts.map +1 -1
  62. package/dist/messages.cjs +159 -141
  63. package/dist/messages.cjs.map +1 -1
  64. package/dist/messages.d.cts.map +1 -1
  65. package/dist/messages.d.ts.map +1 -1
  66. package/dist/messages.js +161 -143
  67. package/dist/messages.js.map +1 -1
  68. package/dist/misbehavior.cjs +62 -3
  69. package/dist/misbehavior.cjs.map +1 -1
  70. package/dist/misbehavior.d.cts.map +1 -1
  71. package/dist/misbehavior.d.ts.map +1 -1
  72. package/dist/misbehavior.js +63 -4
  73. package/dist/misbehavior.js.map +1 -1
  74. package/dist/ndjson-writer.cjs +21 -23
  75. package/dist/ndjson-writer.cjs.map +1 -1
  76. package/dist/ndjson-writer.d.cts +0 -1
  77. package/dist/ndjson-writer.d.cts.map +1 -1
  78. package/dist/ndjson-writer.d.ts +0 -1
  79. package/dist/ndjson-writer.d.ts.map +1 -1
  80. package/dist/ndjson-writer.js +21 -24
  81. package/dist/ndjson-writer.js.map +1 -1
  82. package/dist/ollama.cjs +153 -147
  83. package/dist/ollama.cjs.map +1 -1
  84. package/dist/ollama.d.cts.map +1 -1
  85. package/dist/ollama.d.ts.map +1 -1
  86. package/dist/ollama.js +155 -149
  87. package/dist/ollama.js.map +1 -1
  88. package/dist/openrouter-chat.cjs +1 -1
  89. package/dist/openrouter-chat.js +1 -1
  90. package/dist/raw-validation.cjs +177 -0
  91. package/dist/raw-validation.cjs.map +1 -0
  92. package/dist/raw-validation.js +176 -0
  93. package/dist/raw-validation.js.map +1 -0
  94. package/dist/response/deliver.cjs +144 -0
  95. package/dist/response/deliver.cjs.map +1 -0
  96. package/dist/response/deliver.js +143 -0
  97. package/dist/response/deliver.js.map +1 -0
  98. package/dist/response/frame-loop.cjs +102 -0
  99. package/dist/response/frame-loop.cjs.map +1 -0
  100. package/dist/response/frame-loop.js +100 -0
  101. package/dist/response/frame-loop.js.map +1 -0
  102. package/dist/response/lifecycle.cjs +223 -0
  103. package/dist/response/lifecycle.cjs.map +1 -0
  104. package/dist/response/lifecycle.js +211 -0
  105. package/dist/response/lifecycle.js.map +1 -0
  106. package/dist/response/proxy.cjs +71 -0
  107. package/dist/response/proxy.cjs.map +1 -0
  108. package/dist/response/proxy.js +70 -0
  109. package/dist/response/proxy.js.map +1 -0
  110. package/dist/response/raw.cjs +228 -0
  111. package/dist/response/raw.cjs.map +1 -0
  112. package/dist/response/raw.js +227 -0
  113. package/dist/response/raw.js.map +1 -0
  114. package/dist/response/timing.cjs +157 -0
  115. package/dist/response/timing.cjs.map +1 -0
  116. package/dist/response/timing.js +150 -0
  117. package/dist/response/timing.js.map +1 -0
  118. package/dist/responses.cjs +269 -148
  119. package/dist/responses.cjs.map +1 -1
  120. package/dist/responses.d.cts.map +1 -1
  121. package/dist/responses.d.ts.map +1 -1
  122. package/dist/responses.js +270 -149
  123. package/dist/responses.js.map +1 -1
  124. package/dist/router.cjs +20 -4
  125. package/dist/router.cjs.map +1 -1
  126. package/dist/router.d.cts +7 -1
  127. package/dist/router.d.cts.map +1 -1
  128. package/dist/router.d.ts +7 -1
  129. package/dist/router.d.ts.map +1 -1
  130. package/dist/router.js +21 -5
  131. package/dist/router.js.map +1 -1
  132. package/dist/server.cjs +89 -120
  133. package/dist/server.cjs.map +1 -1
  134. package/dist/server.d.cts.map +1 -1
  135. package/dist/server.d.ts.map +1 -1
  136. package/dist/server.js +91 -122
  137. package/dist/server.js.map +1 -1
  138. package/dist/sse-writer.cjs +49 -30
  139. package/dist/sse-writer.cjs.map +1 -1
  140. package/dist/sse-writer.d.cts +0 -1
  141. package/dist/sse-writer.d.cts.map +1 -1
  142. package/dist/sse-writer.d.ts +0 -1
  143. package/dist/sse-writer.d.ts.map +1 -1
  144. package/dist/sse-writer.js +49 -31
  145. package/dist/sse-writer.js.map +1 -1
  146. package/dist/transcription.cjs +19 -33
  147. package/dist/transcription.cjs.map +1 -1
  148. package/dist/transcription.d.cts.map +1 -1
  149. package/dist/transcription.d.ts.map +1 -1
  150. package/dist/transcription.js +20 -34
  151. package/dist/transcription.js.map +1 -1
  152. package/dist/types.cjs.map +1 -1
  153. package/dist/types.d.cts +173 -10
  154. package/dist/types.d.cts.map +1 -1
  155. package/dist/types.d.ts +173 -10
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js.map +1 -1
  158. package/dist/ws-framing.cjs +12 -6
  159. package/dist/ws-framing.cjs.map +1 -1
  160. package/dist/ws-framing.d.cts +2 -0
  161. package/dist/ws-framing.d.cts.map +1 -1
  162. package/dist/ws-framing.d.ts +2 -0
  163. package/dist/ws-framing.d.ts.map +1 -1
  164. package/dist/ws-framing.js +12 -6
  165. package/dist/ws-framing.js.map +1 -1
  166. package/dist/ws-gemini-live.cjs +120 -93
  167. package/dist/ws-gemini-live.cjs.map +1 -1
  168. package/dist/ws-gemini-live.js +120 -93
  169. package/dist/ws-gemini-live.js.map +1 -1
  170. package/dist/ws-realtime.cjs +80 -70
  171. package/dist/ws-realtime.cjs.map +1 -1
  172. package/dist/ws-realtime.d.cts.map +1 -1
  173. package/dist/ws-realtime.d.ts.map +1 -1
  174. package/dist/ws-realtime.js +80 -70
  175. package/dist/ws-realtime.js.map +1 -1
  176. package/dist/ws-responses.cjs +72 -53
  177. package/dist/ws-responses.cjs.map +1 -1
  178. package/dist/ws-responses.d.cts.map +1 -1
  179. package/dist/ws-responses.d.ts.map +1 -1
  180. package/dist/ws-responses.js +73 -54
  181. package/dist/ws-responses.js.map +1 -1
  182. package/package.json +1 -1
  183. 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.46.0"
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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aimock",
3
- "version": "1.46.0",
3
+ "version": "1.47.0",
4
4
  "description": "Fixture authoring guidance for @copilotkit/aimock — LLM, multimedia, MCP, A2A, AG-UI, vector, and service mocking",
5
5
  "author": {
6
6
  "name": "CopilotKit"
package/CHANGELOG.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # @copilotkit/aimock
2
2
 
3
- ## [Unreleased]
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 require_sse_writer = require('./sse-writer.cjs');
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
- const latency = opts.latency ?? 0;
91
- const profile = opts.streamingProfile;
92
- const { recordedTimings, replaySpeed } = opts;
93
- const signal = opts.signal;
94
- const onChunkSent = opts.onChunkSent;
95
- if (res.writableEnded) return true;
96
- res.setHeader("Content-Type", "application/vnd.amazon.eventstream");
97
- res.setHeader("Transfer-Encoding", "chunked");
98
- let chunkIndex = 0;
99
- for (const event of events) {
100
- const chunkDelay = require_sse_writer.calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
101
- if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, signal);
102
- if (signal?.aborted) return false;
103
- if (res.writableEnded) return true;
104
- const frame = encodeEventStreamMessage(event.eventType, event.payload);
105
- res.write(frame);
106
- onChunkSent?.();
107
- if (signal?.aborted) return false;
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":["calculateDelay","delay"],"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 { delay, calculateDelay } from \"./sse-writer.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 const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const { recordedTimings, replaySpeed } = opts;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"application/vnd.amazon.eventstream\");\n res.setHeader(\"Transfer-Encoding\", \"chunked\");\n\n let chunkIndex = 0;\n for (const event of events) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) {\n await delay(chunkDelay, signal);\n }\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n\n const frame = encodeEventStreamMessage(event.eventType, event.payload);\n res.write(frame);\n onChunkSent?.();\n if (signal?.aborted) return false;\n chunkIndex++;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\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;CAC1B,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,UAAU,KAAK;CACrB,MAAM,EAAE,iBAAiB,gBAAgB;CACzC,MAAM,SAAS,KAAK;CACpB,MAAM,cAAc,KAAK;AAEzB,KAAI,IAAI,cAAe,QAAO;AAC9B,KAAI,UAAU,gBAAgB,qCAAqC;AACnE,KAAI,UAAU,qBAAqB,UAAU;CAE7C,IAAI,aAAa;AACjB,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,aAAaA,kCAAe,YAAY,SAAS,SAAS,iBAAiB,YAAY;AAC7F,MAAI,aAAa,EACf,OAAMC,yBAAM,YAAY,OAAO;AAEjC,MAAI,QAAQ,QAAS,QAAO;AAC5B,MAAI,IAAI,cAAe,QAAO;EAE9B,MAAM,QAAQ,yBAAyB,MAAM,WAAW,MAAM,QAAQ;AACtE,MAAI,MAAM,MAAM;AAChB,iBAAe;AACf,MAAI,QAAQ,QAAS,QAAO;AAC5B;;AAGF,KAAI,CAAC,IAAI,cACP,KAAI,KAAK;AAEX,QAAO"}
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
@@ -1,4 +1,4 @@
1
- import { calculateDelay, delay } from "./sse-writer.js";
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
- const latency = opts.latency ?? 0;
90
- const profile = opts.streamingProfile;
91
- const { recordedTimings, replaySpeed } = opts;
92
- const signal = opts.signal;
93
- const onChunkSent = opts.onChunkSent;
94
- if (res.writableEnded) return true;
95
- res.setHeader("Content-Type", "application/vnd.amazon.eventstream");
96
- res.setHeader("Transfer-Encoding", "chunked");
97
- let chunkIndex = 0;
98
- for (const event of events) {
99
- const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
100
- if (chunkDelay > 0) await delay(chunkDelay, signal);
101
- if (signal?.aborted) return false;
102
- if (res.writableEnded) return true;
103
- const frame = encodeEventStreamMessage(event.eventType, event.payload);
104
- res.write(frame);
105
- onChunkSent?.();
106
- if (signal?.aborted) return false;
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 { delay, calculateDelay } from \"./sse-writer.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 const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const { recordedTimings, replaySpeed } = opts;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"application/vnd.amazon.eventstream\");\n res.setHeader(\"Transfer-Encoding\", \"chunked\");\n\n let chunkIndex = 0;\n for (const event of events) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) {\n await delay(chunkDelay, signal);\n }\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n\n const frame = encodeEventStreamMessage(event.eventType, event.payload);\n res.write(frame);\n onChunkSent?.();\n if (signal?.aborted) return false;\n chunkIndex++;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\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;CAC1B,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,UAAU,KAAK;CACrB,MAAM,EAAE,iBAAiB,gBAAgB;CACzC,MAAM,SAAS,KAAK;CACpB,MAAM,cAAc,KAAK;AAEzB,KAAI,IAAI,cAAe,QAAO;AAC9B,KAAI,UAAU,gBAAgB,qCAAqC;AACnE,KAAI,UAAU,qBAAqB,UAAU;CAE7C,IAAI,aAAa;AACjB,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,aAAa,eAAe,YAAY,SAAS,SAAS,iBAAiB,YAAY;AAC7F,MAAI,aAAa,EACf,OAAM,MAAM,YAAY,OAAO;AAEjC,MAAI,QAAQ,QAAS,QAAO;AAC5B,MAAI,IAAI,cAAe,QAAO;EAE9B,MAAM,QAAQ,yBAAyB,MAAM,WAAW,MAAM,QAAQ;AACtE,MAAI,MAAM,MAAM;AAChB,iBAAe;AACf,MAAI,QAAQ,QAAS,QAAO;AAC5B;;AAGF,KAAI,CAAC,IAAI,cACP,KAAI,KAAK;AAEX,QAAO"}
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 require_interruption = require('./interruption.cjs');
10
- const require_finish = require('./response/finish.cjs');
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, require_router.recordMatchOptions(!!defaults.record, defaults.logger));
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
- const body = buildConverseContentWithToolCallsResponse(response.content ?? "", functionToolCalls, logger, effReasoning, overrides, response.blocks);
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 body = buildConverseToolCallResponse(require_helpers.requireEmittedFunctionToolCalls(response, "Bedrock Converse"), logger, effReasoning, overrides);
889
- res.writeHead(200, { "Content-Type": "application/json" });
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, require_router.recordMatchOptions(!!defaults.record, defaults.logger));
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
- const entry = addResponseEntry({
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
- const interruption = require_interruption.createInterruptionSignal(fixture);
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
- const interruption = require_interruption.createInterruptionSignal(fixture);
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
- const interruption = require_interruption.createInterruptionSignal(fixture);
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({