@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
@@ -0,0 +1,228 @@
1
+ const require_errors = require('./errors.cjs');
2
+ const require_helpers = require('../helpers.cjs');
3
+ const require_raw_validation = require('../raw-validation.cjs');
4
+ const require_sse_writer = require('../sse-writer.cjs');
5
+ const require_misbehavior = require('../misbehavior.cjs');
6
+ const require_fixture_loader = require('../fixture-loader.cjs');
7
+ const require_deliver = require('./deliver.cjs');
8
+ const require_aws_event_stream = require('../aws-event-stream.cjs');
9
+ const require_openrouter_chat = require('../openrouter-chat.cjs');
10
+ const require_ndjson_writer = require('../ndjson-writer.cjs');
11
+
12
+ //#region src/response/raw.ts
13
+ /**
14
+ * The default headers of the typed stream on `wire`, read from the typed
15
+ * codec: SSE and NDJSON add `Cache-Control: no-cache` and
16
+ * `Connection: keep-alive`, the AWS event stream `Transfer-Encoding: chunked`.
17
+ */
18
+ function rawStreamHeaders(wire) {
19
+ switch (wire) {
20
+ case "openai-responses":
21
+ case "openai-chat":
22
+ case "anthropic":
23
+ case "gemini":
24
+ case "gemini-interactions":
25
+ case "cohere": return require_sse_writer.sseCodec({ done: false }).headers;
26
+ case "ollama":
27
+ case "ollama-generate": return require_ndjson_writer.ndjsonCodec().headers;
28
+ case "bedrock-invoke":
29
+ case "bedrock-converse": return require_aws_event_stream.eventStreamCodec().headers;
30
+ }
31
+ }
32
+ /** The non-streaming JSON default, as `deliverJson` sends it. */
33
+ const JSON_HEADERS = { "Content-Type": "application/json" };
34
+ /**
35
+ * `defaults` with each `authored` header laid over it. Header names are
36
+ * case-insensitive: an authored header replaces the default of the same name
37
+ * in any letter case and takes the default's spelling, so a later spread over
38
+ * the same defaults (as `deliverJson` does) still has one key per name.
39
+ */
40
+ function overrideHeaders(defaults, authored) {
41
+ const merged = { ...defaults };
42
+ const spelling = new Map(Object.keys(defaults).map((name) => [name.toLowerCase(), name]));
43
+ for (const [name, value] of Object.entries(authored ?? {})) merged[spelling.get(name.toLowerCase()) ?? name] = value;
44
+ return merged;
45
+ }
46
+ /** One raw event, framed for `wire`. Events are checked before framing (raw-validation.ts). */
47
+ function encodeRawFrame(wire, event) {
48
+ const json = JSON.stringify(event);
49
+ switch (wire) {
50
+ case "openai-responses":
51
+ case "anthropic":
52
+ case "cohere": return `event: ${String(event.type)}\ndata: ${json}\n\n`;
53
+ case "openai-chat":
54
+ case "gemini":
55
+ case "gemini-interactions": return `data: ${json}\n\n`;
56
+ case "ollama":
57
+ case "ollama-generate": return `${json}\n`;
58
+ case "bedrock-invoke": return require_aws_event_stream.encodeEventStreamMessage("chunk", { bytes: Buffer.from(json, "utf8").toString("base64") });
59
+ case "bedrock-converse": {
60
+ const [eventType] = Object.keys(event);
61
+ const payload = event[eventType];
62
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) throw new Error(`raw framing internal error: bedrock-converse event "${eventType}" has no object payload`);
63
+ return require_aws_event_stream.encodeEventStreamMessage(eventType, payload);
64
+ }
65
+ }
66
+ }
67
+ /**
68
+ * Bytes written after the last frame, if any. Only `openai-chat` has a
69
+ * terminal (`data: [DONE]`), sent unless `raw.done === false` and never after
70
+ * an interrupted stream.
71
+ */
72
+ function rawTrailer(wire, raw, aborted) {
73
+ if (wire === "openai-chat" && !aborted && raw.done !== false) return "data: [DONE]\n\n";
74
+ }
75
+ /** The error code for a request the raw fixture has no body for. */
76
+ const RAW_MODE_UNAVAILABLE = "aimock_raw_mode_unavailable";
77
+ /** The error code for a raw fixture that fails its checks or targets another wire. */
78
+ const RAW_FIXTURE_INVALID = "aimock_raw_fixture_invalid";
79
+ /**
80
+ * The raw events as a frame codec for the shared frame loop. Its headers are
81
+ * the typed stream's defaults for `wire` with `raw.headers` laid over them.
82
+ */
83
+ function rawCodec(wire, raw) {
84
+ return {
85
+ headers: overrideHeaders(rawStreamHeaders(wire), raw.headers),
86
+ encode: (event) => encodeRawFrame(wire, event),
87
+ trailer: (aborted) => {
88
+ const trailer = rawTrailer(wire, raw, aborted);
89
+ return trailer === void 0 ? [] : [trailer];
90
+ }
91
+ };
92
+ }
93
+ /**
94
+ * The error body for a raw 500, in the dialect of the handler's misbehavior
95
+ * errors. Dialects with no code slot (Gemini, Ollama, OpenRouter) carry the
96
+ * code as a message prefix.
97
+ */
98
+ function renderRawError(ctx, code, message) {
99
+ if (ctx.openRouter) return { body: require_openrouter_chat.serializeOpenRouterError(500, `${code}: ${message}`) };
100
+ switch (ctx.errorWire) {
101
+ case "openai": return require_errors.renderError("openai", "misbehavior", {
102
+ message,
103
+ type: "server_error",
104
+ ...ctx.wire === "openai-chat" ? {
105
+ param: null,
106
+ includeParam: true
107
+ } : {},
108
+ code
109
+ });
110
+ case "messages": return require_errors.renderError("messages", "misbehavior", {
111
+ type: "api_error",
112
+ code,
113
+ message
114
+ });
115
+ case "gemini": return require_errors.renderError("gemini", "misbehavior", {
116
+ bodyCode: 500,
117
+ message: `${code}: ${message}`,
118
+ status: "INTERNAL"
119
+ });
120
+ case "bedrock":
121
+ case "bedrock-converse": return require_errors.renderError(ctx.errorWire, "misbehavior", {
122
+ type: "InternalServerException",
123
+ code,
124
+ message
125
+ });
126
+ default: return require_errors.renderError(ctx.errorWire, "misbehavior", {
127
+ code,
128
+ message
129
+ });
130
+ }
131
+ }
132
+ /**
133
+ * Serve a raw passthrough fixture on the handler's wire: check it, journal it,
134
+ * and send its events or JSON through the shared delivery layer. A fixture
135
+ * that fails a check, targets another wire, or has no body for the requested
136
+ * mode gets a 500 in the handler's misbehavior error dialect, and an error log
137
+ * line. A reply with status 400 or more is never delayed, streaming or not,
138
+ * and one with `json` answers in JSON even to a streaming request.
139
+ */
140
+ async function serveRawResponse(ctx) {
141
+ const { wire, stream, req, res, fixture, journal, defaults, testId } = ctx;
142
+ const raw = ctx.response.raw;
143
+ const journalBase = {
144
+ method: req.method ?? "POST",
145
+ path: req.url ?? "/",
146
+ headers: require_helpers.flattenHeaders(req.headers),
147
+ body: ctx.body
148
+ };
149
+ const record = (entry) => require_misbehavior.recordMisbehaviorOutcome({
150
+ entry,
151
+ summary: ctx.misbehaviorSummary,
152
+ defaults,
153
+ testId
154
+ });
155
+ const status = raw.status ?? 200;
156
+ const errorReply = status >= 400;
157
+ const servesStream = stream && !(errorReply && raw.json !== void 0);
158
+ let failure;
159
+ const problems = require_raw_validation.checkRawResponse(raw, require_fixture_loader.rawFixtureContext(fixture, ctx.response)).filter((finding) => finding.severity === "error");
160
+ if (problems.length > 0) failure = {
161
+ code: RAW_FIXTURE_INVALID,
162
+ message: `invalid raw fixture: ${problems.map((p) => p.message).join("; ")}`
163
+ };
164
+ else if (raw.wire !== wire) failure = {
165
+ code: RAW_FIXTURE_INVALID,
166
+ message: `raw fixture is for ${raw.wire}, not ${wire}`
167
+ };
168
+ else if (servesStream ? raw.events === void 0 : raw.json === void 0) failure = {
169
+ code: RAW_MODE_UNAVAILABLE,
170
+ message: `raw fixture has no "${servesStream ? "events" : "json"}" body for a ${servesStream ? "streaming" : "non-streaming"} request on ${wire}`
171
+ };
172
+ if (failure) {
173
+ defaults.logger.error(`${journalBase.method} ${journalBase.path}: ${failure.message}`);
174
+ const entry = journal.add({
175
+ ...journalBase,
176
+ response: {
177
+ status: 500,
178
+ fixture
179
+ }
180
+ });
181
+ entry.response.error = failure.message;
182
+ record(entry);
183
+ const rendered = renderRawError(ctx, failure.code, failure.message);
184
+ for (const [name, value] of Object.entries(rendered.headers ?? {})) res.setHeader(name, value);
185
+ require_sse_writer.writeErrorResponse(res, 500, rendered.body);
186
+ return;
187
+ }
188
+ const entry = journal.add({
189
+ ...journalBase,
190
+ response: {
191
+ status,
192
+ fixture
193
+ }
194
+ });
195
+ record(entry);
196
+ if (servesStream) {
197
+ const events = raw.events;
198
+ if (events === void 0) throw new Error(`raw serve internal error: a streamed reply on ${wire} has no events`);
199
+ await require_deliver.deliverStream(res, entry, fixture, rawCodec(wire, raw), events, errorReply ? { latency: 0 } : {
200
+ latency: fixture.latency ?? defaults.latency,
201
+ streamingProfile: fixture.streamingProfile,
202
+ recordedTimings: fixture.recordedTimings,
203
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
204
+ }, { status: raw.status });
205
+ return;
206
+ }
207
+ const json = raw.json;
208
+ if (json === void 0) throw new Error(`raw serve internal error: a JSON reply on ${wire} has no json`);
209
+ const servedLength = JSON.stringify(json).length;
210
+ await require_deliver.deliverJson(res, entry, errorReply ? {
211
+ ...fixture,
212
+ nonStreamingTiming: false,
213
+ nonStreamingTimingAllProviders: false
214
+ } : fixture, () => json, {
215
+ surface: wire === "openai-chat" ? "openai-chat" : "other",
216
+ status: raw.status,
217
+ payloadLength: servedLength,
218
+ chunkSize: Math.max(1, fixture.chunkSize ?? defaults.chunkSize),
219
+ recordedTimings: fixture.recordedTimings,
220
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
221
+ headers: overrideHeaders(JSON_HEADERS, raw.headers)
222
+ });
223
+ }
224
+
225
+ //#endregion
226
+ exports.RAW_MODE_UNAVAILABLE = RAW_MODE_UNAVAILABLE;
227
+ exports.serveRawResponse = serveRawResponse;
228
+ //# sourceMappingURL=raw.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raw.cjs","names":["sseCodec","ndjsonCodec","eventStreamCodec","encodeEventStreamMessage","serializeOpenRouterError","renderError","flattenHeaders","recordMisbehaviorOutcome","checkRawResponse","rawFixtureContext","deliverStream","deliverJson"],"sources":["../../src/response/raw.ts"],"sourcesContent":["/**\n * @internal Raw passthrough fixtures (#549): per-wire framing and serving.\n *\n * A raw fixture supplies event objects; aimock adds only the named wire's\n * framing, using exactly the bytes the typed handlers write for that wire:\n *\n * - named SSE (`openai-responses`, `anthropic`, `cohere`):\n * `event: <type>\\ndata: <json>\\n\\n`\n * - data SSE (`openai-chat`, `gemini`, `gemini-interactions`): `data: <json>\\n\\n`;\n * `openai-chat` ends with `data: [DONE]\\n\\n` unless `raw.done === false`\n * - NDJSON (`ollama`, `ollama-generate`): `<json>\\n`\n * - AWS event stream: `bedrock-invoke` wraps each event as a `chunk` frame with\n * base64 `bytes`; `bedrock-converse` takes the event's single key as the\n * frame's event type and its value as the payload.\n *\n * The framing functions are pure. `serveRawResponse` checks and journals the\n * fixture. It writes one reply itself: the untimed 500 for a fixture that\n * fails a check, targets another wire or has no body for the request mode.\n * Every other reply goes through the shared delivery layer (`deliverStream`,\n * `deliverJson`), which owns the delay loop, writability checks and abort\n * handling. A reply with a status of 400 or more is never delayed.\n */\nimport type * as http from \"node:http\";\nimport { encodeEventStreamMessage, eventStreamCodec } from \"../aws-event-stream.js\";\nimport { rawFixtureContext } from \"../fixture-loader.js\";\nimport { flattenHeaders } from \"../helpers.js\";\nimport type { Journal } from \"../journal.js\";\nimport { recordMisbehaviorOutcome, type MisbehaviorSummary } from \"../misbehavior.js\";\nimport { serializeOpenRouterError } from \"../openrouter-chat.js\";\nimport { ndjsonCodec } from \"../ndjson-writer.js\";\nimport { checkRawResponse } from \"../raw-validation.js\";\nimport { sseCodec, writeErrorResponse } from \"../sse-writer.js\";\nimport type { Fixture, HandlerDefaults, JournalEntry, RawResponse, RawWireId } from \"../types.js\";\nimport { deliverJson, deliverStream } from \"./deliver.js\";\nimport { renderError } from \"./errors.js\";\nimport type { FrameCodec } from \"./frame-loop.js\";\nimport type { ErrorWire } from \"./profiles.js\";\n\n/** The streaming Content-Type each wire's typed handler sends. */\nexport function rawContentType(wire: RawWireId): string {\n switch (wire) {\n case \"openai-responses\":\n case \"openai-chat\":\n case \"anthropic\":\n case \"gemini\":\n case \"gemini-interactions\":\n case \"cohere\":\n return \"text/event-stream\";\n case \"ollama\":\n case \"ollama-generate\":\n return \"application/x-ndjson\";\n case \"bedrock-invoke\":\n case \"bedrock-converse\":\n return \"application/vnd.amazon.eventstream\";\n }\n}\n\n/**\n * The default headers of the typed stream on `wire`, read from the typed\n * codec: SSE and NDJSON add `Cache-Control: no-cache` and\n * `Connection: keep-alive`, the AWS event stream `Transfer-Encoding: chunked`.\n */\nfunction rawStreamHeaders(wire: RawWireId): Record<string, string> {\n switch (wire) {\n case \"openai-responses\":\n case \"openai-chat\":\n case \"anthropic\":\n case \"gemini\":\n case \"gemini-interactions\":\n case \"cohere\":\n return sseCodec({ done: false }).headers;\n case \"ollama\":\n case \"ollama-generate\":\n return ndjsonCodec().headers;\n case \"bedrock-invoke\":\n case \"bedrock-converse\":\n return eventStreamCodec().headers;\n }\n}\n\n/** The non-streaming JSON default, as `deliverJson` sends it. */\nconst JSON_HEADERS: Readonly<Record<string, string>> = { \"Content-Type\": \"application/json\" };\n\n/**\n * `defaults` with each `authored` header laid over it. Header names are\n * case-insensitive: an authored header replaces the default of the same name\n * in any letter case and takes the default's spelling, so a later spread over\n * the same defaults (as `deliverJson` does) still has one key per name.\n */\nfunction overrideHeaders(\n defaults: Readonly<Record<string, string>>,\n authored: Readonly<Record<string, string>> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = { ...defaults };\n const spelling = new Map(Object.keys(defaults).map((name) => [name.toLowerCase(), name]));\n for (const [name, value] of Object.entries(authored ?? {})) {\n merged[spelling.get(name.toLowerCase()) ?? name] = value;\n }\n return merged;\n}\n\n/** One raw event, framed for `wire`. Events are checked before framing (raw-validation.ts). */\nexport function encodeRawFrame(wire: RawWireId, event: Record<string, unknown>): string | Buffer {\n const json = JSON.stringify(event);\n switch (wire) {\n case \"openai-responses\":\n case \"anthropic\":\n case \"cohere\":\n return `event: ${String(event.type)}\\ndata: ${json}\\n\\n`;\n case \"openai-chat\":\n case \"gemini\":\n case \"gemini-interactions\":\n return `data: ${json}\\n\\n`;\n case \"ollama\":\n case \"ollama-generate\":\n return `${json}\\n`;\n case \"bedrock-invoke\":\n return encodeEventStreamMessage(\"chunk\", {\n bytes: Buffer.from(json, \"utf8\").toString(\"base64\"),\n });\n case \"bedrock-converse\": {\n // The checker guarantees exactly one key whose value is a JSON object.\n const [eventType] = Object.keys(event);\n const payload = event[eventType];\n if (payload === null || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new Error(\n `raw framing internal error: bedrock-converse event \"${eventType}\" has no object payload`,\n );\n }\n return encodeEventStreamMessage(eventType, payload);\n }\n }\n}\n\n/**\n * Bytes written after the last frame, if any. Only `openai-chat` has a\n * terminal (`data: [DONE]`), sent unless `raw.done === false` and never after\n * an interrupted stream.\n */\nexport function rawTrailer(\n wire: RawWireId,\n raw: RawResponse[\"raw\"],\n aborted: boolean,\n): string | undefined {\n if (wire === \"openai-chat\" && !aborted && raw.done !== false) return \"data: [DONE]\\n\\n\";\n return undefined;\n}\n\n// ─── Serving ──────────────────────────────────────────────────────────────────\n\n/** The error code for a request the raw fixture has no body for. */\nexport const RAW_MODE_UNAVAILABLE = \"aimock_raw_mode_unavailable\";\n/** The error code for a raw fixture that fails its checks or targets another wire. */\nexport const RAW_FIXTURE_INVALID = \"aimock_raw_fixture_invalid\";\n\n/** Everything {@link serveRawResponse} needs from the calling handler. */\nexport interface RawServeContext {\n wire: RawWireId;\n /** Whether the request asked for a stream, as the handler decides it. */\n stream: boolean;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n fixture: Fixture;\n response: RawResponse;\n /** The journaled request body, as the handler journals it. */\n body: JournalEntry[\"body\"];\n journal: Journal;\n defaults: HandlerDefaults;\n testId: string;\n misbehaviorSummary?: MisbehaviorSummary;\n /** The dialect of the handler's own `renderError(…, \"misbehavior\", …)` call. */\n errorWire: ErrorWire;\n /** OpenAI chat requests that arrived on the OpenRouter route. */\n openRouter?: boolean;\n}\n\n/**\n * The raw events as a frame codec for the shared frame loop. Its headers are\n * the typed stream's defaults for `wire` with `raw.headers` laid over them.\n */\nexport function rawCodec(\n wire: RawWireId,\n raw: RawResponse[\"raw\"],\n): FrameCodec<Record<string, unknown>> {\n return {\n headers: overrideHeaders(rawStreamHeaders(wire), raw.headers),\n encode: (event) => encodeRawFrame(wire, event),\n trailer: (aborted) => {\n const trailer = rawTrailer(wire, raw, aborted);\n return trailer === undefined ? [] : [trailer];\n },\n };\n}\n\n/**\n * The error body for a raw 500, in the dialect of the handler's misbehavior\n * errors. Dialects with no code slot (Gemini, Ollama, OpenRouter) carry the\n * code as a message prefix.\n */\nfunction renderRawError(\n ctx: RawServeContext,\n code: string,\n message: string,\n): { body: string; headers?: Record<string, string> } {\n if (ctx.openRouter) return { body: serializeOpenRouterError(500, `${code}: ${message}`) };\n switch (ctx.errorWire) {\n case \"openai\":\n return renderError(\"openai\", \"misbehavior\", {\n message,\n type: \"server_error\",\n ...(ctx.wire === \"openai-chat\" ? { param: null, includeParam: true as const } : {}),\n code,\n });\n case \"messages\":\n return renderError(\"messages\", \"misbehavior\", { type: \"api_error\", code, message });\n case \"gemini\":\n return renderError(\"gemini\", \"misbehavior\", {\n bodyCode: 500,\n message: `${code}: ${message}`,\n status: \"INTERNAL\",\n });\n case \"bedrock\":\n case \"bedrock-converse\":\n return renderError(ctx.errorWire, \"misbehavior\", {\n type: \"InternalServerException\",\n code,\n message,\n });\n default:\n return renderError(ctx.errorWire, \"misbehavior\", { code, message });\n }\n}\n\n/**\n * Serve a raw passthrough fixture on the handler's wire: check it, journal it,\n * and send its events or JSON through the shared delivery layer. A fixture\n * that fails a check, targets another wire, or has no body for the requested\n * mode gets a 500 in the handler's misbehavior error dialect, and an error log\n * line. A reply with status 400 or more is never delayed, streaming or not,\n * and one with `json` answers in JSON even to a streaming request.\n */\nexport async function serveRawResponse(ctx: RawServeContext): Promise<void> {\n const { wire, stream, req, res, fixture, journal, defaults, testId } = ctx;\n const raw = ctx.response.raw;\n const journalBase = {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: ctx.body,\n };\n const record = (entry: JournalEntry): void =>\n recordMisbehaviorOutcome({ entry, summary: ctx.misbehaviorSummary, defaults, testId });\n\n const status = raw.status ?? 200;\n // An error reply (status >= 400) is never delayed, streaming or not, as\n // typed error replies are not. Fixture interruptions still apply.\n const errorReply = status >= 400;\n // An error status with a JSON body answers in JSON even to a streaming\n // request, as provider APIs do; the JSON wins over any events.\n const servesStream = stream && !(errorReply && raw.json !== undefined);\n\n let failure: { code: string; message: string } | undefined;\n const problems = checkRawResponse(raw, rawFixtureContext(fixture, ctx.response)).filter(\n (finding) => finding.severity === \"error\",\n );\n if (problems.length > 0) {\n failure = {\n code: RAW_FIXTURE_INVALID,\n message: `invalid raw fixture: ${problems.map((p) => p.message).join(\"; \")}`,\n };\n } else if (raw.wire !== wire) {\n failure = { code: RAW_FIXTURE_INVALID, message: `raw fixture is for ${raw.wire}, not ${wire}` };\n } else if (servesStream ? raw.events === undefined : raw.json === undefined) {\n failure = {\n code: RAW_MODE_UNAVAILABLE,\n message: `raw fixture has no \"${servesStream ? \"events\" : \"json\"}\" body for a ${\n servesStream ? \"streaming\" : \"non-streaming\"\n } request on ${wire}`,\n };\n }\n if (failure) {\n defaults.logger.error(`${journalBase.method} ${journalBase.path}: ${failure.message}`);\n const entry = journal.add({ ...journalBase, response: { status: 500, fixture } });\n entry.response.error = failure.message;\n record(entry);\n const rendered = renderRawError(ctx, failure.code, failure.message);\n for (const [name, value] of Object.entries(rendered.headers ?? {})) res.setHeader(name, value);\n writeErrorResponse(res, 500, rendered.body);\n return;\n }\n\n const entry = journal.add({ ...journalBase, response: { status, fixture } });\n record(entry);\n\n if (servesStream) {\n const events = raw.events;\n if (events === undefined) {\n throw new Error(`raw serve internal error: a streamed reply on ${wire} has no events`);\n }\n await deliverStream(\n res,\n entry,\n fixture,\n rawCodec(wire, raw),\n events,\n errorReply\n ? { latency: 0 }\n : {\n latency: fixture.latency ?? defaults.latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\n },\n // The codec already carries raw.headers over its defaults.\n { status: raw.status },\n );\n return;\n }\n\n // The wait is sized from the served JSON, as typed replies size theirs from\n // the served content, in chunks of the fixture's or server's chunk size.\n const json = raw.json;\n if (json === undefined) {\n throw new Error(`raw serve internal error: a JSON reply on ${wire} has no json`);\n }\n const servedLength = JSON.stringify(json).length;\n // deliverJson reads the timing opt-ins from the fixture. With them off, an\n // error reply still gets its writability check and journaling, but no wait.\n await deliverJson(\n res,\n entry,\n errorReply\n ? { ...fixture, nonStreamingTiming: false, nonStreamingTimingAllProviders: false }\n : fixture,\n () => json,\n {\n surface: wire === \"openai-chat\" ? \"openai-chat\" : \"other\",\n status: raw.status,\n payloadLength: servedLength,\n chunkSize: Math.max(1, fixture.chunkSize ?? defaults.chunkSize),\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\n headers: overrideHeaders(JSON_HEADERS, raw.headers),\n },\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,227 @@
1
+ import { renderError } from "./errors.js";
2
+ import { flattenHeaders } from "../helpers.js";
3
+ import { checkRawResponse } from "../raw-validation.js";
4
+ import { sseCodec, writeErrorResponse } from "../sse-writer.js";
5
+ import { recordMisbehaviorOutcome } from "../misbehavior.js";
6
+ import { rawFixtureContext } from "../fixture-loader.js";
7
+ import { deliverJson, deliverStream } from "./deliver.js";
8
+ import { encodeEventStreamMessage, eventStreamCodec } from "../aws-event-stream.js";
9
+ import { serializeOpenRouterError } from "../openrouter-chat.js";
10
+ import { ndjsonCodec } from "../ndjson-writer.js";
11
+
12
+ //#region src/response/raw.ts
13
+ /**
14
+ * The default headers of the typed stream on `wire`, read from the typed
15
+ * codec: SSE and NDJSON add `Cache-Control: no-cache` and
16
+ * `Connection: keep-alive`, the AWS event stream `Transfer-Encoding: chunked`.
17
+ */
18
+ function rawStreamHeaders(wire) {
19
+ switch (wire) {
20
+ case "openai-responses":
21
+ case "openai-chat":
22
+ case "anthropic":
23
+ case "gemini":
24
+ case "gemini-interactions":
25
+ case "cohere": return sseCodec({ done: false }).headers;
26
+ case "ollama":
27
+ case "ollama-generate": return ndjsonCodec().headers;
28
+ case "bedrock-invoke":
29
+ case "bedrock-converse": return eventStreamCodec().headers;
30
+ }
31
+ }
32
+ /** The non-streaming JSON default, as `deliverJson` sends it. */
33
+ const JSON_HEADERS = { "Content-Type": "application/json" };
34
+ /**
35
+ * `defaults` with each `authored` header laid over it. Header names are
36
+ * case-insensitive: an authored header replaces the default of the same name
37
+ * in any letter case and takes the default's spelling, so a later spread over
38
+ * the same defaults (as `deliverJson` does) still has one key per name.
39
+ */
40
+ function overrideHeaders(defaults, authored) {
41
+ const merged = { ...defaults };
42
+ const spelling = new Map(Object.keys(defaults).map((name) => [name.toLowerCase(), name]));
43
+ for (const [name, value] of Object.entries(authored ?? {})) merged[spelling.get(name.toLowerCase()) ?? name] = value;
44
+ return merged;
45
+ }
46
+ /** One raw event, framed for `wire`. Events are checked before framing (raw-validation.ts). */
47
+ function encodeRawFrame(wire, event) {
48
+ const json = JSON.stringify(event);
49
+ switch (wire) {
50
+ case "openai-responses":
51
+ case "anthropic":
52
+ case "cohere": return `event: ${String(event.type)}\ndata: ${json}\n\n`;
53
+ case "openai-chat":
54
+ case "gemini":
55
+ case "gemini-interactions": return `data: ${json}\n\n`;
56
+ case "ollama":
57
+ case "ollama-generate": return `${json}\n`;
58
+ case "bedrock-invoke": return encodeEventStreamMessage("chunk", { bytes: Buffer.from(json, "utf8").toString("base64") });
59
+ case "bedrock-converse": {
60
+ const [eventType] = Object.keys(event);
61
+ const payload = event[eventType];
62
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) throw new Error(`raw framing internal error: bedrock-converse event "${eventType}" has no object payload`);
63
+ return encodeEventStreamMessage(eventType, payload);
64
+ }
65
+ }
66
+ }
67
+ /**
68
+ * Bytes written after the last frame, if any. Only `openai-chat` has a
69
+ * terminal (`data: [DONE]`), sent unless `raw.done === false` and never after
70
+ * an interrupted stream.
71
+ */
72
+ function rawTrailer(wire, raw, aborted) {
73
+ if (wire === "openai-chat" && !aborted && raw.done !== false) return "data: [DONE]\n\n";
74
+ }
75
+ /** The error code for a request the raw fixture has no body for. */
76
+ const RAW_MODE_UNAVAILABLE = "aimock_raw_mode_unavailable";
77
+ /** The error code for a raw fixture that fails its checks or targets another wire. */
78
+ const RAW_FIXTURE_INVALID = "aimock_raw_fixture_invalid";
79
+ /**
80
+ * The raw events as a frame codec for the shared frame loop. Its headers are
81
+ * the typed stream's defaults for `wire` with `raw.headers` laid over them.
82
+ */
83
+ function rawCodec(wire, raw) {
84
+ return {
85
+ headers: overrideHeaders(rawStreamHeaders(wire), raw.headers),
86
+ encode: (event) => encodeRawFrame(wire, event),
87
+ trailer: (aborted) => {
88
+ const trailer = rawTrailer(wire, raw, aborted);
89
+ return trailer === void 0 ? [] : [trailer];
90
+ }
91
+ };
92
+ }
93
+ /**
94
+ * The error body for a raw 500, in the dialect of the handler's misbehavior
95
+ * errors. Dialects with no code slot (Gemini, Ollama, OpenRouter) carry the
96
+ * code as a message prefix.
97
+ */
98
+ function renderRawError(ctx, code, message) {
99
+ if (ctx.openRouter) return { body: serializeOpenRouterError(500, `${code}: ${message}`) };
100
+ switch (ctx.errorWire) {
101
+ case "openai": return renderError("openai", "misbehavior", {
102
+ message,
103
+ type: "server_error",
104
+ ...ctx.wire === "openai-chat" ? {
105
+ param: null,
106
+ includeParam: true
107
+ } : {},
108
+ code
109
+ });
110
+ case "messages": return renderError("messages", "misbehavior", {
111
+ type: "api_error",
112
+ code,
113
+ message
114
+ });
115
+ case "gemini": return renderError("gemini", "misbehavior", {
116
+ bodyCode: 500,
117
+ message: `${code}: ${message}`,
118
+ status: "INTERNAL"
119
+ });
120
+ case "bedrock":
121
+ case "bedrock-converse": return renderError(ctx.errorWire, "misbehavior", {
122
+ type: "InternalServerException",
123
+ code,
124
+ message
125
+ });
126
+ default: return renderError(ctx.errorWire, "misbehavior", {
127
+ code,
128
+ message
129
+ });
130
+ }
131
+ }
132
+ /**
133
+ * Serve a raw passthrough fixture on the handler's wire: check it, journal it,
134
+ * and send its events or JSON through the shared delivery layer. A fixture
135
+ * that fails a check, targets another wire, or has no body for the requested
136
+ * mode gets a 500 in the handler's misbehavior error dialect, and an error log
137
+ * line. A reply with status 400 or more is never delayed, streaming or not,
138
+ * and one with `json` answers in JSON even to a streaming request.
139
+ */
140
+ async function serveRawResponse(ctx) {
141
+ const { wire, stream, req, res, fixture, journal, defaults, testId } = ctx;
142
+ const raw = ctx.response.raw;
143
+ const journalBase = {
144
+ method: req.method ?? "POST",
145
+ path: req.url ?? "/",
146
+ headers: flattenHeaders(req.headers),
147
+ body: ctx.body
148
+ };
149
+ const record = (entry) => recordMisbehaviorOutcome({
150
+ entry,
151
+ summary: ctx.misbehaviorSummary,
152
+ defaults,
153
+ testId
154
+ });
155
+ const status = raw.status ?? 200;
156
+ const errorReply = status >= 400;
157
+ const servesStream = stream && !(errorReply && raw.json !== void 0);
158
+ let failure;
159
+ const problems = checkRawResponse(raw, rawFixtureContext(fixture, ctx.response)).filter((finding) => finding.severity === "error");
160
+ if (problems.length > 0) failure = {
161
+ code: RAW_FIXTURE_INVALID,
162
+ message: `invalid raw fixture: ${problems.map((p) => p.message).join("; ")}`
163
+ };
164
+ else if (raw.wire !== wire) failure = {
165
+ code: RAW_FIXTURE_INVALID,
166
+ message: `raw fixture is for ${raw.wire}, not ${wire}`
167
+ };
168
+ else if (servesStream ? raw.events === void 0 : raw.json === void 0) failure = {
169
+ code: RAW_MODE_UNAVAILABLE,
170
+ message: `raw fixture has no "${servesStream ? "events" : "json"}" body for a ${servesStream ? "streaming" : "non-streaming"} request on ${wire}`
171
+ };
172
+ if (failure) {
173
+ defaults.logger.error(`${journalBase.method} ${journalBase.path}: ${failure.message}`);
174
+ const entry = journal.add({
175
+ ...journalBase,
176
+ response: {
177
+ status: 500,
178
+ fixture
179
+ }
180
+ });
181
+ entry.response.error = failure.message;
182
+ record(entry);
183
+ const rendered = renderRawError(ctx, failure.code, failure.message);
184
+ for (const [name, value] of Object.entries(rendered.headers ?? {})) res.setHeader(name, value);
185
+ writeErrorResponse(res, 500, rendered.body);
186
+ return;
187
+ }
188
+ const entry = journal.add({
189
+ ...journalBase,
190
+ response: {
191
+ status,
192
+ fixture
193
+ }
194
+ });
195
+ record(entry);
196
+ if (servesStream) {
197
+ const events = raw.events;
198
+ if (events === void 0) throw new Error(`raw serve internal error: a streamed reply on ${wire} has no events`);
199
+ await deliverStream(res, entry, fixture, rawCodec(wire, raw), events, errorReply ? { latency: 0 } : {
200
+ latency: fixture.latency ?? defaults.latency,
201
+ streamingProfile: fixture.streamingProfile,
202
+ recordedTimings: fixture.recordedTimings,
203
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
204
+ }, { status: raw.status });
205
+ return;
206
+ }
207
+ const json = raw.json;
208
+ if (json === void 0) throw new Error(`raw serve internal error: a JSON reply on ${wire} has no json`);
209
+ const servedLength = JSON.stringify(json).length;
210
+ await deliverJson(res, entry, errorReply ? {
211
+ ...fixture,
212
+ nonStreamingTiming: false,
213
+ nonStreamingTimingAllProviders: false
214
+ } : fixture, () => json, {
215
+ surface: wire === "openai-chat" ? "openai-chat" : "other",
216
+ status: raw.status,
217
+ payloadLength: servedLength,
218
+ chunkSize: Math.max(1, fixture.chunkSize ?? defaults.chunkSize),
219
+ recordedTimings: fixture.recordedTimings,
220
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
221
+ headers: overrideHeaders(JSON_HEADERS, raw.headers)
222
+ });
223
+ }
224
+
225
+ //#endregion
226
+ export { RAW_MODE_UNAVAILABLE, serveRawResponse };
227
+ //# sourceMappingURL=raw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raw.js","names":[],"sources":["../../src/response/raw.ts"],"sourcesContent":["/**\n * @internal Raw passthrough fixtures (#549): per-wire framing and serving.\n *\n * A raw fixture supplies event objects; aimock adds only the named wire's\n * framing, using exactly the bytes the typed handlers write for that wire:\n *\n * - named SSE (`openai-responses`, `anthropic`, `cohere`):\n * `event: <type>\\ndata: <json>\\n\\n`\n * - data SSE (`openai-chat`, `gemini`, `gemini-interactions`): `data: <json>\\n\\n`;\n * `openai-chat` ends with `data: [DONE]\\n\\n` unless `raw.done === false`\n * - NDJSON (`ollama`, `ollama-generate`): `<json>\\n`\n * - AWS event stream: `bedrock-invoke` wraps each event as a `chunk` frame with\n * base64 `bytes`; `bedrock-converse` takes the event's single key as the\n * frame's event type and its value as the payload.\n *\n * The framing functions are pure. `serveRawResponse` checks and journals the\n * fixture. It writes one reply itself: the untimed 500 for a fixture that\n * fails a check, targets another wire or has no body for the request mode.\n * Every other reply goes through the shared delivery layer (`deliverStream`,\n * `deliverJson`), which owns the delay loop, writability checks and abort\n * handling. A reply with a status of 400 or more is never delayed.\n */\nimport type * as http from \"node:http\";\nimport { encodeEventStreamMessage, eventStreamCodec } from \"../aws-event-stream.js\";\nimport { rawFixtureContext } from \"../fixture-loader.js\";\nimport { flattenHeaders } from \"../helpers.js\";\nimport type { Journal } from \"../journal.js\";\nimport { recordMisbehaviorOutcome, type MisbehaviorSummary } from \"../misbehavior.js\";\nimport { serializeOpenRouterError } from \"../openrouter-chat.js\";\nimport { ndjsonCodec } from \"../ndjson-writer.js\";\nimport { checkRawResponse } from \"../raw-validation.js\";\nimport { sseCodec, writeErrorResponse } from \"../sse-writer.js\";\nimport type { Fixture, HandlerDefaults, JournalEntry, RawResponse, RawWireId } from \"../types.js\";\nimport { deliverJson, deliverStream } from \"./deliver.js\";\nimport { renderError } from \"./errors.js\";\nimport type { FrameCodec } from \"./frame-loop.js\";\nimport type { ErrorWire } from \"./profiles.js\";\n\n/** The streaming Content-Type each wire's typed handler sends. */\nexport function rawContentType(wire: RawWireId): string {\n switch (wire) {\n case \"openai-responses\":\n case \"openai-chat\":\n case \"anthropic\":\n case \"gemini\":\n case \"gemini-interactions\":\n case \"cohere\":\n return \"text/event-stream\";\n case \"ollama\":\n case \"ollama-generate\":\n return \"application/x-ndjson\";\n case \"bedrock-invoke\":\n case \"bedrock-converse\":\n return \"application/vnd.amazon.eventstream\";\n }\n}\n\n/**\n * The default headers of the typed stream on `wire`, read from the typed\n * codec: SSE and NDJSON add `Cache-Control: no-cache` and\n * `Connection: keep-alive`, the AWS event stream `Transfer-Encoding: chunked`.\n */\nfunction rawStreamHeaders(wire: RawWireId): Record<string, string> {\n switch (wire) {\n case \"openai-responses\":\n case \"openai-chat\":\n case \"anthropic\":\n case \"gemini\":\n case \"gemini-interactions\":\n case \"cohere\":\n return sseCodec({ done: false }).headers;\n case \"ollama\":\n case \"ollama-generate\":\n return ndjsonCodec().headers;\n case \"bedrock-invoke\":\n case \"bedrock-converse\":\n return eventStreamCodec().headers;\n }\n}\n\n/** The non-streaming JSON default, as `deliverJson` sends it. */\nconst JSON_HEADERS: Readonly<Record<string, string>> = { \"Content-Type\": \"application/json\" };\n\n/**\n * `defaults` with each `authored` header laid over it. Header names are\n * case-insensitive: an authored header replaces the default of the same name\n * in any letter case and takes the default's spelling, so a later spread over\n * the same defaults (as `deliverJson` does) still has one key per name.\n */\nfunction overrideHeaders(\n defaults: Readonly<Record<string, string>>,\n authored: Readonly<Record<string, string>> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = { ...defaults };\n const spelling = new Map(Object.keys(defaults).map((name) => [name.toLowerCase(), name]));\n for (const [name, value] of Object.entries(authored ?? {})) {\n merged[spelling.get(name.toLowerCase()) ?? name] = value;\n }\n return merged;\n}\n\n/** One raw event, framed for `wire`. Events are checked before framing (raw-validation.ts). */\nexport function encodeRawFrame(wire: RawWireId, event: Record<string, unknown>): string | Buffer {\n const json = JSON.stringify(event);\n switch (wire) {\n case \"openai-responses\":\n case \"anthropic\":\n case \"cohere\":\n return `event: ${String(event.type)}\\ndata: ${json}\\n\\n`;\n case \"openai-chat\":\n case \"gemini\":\n case \"gemini-interactions\":\n return `data: ${json}\\n\\n`;\n case \"ollama\":\n case \"ollama-generate\":\n return `${json}\\n`;\n case \"bedrock-invoke\":\n return encodeEventStreamMessage(\"chunk\", {\n bytes: Buffer.from(json, \"utf8\").toString(\"base64\"),\n });\n case \"bedrock-converse\": {\n // The checker guarantees exactly one key whose value is a JSON object.\n const [eventType] = Object.keys(event);\n const payload = event[eventType];\n if (payload === null || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new Error(\n `raw framing internal error: bedrock-converse event \"${eventType}\" has no object payload`,\n );\n }\n return encodeEventStreamMessage(eventType, payload);\n }\n }\n}\n\n/**\n * Bytes written after the last frame, if any. Only `openai-chat` has a\n * terminal (`data: [DONE]`), sent unless `raw.done === false` and never after\n * an interrupted stream.\n */\nexport function rawTrailer(\n wire: RawWireId,\n raw: RawResponse[\"raw\"],\n aborted: boolean,\n): string | undefined {\n if (wire === \"openai-chat\" && !aborted && raw.done !== false) return \"data: [DONE]\\n\\n\";\n return undefined;\n}\n\n// ─── Serving ──────────────────────────────────────────────────────────────────\n\n/** The error code for a request the raw fixture has no body for. */\nexport const RAW_MODE_UNAVAILABLE = \"aimock_raw_mode_unavailable\";\n/** The error code for a raw fixture that fails its checks or targets another wire. */\nexport const RAW_FIXTURE_INVALID = \"aimock_raw_fixture_invalid\";\n\n/** Everything {@link serveRawResponse} needs from the calling handler. */\nexport interface RawServeContext {\n wire: RawWireId;\n /** Whether the request asked for a stream, as the handler decides it. */\n stream: boolean;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n fixture: Fixture;\n response: RawResponse;\n /** The journaled request body, as the handler journals it. */\n body: JournalEntry[\"body\"];\n journal: Journal;\n defaults: HandlerDefaults;\n testId: string;\n misbehaviorSummary?: MisbehaviorSummary;\n /** The dialect of the handler's own `renderError(…, \"misbehavior\", …)` call. */\n errorWire: ErrorWire;\n /** OpenAI chat requests that arrived on the OpenRouter route. */\n openRouter?: boolean;\n}\n\n/**\n * The raw events as a frame codec for the shared frame loop. Its headers are\n * the typed stream's defaults for `wire` with `raw.headers` laid over them.\n */\nexport function rawCodec(\n wire: RawWireId,\n raw: RawResponse[\"raw\"],\n): FrameCodec<Record<string, unknown>> {\n return {\n headers: overrideHeaders(rawStreamHeaders(wire), raw.headers),\n encode: (event) => encodeRawFrame(wire, event),\n trailer: (aborted) => {\n const trailer = rawTrailer(wire, raw, aborted);\n return trailer === undefined ? [] : [trailer];\n },\n };\n}\n\n/**\n * The error body for a raw 500, in the dialect of the handler's misbehavior\n * errors. Dialects with no code slot (Gemini, Ollama, OpenRouter) carry the\n * code as a message prefix.\n */\nfunction renderRawError(\n ctx: RawServeContext,\n code: string,\n message: string,\n): { body: string; headers?: Record<string, string> } {\n if (ctx.openRouter) return { body: serializeOpenRouterError(500, `${code}: ${message}`) };\n switch (ctx.errorWire) {\n case \"openai\":\n return renderError(\"openai\", \"misbehavior\", {\n message,\n type: \"server_error\",\n ...(ctx.wire === \"openai-chat\" ? { param: null, includeParam: true as const } : {}),\n code,\n });\n case \"messages\":\n return renderError(\"messages\", \"misbehavior\", { type: \"api_error\", code, message });\n case \"gemini\":\n return renderError(\"gemini\", \"misbehavior\", {\n bodyCode: 500,\n message: `${code}: ${message}`,\n status: \"INTERNAL\",\n });\n case \"bedrock\":\n case \"bedrock-converse\":\n return renderError(ctx.errorWire, \"misbehavior\", {\n type: \"InternalServerException\",\n code,\n message,\n });\n default:\n return renderError(ctx.errorWire, \"misbehavior\", { code, message });\n }\n}\n\n/**\n * Serve a raw passthrough fixture on the handler's wire: check it, journal it,\n * and send its events or JSON through the shared delivery layer. A fixture\n * that fails a check, targets another wire, or has no body for the requested\n * mode gets a 500 in the handler's misbehavior error dialect, and an error log\n * line. A reply with status 400 or more is never delayed, streaming or not,\n * and one with `json` answers in JSON even to a streaming request.\n */\nexport async function serveRawResponse(ctx: RawServeContext): Promise<void> {\n const { wire, stream, req, res, fixture, journal, defaults, testId } = ctx;\n const raw = ctx.response.raw;\n const journalBase = {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: ctx.body,\n };\n const record = (entry: JournalEntry): void =>\n recordMisbehaviorOutcome({ entry, summary: ctx.misbehaviorSummary, defaults, testId });\n\n const status = raw.status ?? 200;\n // An error reply (status >= 400) is never delayed, streaming or not, as\n // typed error replies are not. Fixture interruptions still apply.\n const errorReply = status >= 400;\n // An error status with a JSON body answers in JSON even to a streaming\n // request, as provider APIs do; the JSON wins over any events.\n const servesStream = stream && !(errorReply && raw.json !== undefined);\n\n let failure: { code: string; message: string } | undefined;\n const problems = checkRawResponse(raw, rawFixtureContext(fixture, ctx.response)).filter(\n (finding) => finding.severity === \"error\",\n );\n if (problems.length > 0) {\n failure = {\n code: RAW_FIXTURE_INVALID,\n message: `invalid raw fixture: ${problems.map((p) => p.message).join(\"; \")}`,\n };\n } else if (raw.wire !== wire) {\n failure = { code: RAW_FIXTURE_INVALID, message: `raw fixture is for ${raw.wire}, not ${wire}` };\n } else if (servesStream ? raw.events === undefined : raw.json === undefined) {\n failure = {\n code: RAW_MODE_UNAVAILABLE,\n message: `raw fixture has no \"${servesStream ? \"events\" : \"json\"}\" body for a ${\n servesStream ? \"streaming\" : \"non-streaming\"\n } request on ${wire}`,\n };\n }\n if (failure) {\n defaults.logger.error(`${journalBase.method} ${journalBase.path}: ${failure.message}`);\n const entry = journal.add({ ...journalBase, response: { status: 500, fixture } });\n entry.response.error = failure.message;\n record(entry);\n const rendered = renderRawError(ctx, failure.code, failure.message);\n for (const [name, value] of Object.entries(rendered.headers ?? {})) res.setHeader(name, value);\n writeErrorResponse(res, 500, rendered.body);\n return;\n }\n\n const entry = journal.add({ ...journalBase, response: { status, fixture } });\n record(entry);\n\n if (servesStream) {\n const events = raw.events;\n if (events === undefined) {\n throw new Error(`raw serve internal error: a streamed reply on ${wire} has no events`);\n }\n await deliverStream(\n res,\n entry,\n fixture,\n rawCodec(wire, raw),\n events,\n errorReply\n ? { latency: 0 }\n : {\n latency: fixture.latency ?? defaults.latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\n },\n // The codec already carries raw.headers over its defaults.\n { status: raw.status },\n );\n return;\n }\n\n // The wait is sized from the served JSON, as typed replies size theirs from\n // the served content, in chunks of the fixture's or server's chunk size.\n const json = raw.json;\n if (json === undefined) {\n throw new Error(`raw serve internal error: a JSON reply on ${wire} has no json`);\n }\n const servedLength = JSON.stringify(json).length;\n // deliverJson reads the timing opt-ins from the fixture. With them off, an\n // error reply still gets its writability check and journaling, but no wait.\n await deliverJson(\n res,\n entry,\n errorReply\n ? { ...fixture, nonStreamingTiming: false, nonStreamingTimingAllProviders: false }\n : fixture,\n () => json,\n {\n surface: wire === \"openai-chat\" ? \"openai-chat\" : \"other\",\n status: raw.status,\n payloadLength: servedLength,\n chunkSize: Math.max(1, fixture.chunkSize ?? defaults.chunkSize),\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\n headers: overrideHeaders(JSON_HEADERS, raw.headers),\n },\n );\n}\n"],"mappings":"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"}