@copilotkit/aimock 1.43.0 → 1.44.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 (290) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +8 -2
  5. package/dist/agui-handler.d.ts.map +1 -1
  6. package/dist/agui-types.d.ts.map +1 -1
  7. package/dist/bedrock-converse.cjs +32 -4
  8. package/dist/bedrock-converse.cjs.map +1 -1
  9. package/dist/bedrock-converse.d.cts.map +1 -1
  10. package/dist/bedrock-converse.d.ts.map +1 -1
  11. package/dist/bedrock-converse.js +32 -4
  12. package/dist/bedrock-converse.js.map +1 -1
  13. package/dist/chaos.cjs +8 -5
  14. package/dist/chaos.cjs.map +1 -1
  15. package/dist/chaos.d.cts.map +1 -1
  16. package/dist/chaos.d.ts.map +1 -1
  17. package/dist/chaos.js +9 -6
  18. package/dist/chaos.js.map +1 -1
  19. package/dist/cli.cjs +89 -7
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.js +91 -9
  22. package/dist/cli.js.map +1 -1
  23. package/dist/cohere.cjs +27 -1
  24. package/dist/cohere.cjs.map +1 -1
  25. package/dist/cohere.js +27 -1
  26. package/dist/cohere.js.map +1 -1
  27. package/dist/config-loader.cjs +1 -1
  28. package/dist/config-loader.d.ts.map +1 -1
  29. package/dist/config-loader.js +1 -1
  30. package/dist/constants.cjs +8 -0
  31. package/dist/constants.cjs.map +1 -1
  32. package/dist/constants.d.cts +6 -2
  33. package/dist/constants.d.ts +6 -2
  34. package/dist/constants.js +8 -1
  35. package/dist/constants.js.map +1 -1
  36. package/dist/echo-text.cjs +65 -0
  37. package/dist/echo-text.cjs.map +1 -0
  38. package/dist/echo-text.d.cts +22 -0
  39. package/dist/echo-text.d.cts.map +1 -0
  40. package/dist/echo-text.d.ts +22 -0
  41. package/dist/echo-text.d.ts.map +1 -0
  42. package/dist/echo-text.js +65 -0
  43. package/dist/echo-text.js.map +1 -0
  44. package/dist/embeddings.cjs +18 -1
  45. package/dist/embeddings.cjs.map +1 -1
  46. package/dist/embeddings.d.cts +1 -1
  47. package/dist/embeddings.d.cts.map +1 -1
  48. package/dist/embeddings.d.ts +1 -1
  49. package/dist/embeddings.d.ts.map +1 -1
  50. package/dist/embeddings.js +18 -1
  51. package/dist/embeddings.js.map +1 -1
  52. package/dist/files.cjs +9 -11
  53. package/dist/files.cjs.map +1 -1
  54. package/dist/files.d.cts.map +1 -1
  55. package/dist/files.d.ts.map +1 -1
  56. package/dist/files.js +10 -13
  57. package/dist/files.js.map +1 -1
  58. package/dist/fine-tuning.cjs +1 -19
  59. package/dist/fine-tuning.cjs.map +1 -1
  60. package/dist/fine-tuning.d.cts.map +1 -1
  61. package/dist/fine-tuning.d.ts.map +1 -1
  62. package/dist/fine-tuning.js +2 -20
  63. package/dist/fine-tuning.js.map +1 -1
  64. package/dist/fixture-loader-services.cjs +116 -0
  65. package/dist/fixture-loader-services.cjs.map +1 -0
  66. package/dist/fixture-loader-services.d.cts +48 -0
  67. package/dist/fixture-loader-services.d.cts.map +1 -0
  68. package/dist/fixture-loader-services.d.ts +48 -0
  69. package/dist/fixture-loader-services.d.ts.map +1 -0
  70. package/dist/fixture-loader-services.js +114 -0
  71. package/dist/fixture-loader-services.js.map +1 -0
  72. package/dist/fixture-loader.cjs +172 -11
  73. package/dist/fixture-loader.cjs.map +1 -1
  74. package/dist/fixture-loader.d.cts +95 -1
  75. package/dist/fixture-loader.d.cts.map +1 -1
  76. package/dist/fixture-loader.d.ts +95 -1
  77. package/dist/fixture-loader.d.ts.map +1 -1
  78. package/dist/fixture-loader.js +168 -12
  79. package/dist/fixture-loader.js.map +1 -1
  80. package/dist/gemini-embeddings.cjs +42 -2
  81. package/dist/gemini-embeddings.cjs.map +1 -1
  82. package/dist/gemini-embeddings.js +43 -3
  83. package/dist/gemini-embeddings.js.map +1 -1
  84. package/dist/gemini-interactions.cjs +38 -0
  85. package/dist/gemini-interactions.cjs.map +1 -1
  86. package/dist/gemini-interactions.d.cts.map +1 -1
  87. package/dist/gemini-interactions.d.ts.map +1 -1
  88. package/dist/gemini-interactions.js +38 -0
  89. package/dist/gemini-interactions.js.map +1 -1
  90. package/dist/gemini.cjs +8 -1
  91. package/dist/gemini.cjs.map +1 -1
  92. package/dist/gemini.d.cts.map +1 -1
  93. package/dist/gemini.d.ts.map +1 -1
  94. package/dist/gemini.js +8 -1
  95. package/dist/gemini.js.map +1 -1
  96. package/dist/helpers.cjs +276 -5
  97. package/dist/helpers.cjs.map +1 -1
  98. package/dist/helpers.d.cts.map +1 -1
  99. package/dist/helpers.d.ts.map +1 -1
  100. package/dist/helpers.js +275 -7
  101. package/dist/helpers.js.map +1 -1
  102. package/dist/images.cjs +3 -2
  103. package/dist/images.cjs.map +1 -1
  104. package/dist/images.js +3 -2
  105. package/dist/images.js.map +1 -1
  106. package/dist/index.cjs +30 -1
  107. package/dist/index.d.cts +8 -3
  108. package/dist/index.d.ts +8 -3
  109. package/dist/index.js +9 -3
  110. package/dist/jest.cjs +15 -10
  111. package/dist/jest.cjs.map +1 -1
  112. package/dist/jest.d.cts +4 -2
  113. package/dist/jest.d.cts.map +1 -1
  114. package/dist/jest.d.ts +4 -2
  115. package/dist/jest.d.ts.map +1 -1
  116. package/dist/jest.js +16 -11
  117. package/dist/jest.js.map +1 -1
  118. package/dist/journal.cjs +20 -0
  119. package/dist/journal.cjs.map +1 -1
  120. package/dist/journal.d.cts +16 -0
  121. package/dist/journal.d.cts.map +1 -1
  122. package/dist/journal.d.ts +16 -0
  123. package/dist/journal.d.ts.map +1 -1
  124. package/dist/journal.js +20 -0
  125. package/dist/journal.js.map +1 -1
  126. package/dist/llmock.cjs +82 -10
  127. package/dist/llmock.cjs.map +1 -1
  128. package/dist/llmock.d.cts +35 -0
  129. package/dist/llmock.d.cts.map +1 -1
  130. package/dist/llmock.d.ts +35 -0
  131. package/dist/llmock.d.ts.map +1 -1
  132. package/dist/llmock.js +83 -11
  133. package/dist/llmock.js.map +1 -1
  134. package/dist/mcp-fakes-mount.cjs +216 -0
  135. package/dist/mcp-fakes-mount.cjs.map +1 -0
  136. package/dist/mcp-fakes-mount.js +210 -0
  137. package/dist/mcp-fakes-mount.js.map +1 -0
  138. package/dist/mcp-fakes.cjs +1619 -0
  139. package/dist/mcp-fakes.cjs.map +1 -0
  140. package/dist/mcp-fakes.d.cts +101 -0
  141. package/dist/mcp-fakes.d.cts.map +1 -0
  142. package/dist/mcp-fakes.d.ts +101 -0
  143. package/dist/mcp-fakes.d.ts.map +1 -0
  144. package/dist/mcp-fakes.js +1614 -0
  145. package/dist/mcp-fakes.js.map +1 -0
  146. package/dist/mcp-handler.cjs +331 -17
  147. package/dist/mcp-handler.cjs.map +1 -1
  148. package/dist/mcp-handler.js +330 -18
  149. package/dist/mcp-handler.js.map +1 -1
  150. package/dist/mcp-mock.cjs +217 -49
  151. package/dist/mcp-mock.cjs.map +1 -1
  152. package/dist/mcp-mock.d.cts +66 -1
  153. package/dist/mcp-mock.d.cts.map +1 -1
  154. package/dist/mcp-mock.d.ts +66 -1
  155. package/dist/mcp-mock.d.ts.map +1 -1
  156. package/dist/mcp-mock.js +218 -50
  157. package/dist/mcp-mock.js.map +1 -1
  158. package/dist/mcp-stub.cjs +5 -1
  159. package/dist/mcp-stub.d.cts +3 -1
  160. package/dist/mcp-stub.d.ts +3 -1
  161. package/dist/mcp-stub.js +3 -1
  162. package/dist/mcp-types.d.cts +6 -0
  163. package/dist/mcp-types.d.ts +6 -0
  164. package/dist/message-text.cjs +192 -0
  165. package/dist/message-text.cjs.map +1 -0
  166. package/dist/message-text.d.cts +31 -0
  167. package/dist/message-text.d.cts.map +1 -0
  168. package/dist/message-text.d.ts +31 -0
  169. package/dist/message-text.d.ts.map +1 -0
  170. package/dist/message-text.js +184 -0
  171. package/dist/message-text.js.map +1 -0
  172. package/dist/messages.cjs +38 -0
  173. package/dist/messages.cjs.map +1 -1
  174. package/dist/messages.js +38 -0
  175. package/dist/messages.js.map +1 -1
  176. package/dist/metrics.cjs +46 -66
  177. package/dist/metrics.cjs.map +1 -1
  178. package/dist/metrics.d.cts +14 -6
  179. package/dist/metrics.d.cts.map +1 -1
  180. package/dist/metrics.d.ts +14 -6
  181. package/dist/metrics.d.ts.map +1 -1
  182. package/dist/metrics.js +32 -40
  183. package/dist/metrics.js.map +1 -1
  184. package/dist/ollama.cjs +5 -1
  185. package/dist/ollama.cjs.map +1 -1
  186. package/dist/ollama.d.cts.map +1 -1
  187. package/dist/ollama.d.ts.map +1 -1
  188. package/dist/ollama.js +5 -1
  189. package/dist/ollama.js.map +1 -1
  190. package/dist/openapi.cjs +3405 -0
  191. package/dist/openapi.cjs.map +1 -0
  192. package/dist/openapi.d.cts +30 -0
  193. package/dist/openapi.d.cts.map +1 -0
  194. package/dist/openapi.d.ts +30 -0
  195. package/dist/openapi.d.ts.map +1 -0
  196. package/dist/openapi.js +3404 -0
  197. package/dist/openapi.js.map +1 -0
  198. package/dist/openrouter-video.cjs +4 -4
  199. package/dist/openrouter-video.cjs.map +1 -1
  200. package/dist/openrouter-video.d.cts.map +1 -1
  201. package/dist/openrouter-video.d.ts.map +1 -1
  202. package/dist/openrouter-video.js +4 -4
  203. package/dist/openrouter-video.js.map +1 -1
  204. package/dist/recorder.cjs +9 -1
  205. package/dist/recorder.cjs.map +1 -1
  206. package/dist/recorder.d.cts.map +1 -1
  207. package/dist/recorder.d.ts.map +1 -1
  208. package/dist/recorder.js +9 -1
  209. package/dist/recorder.js.map +1 -1
  210. package/dist/responses.cjs +43 -4
  211. package/dist/responses.cjs.map +1 -1
  212. package/dist/responses.d.cts.map +1 -1
  213. package/dist/responses.d.ts.map +1 -1
  214. package/dist/responses.js +43 -4
  215. package/dist/responses.js.map +1 -1
  216. package/dist/route-registry.cjs +951 -0
  217. package/dist/route-registry.cjs.map +1 -0
  218. package/dist/route-registry.d.cts +33 -0
  219. package/dist/route-registry.d.cts.map +1 -0
  220. package/dist/route-registry.d.ts +33 -0
  221. package/dist/route-registry.d.ts.map +1 -0
  222. package/dist/route-registry.js +866 -0
  223. package/dist/route-registry.js.map +1 -0
  224. package/dist/router.cjs +1 -1
  225. package/dist/router.cjs.map +1 -1
  226. package/dist/router.js +1 -1
  227. package/dist/router.js.map +1 -1
  228. package/dist/server.cjs +590 -191
  229. package/dist/server.cjs.map +1 -1
  230. package/dist/server.d.cts +4 -1
  231. package/dist/server.d.cts.map +1 -1
  232. package/dist/server.d.ts +4 -1
  233. package/dist/server.d.ts.map +1 -1
  234. package/dist/server.js +498 -99
  235. package/dist/server.js.map +1 -1
  236. package/dist/speech.cjs +4 -2
  237. package/dist/speech.cjs.map +1 -1
  238. package/dist/speech.js +4 -2
  239. package/dist/speech.js.map +1 -1
  240. package/dist/suite.cjs +1 -1
  241. package/dist/suite.js +1 -1
  242. package/dist/transcription.cjs +2 -2
  243. package/dist/transcription.cjs.map +1 -1
  244. package/dist/transcription.d.cts.map +1 -1
  245. package/dist/transcription.d.ts.map +1 -1
  246. package/dist/transcription.js +2 -2
  247. package/dist/transcription.js.map +1 -1
  248. package/dist/types.cjs +18 -0
  249. package/dist/types.cjs.map +1 -0
  250. package/dist/types.d.cts +202 -2
  251. package/dist/types.d.cts.map +1 -1
  252. package/dist/types.d.ts +202 -2
  253. package/dist/types.d.ts.map +1 -1
  254. package/dist/types.js +17 -0
  255. package/dist/types.js.map +1 -0
  256. package/dist/validate-cli.cjs +89 -31
  257. package/dist/validate-cli.cjs.map +1 -1
  258. package/dist/validate-cli.js +91 -33
  259. package/dist/validate-cli.js.map +1 -1
  260. package/dist/vector-stores.cjs +1180 -0
  261. package/dist/vector-stores.cjs.map +1 -0
  262. package/dist/vector-stores.d.cts +106 -0
  263. package/dist/vector-stores.d.cts.map +1 -0
  264. package/dist/vector-stores.d.ts +106 -0
  265. package/dist/vector-stores.d.ts.map +1 -0
  266. package/dist/vector-stores.js +1164 -0
  267. package/dist/vector-stores.js.map +1 -0
  268. package/dist/vector-types.d.cts.map +1 -1
  269. package/dist/vector-types.d.ts.map +1 -1
  270. package/dist/video.cjs +19 -0
  271. package/dist/video.cjs.map +1 -1
  272. package/dist/video.d.cts.map +1 -1
  273. package/dist/video.d.ts.map +1 -1
  274. package/dist/video.js +19 -0
  275. package/dist/video.js.map +1 -1
  276. package/dist/vitest.cjs +15 -10
  277. package/dist/vitest.cjs.map +1 -1
  278. package/dist/vitest.d.cts +4 -2
  279. package/dist/vitest.d.cts.map +1 -1
  280. package/dist/vitest.d.ts +4 -2
  281. package/dist/vitest.d.ts.map +1 -1
  282. package/dist/vitest.js +16 -11
  283. package/dist/vitest.js.map +1 -1
  284. package/dist/ws-gemini-live.d.ts +2 -2
  285. package/dist/ws-realtime.d.ts +2 -2
  286. package/dist/ws-responses.cjs +14 -2
  287. package/dist/ws-responses.cjs.map +1 -1
  288. package/dist/ws-responses.js +14 -2
  289. package/dist/ws-responses.js.map +1 -1
  290. package/package.json +4 -1
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.js","names":[],"sources":["../src/helpers.ts"],"sourcesContent":["import type { LiveFixtureResponse } from \"./live-types.js\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport type * as http from \"node:http\";\nimport type { IncomingHttpHeaders } from \"node:http\";\nimport { DEFAULT_TEST_ID } from \"./constants.js\";\nimport type { Logger } from \"./logger.js\";\nimport { isReasoningModel } from \"./model-utils.js\";\nimport { isRecognizedApiKeyHeader } from \"./api-key-auth.js\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n FixtureMatch,\n FixtureResponse,\n ResponseFactory,\n TextResponse,\n ToolCallResponse,\n ContentWithToolCallsResponse,\n ErrorResponse,\n EmbeddingResponse,\n ImageResponse,\n AudioResponse,\n TranscriptionResponse,\n VideoResponse,\n RawJSONResponse,\n SSEChunk,\n ToolCall,\n FixtureBlock,\n FixtureFileBlock,\n ChatCompletion,\n ResponseOverrides,\n RecordConfig,\n RecordProviderKey,\n} from \"./types.js\";\n\n/**\n * Resolve effective strict mode from per-request header and server default.\n * Header values override the server default — same precedence pattern as chaos\n * config headers (see resolveChaosConfig in chaos.ts).\n *\n * Header: `X-AIMock-Strict` — \"true\"/\"1\" → strict on, \"false\"/\"0\" → strict off\n * (case-insensitive, surrounding whitespace trimmed).\n * When absent or unrecognised, falls back to the server-level default.\n */\nexport function resolveStrictMode(\n serverDefault: boolean | undefined,\n rawHeaders?: IncomingHttpHeaders,\n): boolean {\n if (rawHeaders) {\n const header = rawHeaders[\"x-aimock-strict\"];\n const rawVal =\n typeof header === \"string\" ? header : Array.isArray(header) ? header[0] : undefined;\n if (typeof rawVal === \"string\") {\n const val = rawVal.trim().toLowerCase();\n if (val === \"true\" || val === \"1\") return true;\n if (val === \"false\" || val === \"0\") return false;\n }\n }\n return serverDefault ?? false;\n}\n\n/**\n * Would a fixture miss on this request have been forwarded upstream? This is\n * the ONE rule behind the no-fixture chaos journal `source` label: \"proxy\"\n * only when record mode has an upstream for THIS provider and strict mode is\n * not refusing the miss first. `effectiveStrict` is the header-resolved value\n * from {@link resolveStrictMode}. `record.providers` is read guarded — a JS\n * caller can hand over `record: {}`.\n */\nexport function wouldProxyMiss(\n effectiveStrict: boolean,\n record: RecordConfig | undefined,\n providerKey: RecordProviderKey | undefined,\n): boolean {\n if (effectiveStrict || providerKey === undefined) return false;\n return Boolean(record?.providers?.[providerKey]);\n}\n\n/**\n * Returns `true` or `false` when the X-AIMock-Strict header overrides the\n * server default, or `undefined` when it doesn't. Designed to be spread\n * directly into a journal entry's `response` object:\n *\n * response: { status, fixture, ...strictOverrideField(defaults.strict, req.headers) }\n */\nexport function strictOverrideField(\n serverDefault: boolean | undefined,\n rawHeaders?: IncomingHttpHeaders,\n): { strictOverride?: boolean } {\n const effective = resolveStrictMode(serverDefault, rawHeaders);\n if (effective !== (serverDefault ?? false)) {\n return { strictOverride: effective };\n }\n return {};\n}\n\n/**\n * Build the strict-mode 503 error message, distinguishing a true no-match from\n * a sequence/turn-exhausted miss.\n *\n * `skippedBySequenceOrTurn` is the count reported by `matchFixtureDiagnostic`\n * (router.ts): the number of fixtures that matched the request SHAPE but were\n * rejected ONLY by their `sequenceIndex`/`turnIndex` count state.\n *\n * - `0` → `\"Strict mode: no fixture matched\"` (no candidate had a matching shape)\n * - `>0` → `\"Strict mode: N candidate fixture(s) skipped by sequence/turn state\"`\n *\n * The HTTP status (503) and error envelope shape are unchanged at every call\n * site — only this message string differs. Endpoints with no sequence/turn\n * gates always pass `0` and therefore see the generic message.\n */\nexport function strictNoMatchMessage(skippedBySequenceOrTurn: number): string {\n if (skippedBySequenceOrTurn > 0) {\n return `Strict mode: ${skippedBySequenceOrTurn} candidate fixture(s) skipped by sequence/turn state`;\n }\n return \"Strict mode: no fixture matched\";\n}\n\n/**\n * Build the strict-mode error LOG line, mirroring {@link strictNoMatchMessage}'s\n * disambiguation so the error log distinguishes the two miss kinds too.\n */\nexport function strictNoMatchLogLine(\n method: string,\n url: string,\n skippedBySequenceOrTurn: number,\n): string {\n if (skippedBySequenceOrTurn > 0) {\n return `STRICT: ${skippedBySequenceOrTurn} candidate fixture(s) skipped by sequence/turn state for ${method} ${url}`;\n }\n return `STRICT: No fixture matched for ${method} ${url}`;\n}\n\n/**\n * Resolve the reasoning string to actually emit for a given model.\n *\n * aimock synthesizes a reasoning channel whenever a fixture carries a\n * `reasoning` string, regardless of the requested model. But a non-reasoning\n * model (e.g. `gpt-4.1`) would emit no reasoning against the real provider, so\n * replaying it is a false green (see aimock#254). This gates the emission on\n * the requested model's capability:\n *\n * - no fixture reasoning → undefined (no-op, short-circuit)\n * - reasoning-capable model → emit unchanged, no log\n * - non-reasoning model, strict OFF → `logger.warn`, still emit (preserves\n * current behavior)\n * - non-reasoning model, strict ON → `logger.error`, suppress (return undefined)\n *\n * Capability is decided from the REQUESTED model id (what the backend was wired\n * to), not any `overrides.model` echoed in the payload.\n */\nexport function resolveReasoningForModel(\n reasoning: string | undefined,\n model: string | undefined,\n strict: boolean,\n logger: Logger,\n): string | undefined {\n if (!reasoning) return undefined;\n if (isReasoningModel(model)) return reasoning;\n if (strict) {\n logger.error(\n `Strict mode: fixture has a reasoning channel but model \"${model}\" is not reasoning-capable — suppressing reasoning emission`,\n );\n return undefined;\n }\n logger.warn(\n `Fixture has a reasoning channel but model \"${model}\" is not reasoning-capable — the real provider would emit no reasoning. Emitting anyway (set X-AIMock-Strict: true to suppress).`,\n );\n return reasoning;\n}\n\n/**\n * Resolve the encrypted reasoning artifacts (`reasoningSignature` and\n * `redactedThinking`) to actually emit for a given model.\n *\n * `redacted_thinking` blocks and a thinking `signature` ARE part of the\n * reasoning channel — they are just the encrypted form of it — so they must be\n * gated on the same model-capability resolution as the plaintext `reasoning`\n * string (see resolveReasoningForModel). Gating only the plaintext channel\n * leaves a half-gated reasoning path: replaying a fixture recorded from a\n * reasoning model against a non-reasoning model would strip the `thinking`\n * block but still emit `redacted_thinking` blocks, which the real provider for\n * that model would never produce.\n *\n * Capability is decided from the REQUESTED model id, independently of whether a\n * plaintext `reasoning` string is present — a fixture may carry only\n * `redactedThinking` with no plaintext reasoning.\n *\n * - reasoning-capable model → emit both unchanged, no log\n * - non-reasoning model, no artifacts → no-op, nothing to suppress\n * - non-reasoning model, strict OFF → `logger.warn`, still emit (preserves\n * current behavior)\n * - non-reasoning model, strict ON → `logger.error`, suppress both\n *\n * Must be invoked alongside resolveReasoningForModel with identical model/strict\n * inputs so the plaintext and encrypted channels stay suppressed together.\n */\nexport function resolveReasoningArtifactsForModel(\n reasoningSignature: string | undefined,\n redactedThinking: string[] | undefined,\n model: string | undefined,\n strict: boolean,\n logger: Logger,\n): { reasoningSignature?: string; redactedThinking?: string[] } {\n const hasArtifacts = reasoningSignature !== undefined || (redactedThinking?.length ?? 0) > 0;\n if (!hasArtifacts || isReasoningModel(model)) {\n return { reasoningSignature, redactedThinking };\n }\n if (strict) {\n logger.error(\n `Strict mode: fixture has encrypted reasoning artifacts (redacted_thinking/signature) but model \"${model}\" is not reasoning-capable — suppressing reasoning emission`,\n );\n return {};\n }\n logger.warn(\n `Fixture has encrypted reasoning artifacts (redacted_thinking/signature) but model \"${model}\" is not reasoning-capable — the real provider would emit no reasoning. Emitting anyway (set X-AIMock-Strict: true to suppress).`,\n );\n return { reasoningSignature, redactedThinking };\n}\n\nexport function flattenHeaders(headers: http.IncomingHttpHeaders): Record<string, string> {\n const flat: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue;\n if (isRecognizedApiKeyHeader(key)) {\n flat[key] = \"[REDACTED]\";\n } else {\n flat[key] = Array.isArray(value) ? value.join(\", \") : value;\n }\n }\n return flat;\n}\n\n/** Structural discriminator; registration performs full transcript validation. */\nexport function isLiveResponse(value: unknown): value is LiveFixtureResponse {\n return value !== null && typeof value === \"object\" && !Array.isArray(value) && \"live\" in value;\n}\n\nexport function isResponseFactory(r: FixtureResponse | ResponseFactory): r is ResponseFactory {\n return typeof r === \"function\";\n}\n\nexport async function resolveResponse(\n fixture: Fixture,\n request: ChatCompletionRequest,\n): Promise<FixtureResponse> {\n if (typeof fixture.response === \"function\") {\n try {\n const raw = await fixture.response(request);\n return normalizeFactoryResponse(raw);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Response factory threw: ${msg}`, { cause: err });\n }\n }\n return fixture.response;\n}\n\nfunction normalizeFactoryResponse(raw: FixtureResponse): FixtureResponse {\n const r = { ...raw } as Record<string, unknown>;\n if (typeof r.content === \"object\" && r.content !== null) {\n r.content = JSON.stringify(r.content);\n }\n if (Array.isArray(r.toolCalls)) {\n r.toolCalls = (r.toolCalls as Array<Record<string, unknown>>).map((tc) => {\n if (typeof tc.arguments === \"object\" && tc.arguments !== null) {\n return { ...tc, arguments: JSON.stringify(tc.arguments) };\n }\n return { ...tc };\n });\n }\n // Mirror the toolCalls[].arguments idiom for the optional ordered `blocks`\n // array: auto-stringify object `arguments` on each `toolCall` block so a\n // programmatic ResponseFactory may return objects (resolveFixtureBlocks\n // requires string `arguments`). Text blocks and string arguments pass\n // through unchanged. Matches the loader's block handling.\n if (Array.isArray(r.blocks)) {\n r.blocks = (r.blocks as Array<Record<string, unknown>>).map((block) => {\n if (\n block != null &&\n block.type === \"toolCall\" &&\n typeof block.arguments === \"object\" &&\n block.arguments !== null\n ) {\n return { ...block, arguments: JSON.stringify(block.arguments) };\n }\n return { ...block };\n });\n }\n return r as unknown as FixtureResponse;\n}\n\nexport function generateId(prefix = \"chatcmpl\"): string {\n return `${prefix}-${randomBytes(12).toString(\"base64url\")}`;\n}\n\n/**\n * Resolve the request id for this HTTP request.\n *\n * - When the caller sends a well-formed `X-Request-Id` (1-128 chars of\n * `A-Za-z0-9-_.:`), it is echoed verbatim so distributed traces correlate.\n * - Otherwise (absent, empty, too long, or illegal characters) a fresh\n * `req-…` id is generated — never trust an attacker-controlled correlation\n * value to be a valid log key.\n *\n * Returns `{ id, generated }`. The server normalizes\n * `req.headers[\"x-request-id\"]` to the resolved value, so every downstream\n * `flattenHeaders` journal snapshot carries it with zero per-handler edits —\n * which also means a MINTED id would otherwise ride the egress header set to\n * a real provider. The server therefore passes `generated` to\n * `markMintedRequestId` so `buildForwardHeaders` can drop it; a caller's own\n * id still forwards verbatim.\n */\nexport function resolveRequestId(rawHeaders: http.IncomingHttpHeaders): {\n id: string;\n generated: boolean;\n} {\n const raw = rawHeaders[\"x-request-id\"];\n const first = Array.isArray(raw) ? raw[0] : raw;\n if (typeof first === \"string\") {\n const trimmed = first.trim();\n if (trimmed.length >= 1 && trimmed.length <= 128 && /^[A-Za-z0-9\\-_.:]+$/.test(trimmed)) {\n return { id: trimmed, generated: false };\n }\n }\n return { id: generateId(\"req\"), generated: true };\n}\n\n/**\n * Requests whose `x-request-id` aimock MINTED (the caller sent none, or sent\n * an unusable one). The normalized header is indistinguishable from a\n * caller-supplied one by inspection, so the provenance is tracked out-of-band\n * and keyed on the request object, exactly like the egress auth marker.\n */\nconst mintedRequestIds = new WeakSet<http.IncomingMessage>();\n\n/** @internal Record that this request's `x-request-id` is aimock's own. */\nexport function markMintedRequestId(req: http.IncomingMessage, generated: boolean): void {\n if (generated) mintedRequestIds.add(req);\n}\n\n/**\n * @internal True when `x-request-id` on this request was minted by aimock.\n * Egress paths use it to avoid transmitting an invented correlation id to a\n * real provider.\n */\nexport function hasMintedRequestId(req: http.IncomingMessage): boolean {\n return mintedRequestIds.has(req);\n}\n\nexport function generateToolCallId(): string {\n return `call_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function generateMessageId(): string {\n return `msg_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function generateToolUseId(): string {\n return `toolu_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function isTextResponse(r: FixtureResponse): r is TextResponse {\n return \"content\" in r && typeof (r as TextResponse).content === \"string\" && !(\"toolCalls\" in r);\n}\n\nexport function isToolCallResponse(r: FixtureResponse): r is ToolCallResponse {\n return (\n \"toolCalls\" in r &&\n Array.isArray((r as ToolCallResponse).toolCalls) &&\n !(\"content\" in r && typeof (r as unknown as Record<string, unknown>).content === \"string\")\n );\n}\n\nexport function isContentWithToolCallsResponse(\n r: FixtureResponse,\n): r is ContentWithToolCallsResponse {\n const o = r as ContentWithToolCallsResponse;\n // LEGACY / COMBINED shape — BOTH content (string) + toolCalls (array). This\n // clause is byte-identical to the original guard, so every fixture that\n // matched before still matches here and is classified exactly as before.\n const hasContentAndToolCalls =\n \"content\" in r &&\n typeof o.content === \"string\" &&\n \"toolCalls\" in r &&\n Array.isArray(o.toolCalls);\n // BLOCKS-ONLY shape (additive, #274 F0) — a non-empty `blocks` array with no\n // content/toolCalls. This is a pure RELAXATION: it recognizes MORE, never\n // reclassifies an existing fixture. A blocks-only fixture cannot be claimed by\n // any earlier/looser guard in the dispatch order — `isTextResponse` requires a\n // string `content` AND `!(\"toolCalls\" in r)`, and `isToolCallResponse`\n // requires a `toolCalls` array — so it would otherwise fall through to 500.\n // `isAudioResponse` (checked first everywhere) requires an `audio` field, which\n // blocks-only lacks, so there is no overlap there either.\n const hasNonEmptyBlocks = Array.isArray(o.blocks) && o.blocks.length > 0;\n return hasContentAndToolCalls || hasNonEmptyBlocks;\n}\n\n/**\n * Validate and pass through the ordered `blocks` field of a combined\n * content+toolCalls fixture. Used ONLY on the new block-iteration path (when a\n * fixture explicitly sets `blocks`); it is NOT a legacy-order reconstructor —\n * fixtures without `blocks` never reach this function and keep their unchanged\n * text-first path.\n *\n * An EMPTY `blocks` array is treated as \"no blocks\" by every builder's\n * streaming gate (`blocks && blocks.length > 0`), so it falls back to the\n * legacy `{content, toolCalls}` path and never reaches this function — the gate\n * is the single source of truth for \"has blocks\". This validator therefore only\n * ever runs on a non-empty array.\n *\n * Accepts the relaxed on-disk {@link FixtureFileBlock} input shape — a\n * `toolCall` block's `arguments` may be a string OR a JSON object/array — which\n * makes the object-tolerance below type-visible and mirrors how\n * normalizeResponse types its file-form input (see {@link FixtureFileBlock} /\n * {@link FixtureFileContentWithToolCallsResponse}). The in-memory\n * {@link FixtureBlock} form (string `arguments`) is a structural subtype, so\n * existing callers that pass `FixtureBlock[]` continue to type-check.\n *\n * Returns the blocks in array order, NORMALIZED to {@link FixtureBlock}: a\n * `text` block with a string `text`, or a `toolCall` block with string `name` +\n * string `arguments` (object/array `arguments` is JSON.stringified) and an\n * optional string `id`. The return type guarantees `arguments: string` — every\n * caller relies on that. Throws on a malformed array or entry — same fail-fast\n * idiom as the other fixture validators in this module (see e.g. the factory\n * guard at {@link resolveResponse}).\n */\nexport function resolveFixtureBlocks(blocks: FixtureFileBlock[]): FixtureBlock[] {\n if (!Array.isArray(blocks)) {\n throw new Error(`Invalid fixture blocks: expected an array, got ${typeof blocks}`);\n }\n // Validate each block and return a normalized COPY. Builders iterate the\n // result and must not observe later mutations of — nor be able to mutate —\n // the caller's stored fixture array, and block objects are consumed read-only\n // downstream, so we never mutate the input in place: any normalization (e.g.\n // stringifying object `arguments`) is applied to a fresh per-block copy.\n return blocks.map((block, i) => {\n if (block === null || typeof block !== \"object\") {\n throw new Error(`Invalid fixture block at index ${i}: expected an object`);\n }\n const b = block as Record<string, unknown>;\n if (b.type === \"text\") {\n if (typeof b.text !== \"string\") {\n throw new Error(\n `Invalid fixture block at index ${i}: \"text\" block requires a string \"text\" field`,\n );\n }\n return { type: \"text\", text: b.text };\n } else if (b.type === \"toolCall\") {\n if (typeof b.name !== \"string\") {\n throw new Error(\n `Invalid fixture block at index ${i}: \"toolCall\" block requires a string \"name\" field`,\n );\n }\n if (b.id !== undefined && typeof b.id !== \"string\") {\n throw new Error(\n `Invalid fixture block at index ${i}: \"toolCall\" block \"id\" must be a string when present`,\n );\n }\n // `arguments` is a JSON string in normalized (file-load) form. The\n // programmatic path (addFixture/addFixtures/prependFixture) stores RAW\n // fixtures with no normalizeResponse pass, so an OBJECT `arguments` can\n // reach here. Be tolerant: stringify an object/array (mirroring\n // normalizeResponse's `JSON.stringify`) into a fresh block copy so the\n // programmatic path is safe and the caller's stored fixture is untouched.\n // A string stays byte-identical (file-load path unchanged); any other\n // type is still rejected.\n if (typeof b.arguments === \"object\" && b.arguments !== null) {\n return { ...b, arguments: JSON.stringify(b.arguments) } as unknown as FixtureBlock;\n }\n if (typeof b.arguments !== \"string\") {\n throw new Error(\n `Invalid fixture block at index ${i}: \"toolCall\" block requires a string or object \"arguments\" field`,\n );\n }\n return { ...b, type: \"toolCall\", name: b.name, arguments: b.arguments } as FixtureBlock;\n } else {\n throw new Error(\n `Invalid fixture block at index ${i}: unknown type ${JSON.stringify(b.type)} (expected \"text\" or \"toolCall\")`,\n );\n }\n });\n}\n\n/** Project normalized ordered blocks without changing the fixture or generating ids. */\nexport function resolveFixtureBlockOutcome(blocks: FixtureFileBlock[]) {\n const ordered = resolveFixtureBlocks(blocks);\n let content = \"\";\n const toolCalls: ToolCall[] = [];\n for (const block of ordered) {\n if (block.type === \"text\") content += block.text;\n else\n toolCalls.push({\n name: block.name,\n arguments: block.arguments,\n ...(block.id !== undefined ? { id: block.id } : {}),\n });\n }\n return { ordered, content, toolCalls, hasToolCalls: toolCalls.length > 0 };\n}\n\nexport function isErrorResponse(r: FixtureResponse): r is ErrorResponse {\n return (\n \"error\" in r &&\n (r as ErrorResponse).error !== null &&\n typeof (r as ErrorResponse).error === \"object\" &&\n \"message\" in ((r as ErrorResponse).error as Record<string, unknown>) &&\n typeof ((r as ErrorResponse).error as Record<string, unknown>).message === \"string\"\n );\n}\n\n/**\n * Serialize an ErrorResponse to JSON, stripping the internal-only `status`\n * field that controls the HTTP status code but should never appear in the\n * response body. Real LLM APIs don't include it.\n */\nexport function serializeErrorResponse(response: ErrorResponse): string {\n return JSON.stringify({\n error: {\n message: response.error.message,\n type: response.error.type ?? \"server_error\",\n param: response.error.param ?? null,\n code: response.error.code ?? null,\n },\n });\n}\n\nexport function isEmbeddingResponse(r: FixtureResponse): r is EmbeddingResponse {\n return \"embedding\" in r && Array.isArray((r as EmbeddingResponse).embedding);\n}\n\nexport function isImageResponse(r: FixtureResponse): r is ImageResponse {\n return (\n (\"image\" in r && typeof r.image === \"object\" && r.image != null) ||\n (\"images\" in r && Array.isArray((r as ImageResponse).images))\n );\n}\n\nexport function isAudioResponse(r: FixtureResponse): r is AudioResponse {\n if (!(\"audio\" in r)) return false;\n const a = (r as AudioResponse).audio;\n return typeof a === \"string\" || (typeof a === \"object\" && a !== null && \"b64Json\" in a);\n}\n\n/**\n * Map audio format shorthand to MIME content types.\n * Shared between speech, ElevenLabs, and fal audio handlers.\n */\nexport const FORMAT_TO_CONTENT_TYPE: Record<string, string> = {\n mp3: \"audio/mpeg\",\n opus: \"audio/opus\",\n aac: \"audio/aac\",\n flac: \"audio/flac\",\n wav: \"audio/wav\",\n pcm: \"audio/pcm\",\n};\n\n/**\n * Resolve a format string (e.g. \"mp3\", \"opus\") to its MIME content type.\n * Falls back to \"application/octet-stream\" for unknown formats.\n */\nexport function formatToMime(format: string): string {\n return FORMAT_TO_CONTENT_TYPE[format] ?? \"application/octet-stream\";\n}\n\nexport function isTranscriptionResponse(r: FixtureResponse): r is TranscriptionResponse {\n return (\n \"transcription\" in r &&\n (r as TranscriptionResponse).transcription != null &&\n typeof (r as TranscriptionResponse).transcription === \"object\"\n );\n}\n\nexport function isVideoResponse(r: FixtureResponse): r is VideoResponse {\n return (\n \"video\" in r &&\n (r as VideoResponse).video != null &&\n typeof (r as VideoResponse).video === \"object\"\n );\n}\n\nexport function isJSONResponse(r: FixtureResponse): r is RawJSONResponse {\n return \"json\" in r && (r as RawJSONResponse).json !== undefined;\n}\n\nexport function extractOverrides(\n response: TextResponse | ToolCallResponse | ContentWithToolCallsResponse,\n): ResponseOverrides {\n const r = response;\n return {\n ...(r.id !== undefined && { id: r.id }),\n ...(r.created !== undefined && { created: r.created }),\n ...(r.model !== undefined && { model: r.model }),\n ...(r.usage !== undefined && { usage: r.usage }),\n ...(r.systemFingerprint !== undefined && { systemFingerprint: r.systemFingerprint }),\n ...(r.finishReason !== undefined && { finishReason: r.finishReason }),\n ...(r.role !== undefined && { role: r.role }),\n ...(r.provider !== undefined && { provider: r.provider }),\n ...(r.nativeFinishReason !== undefined && { nativeFinishReason: r.nativeFinishReason }),\n };\n}\n\n// ─── Token estimation ────────────────────────────────────────────────────\n\n/**\n * Rough token count estimation based on character length.\n * Uses the ~4 characters per token heuristic common for English text.\n */\nexport function estimateTokens(text: string): number {\n return Math.max(1, Math.ceil(text.length / 4));\n}\n\n/**\n * Estimate prompt tokens from a request's messages array.\n */\nexport function estimatePromptTokens(messages: ChatCompletionRequest[\"messages\"]): number {\n let totalChars = 0;\n for (const msg of messages) {\n if (typeof msg.content === \"string\") {\n totalChars += msg.content.length;\n } else if (Array.isArray(msg.content)) {\n for (const part of msg.content) {\n if (part.text) totalChars += part.text.length;\n }\n }\n }\n return Math.max(1, Math.ceil(totalChars / 4));\n}\n\n/**\n * Build usage object: use explicit overrides if provided, otherwise estimate.\n *\n * Shared by BOTH the non-streaming completion builders (below) and the\n * streaming usage-chunk sites in server.ts — the single source of truth for\n * \"explicit token override wins, cost-only override still estimates\". Exported\n * so the streaming path does not re-implement the estimation logic.\n */\nexport function resolveUsage(\n overrides: ResponseOverrides | undefined,\n promptText: string,\n completionText: string,\n): { prompt_tokens: number; completion_tokens: number; total_tokens: number } {\n if (overrides?.usage) {\n const u = overrides.usage;\n // A usage override that scripts only cost fields (e.g. `{ cost: 0.5 }`)\n // carries NO token counts. Real providers always report real token usage,\n // so estimate the counts rather than forcing them to 0 — otherwise a\n // cost-scripting fixture is unfaithful (cost present, tokens 0). When the\n // override DOES set any token count we preserve the explicit-or-zero merge\n // behavior, and any explicit token value always wins.\n const hasExplicitTokens =\n u.prompt_tokens !== undefined ||\n u.completion_tokens !== undefined ||\n u.total_tokens !== undefined ||\n u.input_tokens !== undefined ||\n u.output_tokens !== undefined ||\n u.promptTokenCount !== undefined ||\n u.candidatesTokenCount !== undefined ||\n u.totalTokenCount !== undefined;\n const fallbackPrompt = hasExplicitTokens ? 0 : estimateTokens(promptText || \"x\");\n const fallbackCompletion = hasExplicitTokens ? 0 : estimateTokens(completionText || \"x\");\n const prompt = u.prompt_tokens ?? u.input_tokens ?? u.promptTokenCount ?? fallbackPrompt;\n const completion =\n u.completion_tokens ?? u.output_tokens ?? u.candidatesTokenCount ?? fallbackCompletion;\n return {\n prompt_tokens: prompt,\n completion_tokens: completion,\n total_tokens: u.total_tokens ?? u.totalTokenCount ?? prompt + completion,\n };\n }\n const prompt = estimateTokens(promptText || \"x\");\n const completion = estimateTokens(completionText || \"x\");\n return {\n prompt_tokens: prompt,\n completion_tokens: completion,\n total_tokens: prompt + completion,\n };\n}\n\n/**\n * Build an SSE usage chunk for streaming responses.\n * OpenAI emits this as the final chunk before [DONE] when\n * stream_options.include_usage is true. It has an empty choices array.\n */\nexport function buildUsageChunk(\n id: string,\n model: string,\n created: number,\n usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number },\n fingerprint?: string,\n): SSEChunk {\n return {\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [],\n usage,\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n };\n}\n\nexport function buildTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): SSEChunk[] {\n const id = overrides?.id ?? generateId();\n const created = overrides?.created ?? Math.floor(Date.now() / 1000);\n const effectiveModel = overrides?.model ?? model;\n const chunks: SSEChunk[] = [];\n const fingerprint = overrides?.systemFingerprint;\n\n // Reasoning chunks (emitted before content, OpenRouter format)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n { index: 0, delta: { reasoning_content: slice }, logprobs: null, finish_reason: null },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Role chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: { role: overrides?.role ?? \"assistant\", content: \"\" },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Content chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [{ index: 0, delta: { content: slice }, logprobs: null, finish_reason: null }],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n\n // Finish chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n { index: 0, delta: {}, logprobs: null, finish_reason: overrides?.finishReason ?? \"stop\" },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n return chunks;\n}\n\nexport function buildToolCallChunks(\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): SSEChunk[] {\n const id = overrides?.id ?? generateId();\n const created = overrides?.created ?? Math.floor(Date.now() / 1000);\n const effectiveModel = overrides?.model ?? model;\n const chunks: SSEChunk[] = [];\n const fingerprint = overrides?.systemFingerprint;\n\n // Reasoning chunks (emitted before tool calls, OpenRouter format)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n { index: 0, delta: { reasoning_content: slice }, logprobs: null, finish_reason: null },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Role chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: { role: overrides?.role ?? \"assistant\", content: null },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Tool call chunks — one initial chunk per tool call, then argument chunks\n for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {\n const tc = toolCalls[tcIdx];\n const tcId = tc.id || generateToolCallId();\n\n // Initial tool call chunk (id + function name)\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: tcIdx,\n id: tcId,\n type: \"function\",\n function: { name: tc.name, arguments: \"\" },\n },\n ],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Argument streaming chunks\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n const slice = args.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [{ index: tcIdx, function: { arguments: slice } }],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Finish chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {},\n logprobs: null,\n finish_reason: overrides?.finishReason ?? \"tool_calls\",\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n return chunks;\n}\n\n// Non-streaming response builders\n\nexport function buildTextCompletion(\n content: string,\n model: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n requestMessages?: ChatCompletionRequest[\"messages\"],\n): ChatCompletion {\n const promptText = requestMessages\n ? requestMessages\n .map((m) =>\n typeof m.content === \"string\"\n ? m.content\n : Array.isArray(m.content)\n ? m.content.map((p) => p.text ?? \"\").join(\"\")\n : \"\",\n )\n .join(\"\")\n : \"\";\n return {\n id: overrides?.id ?? generateId(),\n object: \"chat.completion\",\n created: overrides?.created ?? Math.floor(Date.now() / 1000),\n model: overrides?.model ?? model,\n choices: [\n {\n index: 0,\n message: {\n role: overrides?.role ?? \"assistant\",\n content,\n refusal: null,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n },\n logprobs: null,\n finish_reason: overrides?.finishReason ?? \"stop\",\n },\n ],\n usage: resolveUsage(overrides, promptText, content),\n ...(overrides?.systemFingerprint !== undefined && {\n system_fingerprint: overrides.systemFingerprint,\n }),\n };\n}\n\nexport function buildToolCallCompletion(\n toolCalls: ToolCall[],\n model: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n requestMessages?: ChatCompletionRequest[\"messages\"],\n): ChatCompletion {\n const promptText = requestMessages\n ? requestMessages\n .map((m) =>\n typeof m.content === \"string\"\n ? m.content\n : Array.isArray(m.content)\n ? m.content.map((p) => p.text ?? \"\").join(\"\")\n : \"\",\n )\n .join(\"\")\n : \"\";\n const completionText = toolCalls.map((tc) => tc.name + tc.arguments).join(\"\");\n return {\n id: overrides?.id ?? generateId(),\n object: \"chat.completion\",\n created: overrides?.created ?? Math.floor(Date.now() / 1000),\n model: overrides?.model ?? model,\n choices: [\n {\n index: 0,\n message: {\n role: overrides?.role ?? \"assistant\",\n content: null,\n refusal: null,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n tool_calls: toolCalls.map((tc) => ({\n id: tc.id || generateToolCallId(),\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n })),\n },\n logprobs: null,\n finish_reason: overrides?.finishReason ?? \"tool_calls\",\n },\n ],\n usage: resolveUsage(overrides, promptText, completionText),\n ...(overrides?.systemFingerprint !== undefined && {\n system_fingerprint: overrides.systemFingerprint,\n }),\n };\n}\n\nexport function buildContentWithToolCallsChunks(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n blocks?: FixtureBlock[],\n): SSEChunk[] {\n const id = overrides?.id ?? generateId();\n const created = overrides?.created ?? Math.floor(Date.now() / 1000);\n const effectiveModel = overrides?.model ?? model;\n const chunks: SSEChunk[] = [];\n const fingerprint = overrides?.systemFingerprint;\n\n if (blocks && blocks.length > 0) {\n // NEW: emit chunks in fixture block array order.\n //\n // DEGENERATE PROVIDER NOTE: in OpenAI chat-completions, `delta.content` and\n // `delta.tool_calls` are SEPARATE channels that the client merges with no\n // positional interleaving. So \"tool-call-before-text\" is NOT semantically\n // observable to a real client — it reassembles content and tool calls into\n // their own buckets regardless of chunk order. We still emit honest\n // array-order chunks (the SSE chunk SEQUENCE is the contract this path\n // asserts), but we do NOT fake interleaving the channel cannot express.\n const outcome = resolveFixtureBlockOutcome(blocks);\n const ordered = outcome.ordered;\n\n // Reasoning chunks (emitted first, OpenRouter format) — unchanged from legacy.\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n ...(i === 0 && { role: overrides?.role ?? \"assistant\" }),\n reasoning_content: slice,\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Role chunk — preserved exactly as the legacy path.\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: { role: overrides?.role ?? \"assistant\", content: \"\" },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Tool-call `index` is assigned in encounter order across the block array.\n let tcIdx = 0;\n for (const block of ordered) {\n if (block.type === \"text\") {\n for (let i = 0; i < block.text.length; i += chunkSize) {\n const slice = block.text.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [{ index: 0, delta: { content: slice }, logprobs: null, finish_reason: null }],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n } else {\n const tcId = block.id || generateToolCallId();\n\n // Initial tool call chunk (id + function name)\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: tcIdx,\n id: tcId,\n type: \"function\",\n function: { name: block.name, arguments: \"\" },\n },\n ],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Argument streaming chunks\n const args = block.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n const slice = args.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [{ index: tcIdx, function: { arguments: slice } }],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n tcIdx++;\n }\n }\n\n // Derive the default terminal from the blocks actually emitted.\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {},\n logprobs: null,\n finish_reason: overrides?.finishReason ?? (outcome.hasToolCalls ? \"tool_calls\" : \"stop\"),\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n return chunks;\n }\n\n // EXISTING legacy code, byte-for-byte UNCHANGED.\n // Reasoning chunks (emitted before content, OpenRouter format)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n ...(i === 0 && { role: overrides?.role ?? \"assistant\" }),\n reasoning_content: slice,\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Role chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: { role: overrides?.role ?? \"assistant\", content: \"\" },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Content chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [{ index: 0, delta: { content: slice }, logprobs: null, finish_reason: null }],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n\n // Tool call chunks — one initial chunk per tool call, then argument chunks\n for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {\n const tc = toolCalls[tcIdx];\n const tcId = tc.id || generateToolCallId();\n\n // Initial tool call chunk (id + function name)\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: tcIdx,\n id: tcId,\n type: \"function\",\n function: { name: tc.name, arguments: \"\" },\n },\n ],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Argument streaming chunks\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n const slice = args.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [{ index: tcIdx, function: { arguments: slice } }],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Finish chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {},\n logprobs: null,\n finish_reason: overrides?.finishReason ?? \"tool_calls\",\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n return chunks;\n}\n\n// NOTE (#274): this NON-streaming OpenAI chat-completions builder is\n// intentionally degenerate w.r.t. `blocks` ordering. A chat.completion puts\n// `message.content` and `message.tool_calls` in SEPARATE fields on a single\n// message object — they are NOT a positionally-observable array, so a\n// tool-first `blocks` fixture cannot be expressed in the wire shape. Honoring\n// block order here would be a no-op, so the legacy content+tool_calls fields\n// are unchanged. (Order-observable surfaces — Claude `content[]`, Gemini\n// `parts[]`, Responses `output[]` — DO honor block order; see those builders.)\nexport function buildContentWithToolCallsCompletion(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n requestMessages?: ChatCompletionRequest[\"messages\"],\n): ChatCompletion {\n const promptText = requestMessages\n ? requestMessages\n .map((m) =>\n typeof m.content === \"string\"\n ? m.content\n : Array.isArray(m.content)\n ? m.content.map((p) => p.text ?? \"\").join(\"\")\n : \"\",\n )\n .join(\"\")\n : \"\";\n const completionText = content + toolCalls.map((tc) => tc.name + tc.arguments).join(\"\");\n return {\n id: overrides?.id ?? generateId(),\n object: \"chat.completion\",\n created: overrides?.created ?? Math.floor(Date.now() / 1000),\n model: overrides?.model ?? model,\n choices: [\n {\n index: 0,\n message: {\n role: overrides?.role ?? \"assistant\",\n content,\n refusal: null,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n tool_calls: toolCalls.map((tc) => ({\n id: tc.id || generateToolCallId(),\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n })),\n },\n logprobs: null,\n finish_reason: overrides?.finishReason ?? \"tool_calls\",\n },\n ],\n usage: resolveUsage(overrides, promptText, completionText),\n ...(overrides?.systemFingerprint !== undefined && {\n system_fingerprint: overrides.systemFingerprint,\n }),\n };\n}\n\n// ─── HTTP helpers ─────────────────────────────────────────────────────────\n\nconst DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB\n\n/**\n * A body read that stopped because the CLIENT sent more bytes than the route\n * allows. The socket is already destroyed when this rejects, so no status line\n * ever reaches the caller — but the fault is the caller's, and error arms that\n * journal and log the failure need to say so. Carrying the classification on\n * the error type (rather than re-matching the message text) is what lets them:\n * the message is unchanged from the plain `Error` this replaced.\n */\nexport class RequestBodyTooLargeError extends Error {\n constructor(readonly maxBytes: number) {\n super(`Request body exceeded size limit of ${maxBytes} bytes`);\n this.name = \"RequestBodyTooLargeError\";\n }\n}\n\n/**\n * Read a request body as raw bytes, preserving every octet.\n *\n * This is the byte-level primitive {@link readBody} is built on: routes that\n * must not lose bytes (binary file uploads) take the Buffer, everything else\n * keeps taking the decoded string. Splitting it this way leaves the text path\n * byte-identical — `readBody` performs exactly the same `Buffer.concat(...)`\n * + default (utf8) `toString()` it always did, just one call later.\n */\nexport function readBodyBuffer(\n req: http.IncomingMessage,\n maxBytes: number = DEFAULT_MAX_BODY_BYTES,\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n let settled = false;\n req.on(\"data\", (chunk: Buffer) => {\n if (settled) return;\n totalBytes += chunk.length;\n if (totalBytes > maxBytes) {\n settled = true;\n req.destroy();\n reject(new RequestBodyTooLargeError(maxBytes));\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => {\n if (!settled) {\n settled = true;\n resolve(Buffer.concat(chunks));\n }\n });\n req.on(\"error\", (err) => {\n if (!settled) {\n settled = true;\n reject(err);\n }\n });\n });\n}\n\n/**\n * A body read with a buffering bound that does NOT cost the caller its socket.\n *\n * `buffer` is `null` when the body went over the buffering bound: the bytes\n * read so far were released and the rest was drained, so the request still\n * reaches `end` and the route can answer a real status (with CORS) instead of\n * the client seeing an `ECONNRESET`.\n */\nexport interface BoundedBody {\n buffer: Buffer | null;\n /** Bytes seen on the wire, counted past the buffering bound. */\n bytesRead: number;\n}\n\n/**\n * Read a request body that may legitimately be over-size, without either\n * buffering it all or dropping the socket.\n *\n * {@link readBodyBuffer} answers an over-size body with `req.destroy()`: no\n * status line, no body, no CORS headers. That is the right default for routes\n * whose limit is a pure DoS bound, but it is the wrong answer for a route that\n * owes the caller a `400` describing what was wrong — the caller cannot read an\n * error it never receives. So this variant splits the two concerns:\n *\n * - `maxBytes` bounds MEMORY. Past it the buffered chunks are dropped and the\n * body is only counted, so peak retention never exceeds `maxBytes` no matter\n * how large the body is. The caller gets `buffer: null` and answers itself.\n * - `drainMaxBytes` bounds WORK, and is the only socket drop here: a body that\n * keeps coming past it is not worth draining and rejects as `readBodyBuffer`\n * would.\n */\nexport function readBodyBufferBounded(\n req: http.IncomingMessage,\n maxBytes: number,\n drainMaxBytes: number,\n): Promise<BoundedBody> {\n return new Promise((resolve, reject) => {\n let chunks: Buffer[] | null = [];\n let totalBytes = 0;\n let settled = false;\n req.on(\"data\", (chunk: Buffer) => {\n if (settled) return;\n totalBytes += chunk.length;\n if (chunks !== null && totalBytes > maxBytes) {\n // Release on the FIRST chunk over the bound, before pushing it: the\n // bytes are unusable now, and holding them is what would make peak\n // memory track the attacker's body size instead of `maxBytes`.\n chunks = null;\n }\n if (totalBytes > drainMaxBytes) {\n settled = true;\n req.destroy();\n reject(new RequestBodyTooLargeError(drainMaxBytes));\n return;\n }\n if (chunks !== null) chunks.push(chunk);\n });\n req.on(\"end\", () => {\n if (settled) return;\n settled = true;\n resolve({ buffer: chunks === null ? null : Buffer.concat(chunks), bytesRead: totalBytes });\n });\n req.on(\"error\", (err) => {\n if (!settled) {\n settled = true;\n reject(err);\n }\n });\n });\n}\n\nexport function readBody(\n req: http.IncomingMessage,\n maxBytes: number = DEFAULT_MAX_BODY_BYTES,\n): Promise<string> {\n return readBodyBuffer(req, maxBytes).then((buf) => buf.toString());\n}\n\n// ─── Pattern matching ─────────────────────────────────────────────────────\n\n/**\n * Case-insensitive substring/regex match used for search, rerank, and\n * moderation endpoints where exact casing rarely matters. String patterns\n * are lowercased on both sides before comparison.\n *\n * Note: This intentionally differs from the case-sensitive matching in\n * {@link matchFixture} (router.ts), where fixture authors expect exact\n * string matching against chat completion user messages.\n */\nexport function matchesPattern(text: string, pattern: string | RegExp): boolean {\n if (typeof pattern === \"string\") {\n return text.toLowerCase().includes(pattern.toLowerCase());\n }\n // A global/sticky RegExp carries mutable `lastIndex` state that `.test()`\n // advances. Save and restore it so callers reusing the same regex object\n // (search/rerank/moderation filter loops) are not left with mutated state\n // and get consistent results across repeated calls.\n const savedLastIndex = pattern.lastIndex;\n pattern.lastIndex = 0;\n const result = pattern.test(text);\n pattern.lastIndex = savedLastIndex;\n return result;\n}\n\n/**\n * The ONE definition of \"which test does this traffic belong to\": the\n * `X-Test-Id` header wins, then `?testId=` in the query string, then\n * `DEFAULT_TEST_ID`. Every per-test axis (fixture match-counts, the journal\n * filter, chaos scoping) resolves through this, so a caller that tags one way\n * can never land in two different scopes.\n */\nexport function resolveTestId(headers: IncomingHttpHeaders, url: string | undefined): string {\n const headerValue = headers[\"x-test-id\"];\n if (Array.isArray(headerValue)) {\n if (headerValue.length > 0 && headerValue[0]) return headerValue[0];\n } else if (typeof headerValue === \"string\" && headerValue) {\n return headerValue;\n }\n\n const qIdx = (url ?? \"/\").indexOf(\"?\");\n if (qIdx !== -1) {\n const params = new URLSearchParams((url ?? \"/\").slice(qIdx + 1));\n const queryValue = params.get(\"testId\");\n if (queryValue) return queryValue;\n }\n\n return DEFAULT_TEST_ID;\n}\n\nexport function getTestId(req: http.IncomingMessage): string {\n return resolveTestId(req.headers, req.url);\n}\n\nexport function getContext(req: http.IncomingMessage): string | undefined {\n const headerValue = req.headers[\"x-aimock-context\"];\n if (Array.isArray(headerValue)) {\n if (headerValue.length > 0 && headerValue[0]) return headerValue[0];\n } else if (typeof headerValue === \"string\" && headerValue) {\n return headerValue;\n }\n return undefined;\n}\n\n// ─── Snapshot recording helpers ──────────────────────────────────────────────\n\n/**\n * Convert a test ID (e.g. Playwright titlePath) into a filesystem-safe slug\n * suitable for use as a directory name in snapshot-style recording.\n */\nexport function slugifyTestId(testId: string): string {\n return testId\n .replace(/^.*?\\.(?:spec|test|e2e)\\.(?:tsx|ts|jsx|js|mjs|cjs)(?=\\s|›|$)\\s*›?\\s*/i, \"\") // strip test file extension prefix\n .replace(/\\s*[›>]\\s*/g, \"--\") // Playwright titlePath separator → double dash\n .replace(/[^\\w-]/g, \"-\") // non-word chars → dash\n .replace(/-{3,}/g, \"--\") // collapse 3+ dashes to double\n .replace(/^-+|-+$/g, \"\") // trim leading/trailing dashes\n .toLowerCase();\n}\n\n/**\n * Make a request context (the `X-AIMock-Context` header value) safe to use as\n * a single directory segment in a recorded-fixture path. The header is\n * attacker-controllable, so a raw value containing `../`, path separators, or\n * an absolute-path prefix would let the written fixture escape the configured\n * fixtures base directory. Mirrors `slugifyTestId`: non-word characters\n * (including `/`, `\\`, and `.`) collapse to dashes, so the result is always a\n * single flat segment with no traversal semantics. Returns \"\" when the value\n * sanitizes to nothing (caller treats that as \"no context segment\").\n */\nexport function slugifyContext(context: string): string {\n return context\n .replace(/[^\\w-]/g, \"-\") // non-word chars (incl. / \\ . :) → dash\n .replace(/-{3,}/g, \"--\") // collapse 3+ dashes to double\n .replace(/^-+|-+$/g, \"\") // trim leading/trailing dashes\n .toLowerCase();\n}\n\n// ─── Request shape validation ──────────────────────────────────────────────\n\n/**\n * Validate the shape of a chat `messages` array (Cohere / Ollama / Bedrock\n * style: entries with `content` and optional `tool_calls`).\n *\n * Returns an error detail string when the shape is wrong, or null when the\n * provider converter can safely consume it. Wrong types here used to throw\n * inside the converters (`content.filter` on a number, `.map` on a string\n * `tools`, `msg.role` on a null entry) and surface as 500s. `null` content\n * is accepted — assistant messages legitimately carry `content: null`\n * alongside tool calls, and the converters coerce it to \"\".\n *\n * `checkToolCalls` must be false for providers whose converter never reads\n * `msg.tool_calls` (Bedrock: tool calls are `tool_use` content blocks and\n * `tool_calls` is not a field of the Anthropic Messages body at all). There a\n * stray `tool_calls` is inert — rejecting it turns a request that used to\n * match a fixture and return 200 into a 400.\n */\nexport function validateChatMessages(\n messages: unknown,\n { checkToolCalls = true }: { checkToolCalls?: boolean } = {},\n): string | null {\n // Matches the historic \"messages array is required\" detail so handlers can\n // compose the exact legacy message for a missing/non-array field.\n if (!Array.isArray(messages)) return \"messages array is required\";\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i] as Record<string, unknown> | null;\n if (msg === null || typeof msg !== \"object\" || Array.isArray(msg)) {\n return `messages[${i}] must be an object`;\n }\n const content = msg.content;\n if (\n content !== undefined &&\n content !== null &&\n typeof content !== \"string\" &&\n !Array.isArray(content)\n ) {\n return `messages[${i}].content must be a string or an array`;\n }\n if (Array.isArray(content) && content.some((p) => p === null || p === undefined)) {\n return `messages[${i}].content must be a string or an array`;\n }\n const toolCalls = msg.tool_calls;\n if (checkToolCalls && toolCalls !== undefined && toolCalls !== null) {\n if (!Array.isArray(toolCalls)) {\n return `messages[${i}].tool_calls must be an array`;\n }\n for (const tc of toolCalls) {\n if (tc === null || typeof tc !== \"object\" || Array.isArray(tc)) {\n return `messages[${i}].tool_calls entries must be objects`;\n }\n const fn = (tc as Record<string, unknown>).function;\n if (fn === null || typeof fn !== \"object\" || Array.isArray(fn)) {\n return `messages[${i}].tool_calls entries must have a function object`;\n }\n }\n }\n }\n return null;\n}\n\n/**\n * Validate an optional top-level `tools` field. A string (or other\n * non-array) value passes the `req.tools && req.tools.length` gate\n * (`\"hi\".length > 0`) and then throws on `.map` — a 500 instead of a 400.\n * Returns an error detail string, or null when absent or a valid array.\n */\nexport function validateToolsField(tools: unknown): string | null {\n if (tools !== undefined && tools !== null && !Array.isArray(tools)) {\n return \"tools must be an array\";\n }\n if (Array.isArray(tools)) {\n for (let i = 0; i < tools.length; i++) {\n const t = tools[i] as Record<string, unknown> | null;\n if (t === null || typeof t !== \"object\" || Array.isArray(t)) {\n return `tools[${i}] must be an object`;\n }\n }\n }\n return null;\n}\n\n// ─── Request body helpers ──────────────────────────────────────────────────\n\n/**\n * True when a parsed JSON request body is a plain object.\n *\n * `JSON.parse(\"null\")` yields `null`, and `\"123\"` / `\"\\\"hi\\\"\"` yield\n * non-objects — every handler that then reads `body.field` throws a TypeError\n * that surfaces as a 500. Call this right after `JSON.parse` and answer 400\n * (\"Request body must be a JSON object\") instead. Arrays are rejected too:\n * `[].field` reads as `undefined` today, which degrades into a misleading\n * \"missing parameter\" 400 — rejecting up front names the real problem.\n */\nexport function isJsonObject(body: unknown): body is Record<string, unknown> {\n return body !== null && typeof body === \"object\" && !Array.isArray(body);\n}\n\n// ─── Embedding helpers ─────────────────────────────────────────────────────\n\nconst DEFAULT_EMBEDDING_DIMENSIONS = 1536;\n\n/**\n * Maximum embedding dimensions accepted by POST /v1/embeddings.\n *\n * A serialization budget, not an allocation bound. The ECMAScript array-length\n * bound (2**32 - 1) is where `new Array(n)` throws RangeError, but the handler\n * does not stop at allocating: `generateDeterministicEmbedding` fills the array\n * and the response is JSON-serialized, so at that bound a single request aborts\n * the process (\"FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript\n * heap out of memory\") before any response is written — taking every other test\n * sharing the server with it. A cap the server cannot serve is not a cap.\n *\n * Derivation, measured on this tree: a response body costs 19.58 bytes per\n * dimension (4096 -> 80,456 B; 100,000 -> 1,958,539 B; 1,000,000 ->\n * 19,583,034 B). At 100,000 the body is ~1.96 MB, built in 7 ms at 83 MB RSS\n * under a 1 GB heap — comfortably serviceable.\n *\n * Deliberately still not a model width: /v1/embeddings also serves Azure and\n * every OpenAI-compatible server routed through COMPAT_SUFFIXES, where\n * 4096-dimension models are ordinary. 100,000 leaves >12x headroom over the\n * widest width in circulation while keeping every accepted request answerable.\n */\nexport const MAX_EMBEDDING_DIMENSIONS = 100_000;\n\n/**\n * Validate an embeddings `dimensions` parameter.\n * Returns the effective dimensions (default 1536) or null when invalid.\n * Valid: undefined/null (→ default), integer in [1, MAX_EMBEDDING_DIMENSIONS].\n */\nexport function validateEmbeddingDimensions(raw: unknown): number | null {\n // null means \"unset\" too — the previous code was `dimensions ?? 1536`.\n if (raw === undefined || raw === null) return 1536;\n if (typeof raw !== \"number\" || !Number.isInteger(raw)) return null;\n if (raw < 1 || raw > MAX_EMBEDDING_DIMENSIONS) return null;\n return raw;\n}\n\n/**\n * Normalize an embeddings `input` into the texts to embed.\n * Accepts every shape EmbeddingCreateParams.input declares\n * (openai/resources/embeddings.d.ts): `string | string[] | number[] | number[][]`,\n * the last two being pre-tokenized input. Returns null for shapes the API\n * rejects (non-string scalars, objects, mixed arrays).\n */\nexport function normalizeEmbeddingInput(raw: unknown): string[] | null {\n if (typeof raw === \"string\") return [raw];\n if (Array.isArray(raw)) {\n if (raw.every((el) => typeof el === \"string\")) return raw as string[];\n // number[] — one pre-tokenized input.\n if (raw.every((el) => typeof el === \"number\")) return [raw.join(\" \")];\n // number[][] — a batch of pre-tokenized inputs.\n if (raw.every((el) => Array.isArray(el) && el.every((t) => typeof t === \"number\"))) {\n return (raw as number[][]).map((tokens) => tokens.join(\" \"));\n }\n return null;\n }\n return null;\n}\n\n/**\n * Normalize a free-text field (moderation input, search/rerank query).\n * Accepts a string, or any array — joined with \" \" as before, so no array that\n * used to return 200 starts failing, and ModerationCreateParams' multimodal\n * parts (openai/resources/moderations.d.ts) contribute their `.text`. Returns\n * null only for non-string, non-array values, which used to 500.\n */\nexport function normalizeTextInput(raw: unknown): string | null {\n if (typeof raw === \"string\") return raw;\n if (Array.isArray(raw)) {\n return raw\n .map((el) => {\n if (typeof el === \"string\") return el;\n if (el === null || el === undefined) return \"\";\n if (typeof el === \"object\") {\n const part = el as { type?: unknown; text?: unknown };\n return part.type === \"text\" && typeof part.text === \"string\" ? part.text : \"\";\n }\n return String(el);\n })\n .join(\" \");\n }\n return null;\n}\n\n/**\n * Generate a deterministic embedding vector from input text.\n * Hashes the input with SHA-256 and spreads the hash bytes across\n * the requested number of dimensions, producing values in [-1, 1].\n */\nexport function generateDeterministicEmbedding(\n input: string,\n dimensions: number = DEFAULT_EMBEDDING_DIMENSIONS,\n): number[] {\n let currentHash = createHash(\"sha256\").update(input).digest();\n const embedding: number[] = new Array(dimensions);\n for (let i = 0; i < dimensions; i++) {\n if (i > 0 && i % 32 === 0) {\n currentHash = createHash(\"sha256\").update(currentHash).digest();\n }\n // Map 0-255 → -1.0 to 1.0\n embedding[i] = currentHash[i % 32] / 127.5 - 1;\n }\n return embedding;\n}\n\nexport interface EmbeddingAPIResponse {\n object: \"list\";\n data: { object: \"embedding\"; index: number; embedding: number[] }[];\n model: string;\n usage: { prompt_tokens: number; total_tokens: number };\n}\n\n/**\n * Build an OpenAI-format embeddings API response for one or more inputs.\n */\nexport function buildEmbeddingResponse(\n embeddings: number[][],\n model: string,\n usage?: { prompt_tokens?: number; total_tokens?: number },\n): EmbeddingAPIResponse {\n return {\n object: \"list\",\n data: embeddings.map((embedding, index) => ({\n object: \"embedding\" as const,\n index,\n embedding,\n })),\n model,\n usage: { prompt_tokens: usage?.prompt_tokens ?? 0, total_tokens: usage?.total_tokens ?? 0 },\n };\n}\n\n/**\n * Build a stable, human-readable identifier for a fixture's match shape, for\n * any log line that has to name WHICH fixture it means. The `Fixture` type\n * carries no `id`/`name`, so the matchers are the only handle a reader has.\n *\n * Used by the relaxed-turnIndex warning (`router.ts`) and by the chaos\n * rejected-value warning (`chaos.ts`); one implementation so the two name the\n * same fixture the same way. The obvious `JSON.stringify(match)` is unfit: it\n * DROPS `predicate` functions (non-serialisable) and serialises any RegExp\n * matcher to `{}`, so a predicate- or regex-gated fixture's warning collapsed to\n * an uninformative \"served fixture {}\" / `{\"userMessage\":{}}` blob.\n *\n * Instead we list the PRESENT matcher keys in declaration order, annotating each\n * by VALUE KIND so predicates and regexes survive: `predicate(fn)`,\n * `userMessage(regex)`, `userMessage(\"hello\")`, `turnIndex=0`, etc. The\n * fixture's array `index` is prefixed as a stable positional identifier when\n * the caller knows it (i.e. `>= 0`); callers holding only the fixture object\n * pass `-1` and get the matcher summary alone. String/number values are shown\n * inline (truncated) so a content match remains recognisable; the whole string\n * is capped to keep the log line bounded.\n */\nconst DESCRIBE_MATCH_MAX = 160;\n\nexport function describeMatch(match: FixtureMatch, index: number): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(match)) {\n if (value === undefined) continue;\n if (typeof value === \"function\") {\n parts.push(key === \"predicate\" ? \"[predicate]\" : `${key}[fn]`);\n } else if (value instanceof RegExp) {\n parts.push(`${key}(${value})`);\n } else if (typeof value === \"string\") {\n const v = value.length > 40 ? `${value.slice(0, 40)}…` : value;\n parts.push(`${key}(${JSON.stringify(v)})`);\n } else if (Array.isArray(value)) {\n parts.push(`${key}(${value.length} item${value.length === 1 ? \"\" : \"s\"})`);\n } else if (typeof value === \"object\" && value !== null) {\n // `String(obj)` is \"[object Object]\"; show the (bounded) JSON instead.\n const json = JSON.stringify(value);\n parts.push(`${key}(${json.length > 40 ? `${json.slice(0, 40)}…` : json})`);\n } else {\n parts.push(`${key}=${String(value)}`);\n }\n }\n const keys = parts.length > 0 ? parts.join(\", \") : \"no matchers\";\n const prefix = index >= 0 ? `#${index} ` : \"\";\n const full = `${prefix}{ ${keys} }`;\n if (full.length <= DESCRIBE_MATCH_MAX) return full;\n // Cut at a matcher boundary (never mid-token) and mark the elision; a lone\n // oversized matcher is hard-cut but still marked.\n const head = full.slice(0, DESCRIBE_MATCH_MAX - 4);\n const cut = head.lastIndexOf(\", \");\n return `${cut > 0 ? head.slice(0, cut) : head}, … }`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"helpers.js","names":[],"sources":["../src/helpers.ts"],"sourcesContent":["import type { LiveFixtureResponse } from \"./live-types.js\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport type * as http from \"node:http\";\nimport type { IncomingHttpHeaders } from \"node:http\";\nimport { DEFAULT_TEST_ID, MCP_FAKES_ECHO_LIMIT } from \"./constants.js\";\nimport { cutText } from \"./echo-text.js\";\nimport { build, fixed, msg, quote, type MessageValue } from \"./message-text.js\";\nimport type { Logger } from \"./logger.js\";\nimport { isReasoningModel } from \"./model-utils.js\";\nimport { isRecognizedApiKeyHeader } from \"./api-key-auth.js\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n FixtureMatch,\n FixtureResponse,\n ResponseFactory,\n TextResponse,\n ToolCallResponse,\n ContentWithToolCallsResponse,\n ErrorResponse,\n EmbeddingResponse,\n ImageResponse,\n AudioResponse,\n TranscriptionResponse,\n VideoResponse,\n RawJSONResponse,\n SSEChunk,\n ToolCall,\n FixtureBlock,\n FixtureFileBlock,\n ChatCompletion,\n ResponseOverrides,\n RecordConfig,\n RecordProviderKey,\n McpFakeIdentity,\n McpFakeUndeclaredPolicy,\n} from \"./types.js\";\nimport type { MCPSession } from \"./mcp-types.js\";\n\n/**\n * Resolve effective strict mode from per-request header and server default.\n * Header values override the server default — same precedence pattern as chaos\n * config headers (see resolveChaosConfig in chaos.ts).\n *\n * Header: `X-AIMock-Strict` — \"true\"/\"1\" → strict on, \"false\"/\"0\" → strict off\n * (case-insensitive, surrounding whitespace trimmed).\n * When absent or unrecognised, falls back to the server-level default.\n */\nexport function resolveStrictMode(\n serverDefault: boolean | undefined,\n rawHeaders?: IncomingHttpHeaders,\n): boolean {\n if (rawHeaders) {\n const header = rawHeaders[\"x-aimock-strict\"];\n const rawVal =\n typeof header === \"string\" ? header : Array.isArray(header) ? header[0] : undefined;\n if (typeof rawVal === \"string\") {\n const val = rawVal.trim().toLowerCase();\n if (val === \"true\" || val === \"1\") return true;\n if (val === \"false\" || val === \"0\") return false;\n }\n }\n return serverDefault ?? false;\n}\n\nconst STRICT_INTEGER_RE = /^\\d+$/;\n\n/**\n * Strict decimal-integer grammar shared by the query/list parsers that grew\n * their own local copy on separate branches (files `limit`, fine-tuning\n * `limit`, chaos integer fields): an unsigned digit run and nothing else — no\n * sign, no exponent, no radix prefix, no surrounding whitespace (callers that\n * accept padded input trim before calling).\n *\n * Deliberately narrower than `Number()`, which reads `0x10` as 16, `1e3` as\n * 1000 and strips whitespace and a leading `+`. Returns the value, or `null`\n * when the text is not a safe integer literal. Range checks and error\n * messages stay with the callers — this is the grammar gate only, so sharing\n * it cannot change any surface's accepted range or its 400 text.\n */\nexport function parseStrictIntegerText(text: string): number | null {\n if (!STRICT_INTEGER_RE.test(text)) return null;\n const n = Number(text);\n return Number.isSafeInteger(n) ? n : null;\n}\n\n/**\n * Would a fixture miss on this request have been forwarded upstream? This is\n * the ONE rule behind the no-fixture chaos journal `source` label: \"proxy\"\n * only when record mode has an upstream for THIS provider and strict mode is\n * not refusing the miss first. `effectiveStrict` is the header-resolved value\n * from {@link resolveStrictMode}. `record.providers` is read guarded — a JS\n * caller can hand over `record: {}`.\n */\nexport function wouldProxyMiss(\n effectiveStrict: boolean,\n record: RecordConfig | undefined,\n providerKey: RecordProviderKey | undefined,\n): boolean {\n if (effectiveStrict || providerKey === undefined) return false;\n return Boolean(record?.providers?.[providerKey]);\n}\n\n/**\n * Returns `true` or `false` when the X-AIMock-Strict header overrides the\n * server default, or `undefined` when it doesn't. Designed to be spread\n * directly into a journal entry's `response` object:\n *\n * response: { status, fixture, ...strictOverrideField(defaults.strict, req.headers) }\n */\nexport function strictOverrideField(\n serverDefault: boolean | undefined,\n rawHeaders?: IncomingHttpHeaders,\n): { strictOverride?: boolean } {\n const effective = resolveStrictMode(serverDefault, rawHeaders);\n if (effective !== (serverDefault ?? false)) {\n return { strictOverride: effective };\n }\n return {};\n}\n\n/**\n * Build the strict-mode 503 error message, distinguishing a true no-match from\n * a sequence/turn-exhausted miss.\n *\n * `skippedBySequenceOrTurn` is the count reported by `matchFixtureDiagnostic`\n * (router.ts): the number of fixtures that matched the request SHAPE but were\n * rejected ONLY by their `sequenceIndex`/`turnIndex` count state.\n *\n * - `0` → `\"Strict mode: no fixture matched\"` (no candidate had a matching shape)\n * - `>0` → `\"Strict mode: N candidate fixture(s) skipped by sequence/turn state\"`\n *\n * The HTTP status (503) and error envelope shape are unchanged at every call\n * site — only this message string differs. Endpoints with no sequence/turn\n * gates always pass `0` and therefore see the generic message.\n */\nexport function strictNoMatchMessage(skippedBySequenceOrTurn: number): string {\n if (skippedBySequenceOrTurn > 0) {\n return `Strict mode: ${skippedBySequenceOrTurn} candidate fixture(s) skipped by sequence/turn state`;\n }\n return \"Strict mode: no fixture matched\";\n}\n\n/**\n * Build the strict-mode error LOG line, mirroring {@link strictNoMatchMessage}'s\n * disambiguation so the error log distinguishes the two miss kinds too.\n */\nexport function strictNoMatchLogLine(\n method: string,\n url: string,\n skippedBySequenceOrTurn: number,\n): string {\n if (skippedBySequenceOrTurn > 0) {\n return `STRICT: ${skippedBySequenceOrTurn} candidate fixture(s) skipped by sequence/turn state for ${method} ${url}`;\n }\n return `STRICT: No fixture matched for ${method} ${url}`;\n}\n\n/**\n * Resolve the reasoning string to actually emit for a given model.\n *\n * aimock synthesizes a reasoning channel whenever a fixture carries a\n * `reasoning` string, regardless of the requested model. But a non-reasoning\n * model (e.g. `gpt-4.1`) would emit no reasoning against the real provider, so\n * replaying it is a false green (see aimock#254). This gates the emission on\n * the requested model's capability:\n *\n * - no fixture reasoning → undefined (no-op, short-circuit)\n * - reasoning-capable model → emit unchanged, no log\n * - non-reasoning model, strict OFF → `logger.warn`, still emit (preserves\n * current behavior)\n * - non-reasoning model, strict ON → `logger.error`, suppress (return undefined)\n *\n * Capability is decided from the REQUESTED model id (what the backend was wired\n * to), not any `overrides.model` echoed in the payload.\n */\nexport function resolveReasoningForModel(\n reasoning: string | undefined,\n model: string | undefined,\n strict: boolean,\n logger: Logger,\n): string | undefined {\n if (!reasoning) return undefined;\n if (isReasoningModel(model)) return reasoning;\n if (strict) {\n logger.error(\n `Strict mode: fixture has a reasoning channel but model \"${model}\" is not reasoning-capable — suppressing reasoning emission`,\n );\n return undefined;\n }\n logger.warn(\n `Fixture has a reasoning channel but model \"${model}\" is not reasoning-capable — the real provider would emit no reasoning. Emitting anyway (set X-AIMock-Strict: true to suppress).`,\n );\n return reasoning;\n}\n\n/**\n * Resolve the encrypted reasoning artifacts (`reasoningSignature` and\n * `redactedThinking`) to actually emit for a given model.\n *\n * `redacted_thinking` blocks and a thinking `signature` ARE part of the\n * reasoning channel — they are just the encrypted form of it — so they must be\n * gated on the same model-capability resolution as the plaintext `reasoning`\n * string (see resolveReasoningForModel). Gating only the plaintext channel\n * leaves a half-gated reasoning path: replaying a fixture recorded from a\n * reasoning model against a non-reasoning model would strip the `thinking`\n * block but still emit `redacted_thinking` blocks, which the real provider for\n * that model would never produce.\n *\n * Capability is decided from the REQUESTED model id, independently of whether a\n * plaintext `reasoning` string is present — a fixture may carry only\n * `redactedThinking` with no plaintext reasoning.\n *\n * - reasoning-capable model → emit both unchanged, no log\n * - non-reasoning model, no artifacts → no-op, nothing to suppress\n * - non-reasoning model, strict OFF → `logger.warn`, still emit (preserves\n * current behavior)\n * - non-reasoning model, strict ON → `logger.error`, suppress both\n *\n * Must be invoked alongside resolveReasoningForModel with identical model/strict\n * inputs so the plaintext and encrypted channels stay suppressed together.\n */\nexport function resolveReasoningArtifactsForModel(\n reasoningSignature: string | undefined,\n redactedThinking: string[] | undefined,\n model: string | undefined,\n strict: boolean,\n logger: Logger,\n): { reasoningSignature?: string; redactedThinking?: string[] } {\n const hasArtifacts = reasoningSignature !== undefined || (redactedThinking?.length ?? 0) > 0;\n if (!hasArtifacts || isReasoningModel(model)) {\n return { reasoningSignature, redactedThinking };\n }\n if (strict) {\n logger.error(\n `Strict mode: fixture has encrypted reasoning artifacts (redacted_thinking/signature) but model \"${model}\" is not reasoning-capable — suppressing reasoning emission`,\n );\n return {};\n }\n logger.warn(\n `Fixture has encrypted reasoning artifacts (redacted_thinking/signature) but model \"${model}\" is not reasoning-capable — the real provider would emit no reasoning. Emitting anyway (set X-AIMock-Strict: true to suppress).`,\n );\n return { reasoningSignature, redactedThinking };\n}\n\nexport function flattenHeaders(headers: http.IncomingHttpHeaders): Record<string, string> {\n const flat: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue;\n if (isRecognizedApiKeyHeader(key)) {\n flat[key] = \"[REDACTED]\";\n } else {\n flat[key] = Array.isArray(value) ? value.join(\", \") : value;\n }\n }\n return flat;\n}\n\n/** Structural discriminator; registration performs full transcript validation. */\nexport function isLiveResponse(value: unknown): value is LiveFixtureResponse {\n return value !== null && typeof value === \"object\" && !Array.isArray(value) && \"live\" in value;\n}\n\nexport function isResponseFactory(r: FixtureResponse | ResponseFactory): r is ResponseFactory {\n return typeof r === \"function\";\n}\n\nexport async function resolveResponse(\n fixture: Fixture,\n request: ChatCompletionRequest,\n): Promise<FixtureResponse> {\n if (typeof fixture.response === \"function\") {\n try {\n const raw = await fixture.response(request);\n return normalizeFactoryResponse(raw);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Response factory threw: ${msg}`, { cause: err });\n }\n }\n return fixture.response;\n}\n\nfunction normalizeFactoryResponse(raw: FixtureResponse): FixtureResponse {\n const r = { ...raw } as Record<string, unknown>;\n if (typeof r.content === \"object\" && r.content !== null) {\n r.content = JSON.stringify(r.content);\n }\n if (Array.isArray(r.toolCalls)) {\n r.toolCalls = (r.toolCalls as Array<Record<string, unknown>>).map((tc) => {\n if (typeof tc.arguments === \"object\" && tc.arguments !== null) {\n return { ...tc, arguments: JSON.stringify(tc.arguments) };\n }\n return { ...tc };\n });\n }\n // Mirror the toolCalls[].arguments idiom for the optional ordered `blocks`\n // array: auto-stringify object `arguments` on each `toolCall` block so a\n // programmatic ResponseFactory may return objects (resolveFixtureBlocks\n // requires string `arguments`). Text blocks and string arguments pass\n // through unchanged. Matches the loader's block handling.\n if (Array.isArray(r.blocks)) {\n r.blocks = (r.blocks as Array<Record<string, unknown>>).map((block) => {\n if (\n block != null &&\n block.type === \"toolCall\" &&\n typeof block.arguments === \"object\" &&\n block.arguments !== null\n ) {\n return { ...block, arguments: JSON.stringify(block.arguments) };\n }\n return { ...block };\n });\n }\n return r as unknown as FixtureResponse;\n}\n\nexport function generateId(prefix = \"chatcmpl\"): string {\n return `${prefix}-${randomBytes(12).toString(\"base64url\")}`;\n}\n\n/**\n * Resolve the request id for this HTTP request.\n *\n * - When the caller sends a well-formed `X-Request-Id` (1-128 chars of\n * `A-Za-z0-9-_.:`), it is echoed verbatim so distributed traces correlate.\n * - Otherwise (absent, empty, too long, or illegal characters) a fresh\n * `req-…` id is generated — never trust an attacker-controlled correlation\n * value to be a valid log key.\n *\n * Returns `{ id, generated }`. The server normalizes\n * `req.headers[\"x-request-id\"]` to the resolved value, so every downstream\n * `flattenHeaders` journal snapshot carries it with zero per-handler edits —\n * which also means a MINTED id would otherwise ride the egress header set to\n * a real provider. The server therefore passes `generated` to\n * `markMintedRequestId` so `buildForwardHeaders` can drop it; a caller's own\n * id still forwards verbatim.\n */\nexport function resolveRequestId(rawHeaders: http.IncomingHttpHeaders): {\n id: string;\n generated: boolean;\n} {\n const raw = rawHeaders[\"x-request-id\"];\n const first = Array.isArray(raw) ? raw[0] : raw;\n if (typeof first === \"string\") {\n const trimmed = first.trim();\n if (trimmed.length >= 1 && trimmed.length <= 128 && /^[A-Za-z0-9\\-_.:]+$/.test(trimmed)) {\n return { id: trimmed, generated: false };\n }\n }\n return { id: generateId(\"req\"), generated: true };\n}\n\n/**\n * Requests whose `x-request-id` aimock MINTED (the caller sent none, or sent\n * an unusable one). The normalized header is indistinguishable from a\n * caller-supplied one by inspection, so the provenance is tracked out-of-band\n * and keyed on the request object, exactly like the egress auth marker.\n */\nconst mintedRequestIds = new WeakSet<http.IncomingMessage>();\n\n/** @internal Record that this request's `x-request-id` is aimock's own. */\nexport function markMintedRequestId(req: http.IncomingMessage, generated: boolean): void {\n if (generated) mintedRequestIds.add(req);\n}\n\n/**\n * @internal True when `x-request-id` on this request was minted by aimock.\n * Egress paths use it to avoid transmitting an invented correlation id to a\n * real provider.\n */\nexport function hasMintedRequestId(req: http.IncomingMessage): boolean {\n return mintedRequestIds.has(req);\n}\n\nexport function generateToolCallId(): string {\n return `call_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function generateMessageId(): string {\n return `msg_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function generateToolUseId(): string {\n return `toolu_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function isTextResponse(r: FixtureResponse): r is TextResponse {\n return \"content\" in r && typeof (r as TextResponse).content === \"string\" && !(\"toolCalls\" in r);\n}\n\nexport function isToolCallResponse(r: FixtureResponse): r is ToolCallResponse {\n return (\n \"toolCalls\" in r &&\n Array.isArray((r as ToolCallResponse).toolCalls) &&\n !(\"content\" in r && typeof (r as unknown as Record<string, unknown>).content === \"string\")\n );\n}\n\nexport function isContentWithToolCallsResponse(\n r: FixtureResponse,\n): r is ContentWithToolCallsResponse {\n const o = r as ContentWithToolCallsResponse;\n // LEGACY / COMBINED shape — BOTH content (string) + toolCalls (array). This\n // clause is byte-identical to the original guard, so every fixture that\n // matched before still matches here and is classified exactly as before.\n const hasContentAndToolCalls =\n \"content\" in r &&\n typeof o.content === \"string\" &&\n \"toolCalls\" in r &&\n Array.isArray(o.toolCalls);\n // BLOCKS-ONLY shape (additive, #274 F0) — a non-empty `blocks` array with no\n // content/toolCalls. This is a pure RELAXATION: it recognizes MORE, never\n // reclassifies an existing fixture. A blocks-only fixture cannot be claimed by\n // any earlier/looser guard in the dispatch order — `isTextResponse` requires a\n // string `content` AND `!(\"toolCalls\" in r)`, and `isToolCallResponse`\n // requires a `toolCalls` array — so it would otherwise fall through to 500.\n // `isAudioResponse` (checked first everywhere) requires an `audio` field, which\n // blocks-only lacks, so there is no overlap there either.\n const hasNonEmptyBlocks = Array.isArray(o.blocks) && o.blocks.length > 0;\n return hasContentAndToolCalls || hasNonEmptyBlocks;\n}\n\n/**\n * Validate and pass through the ordered `blocks` field of a combined\n * content+toolCalls fixture. Used ONLY on the new block-iteration path (when a\n * fixture explicitly sets `blocks`); it is NOT a legacy-order reconstructor —\n * fixtures without `blocks` never reach this function and keep their unchanged\n * text-first path.\n *\n * An EMPTY `blocks` array is treated as \"no blocks\" by every builder's\n * streaming gate (`blocks && blocks.length > 0`), so it falls back to the\n * legacy `{content, toolCalls}` path and never reaches this function — the gate\n * is the single source of truth for \"has blocks\". This validator therefore only\n * ever runs on a non-empty array.\n *\n * Accepts the relaxed on-disk {@link FixtureFileBlock} input shape — a\n * `toolCall` block's `arguments` may be a string OR a JSON object/array — which\n * makes the object-tolerance below type-visible and mirrors how\n * normalizeResponse types its file-form input (see {@link FixtureFileBlock} /\n * {@link FixtureFileContentWithToolCallsResponse}). The in-memory\n * {@link FixtureBlock} form (string `arguments`) is a structural subtype, so\n * existing callers that pass `FixtureBlock[]` continue to type-check.\n *\n * Returns the blocks in array order, NORMALIZED to {@link FixtureBlock}: a\n * `text` block with a string `text`, or a `toolCall` block with string `name` +\n * string `arguments` (object/array `arguments` is JSON.stringified) and an\n * optional string `id`. The return type guarantees `arguments: string` — every\n * caller relies on that. Throws on a malformed array or entry — same fail-fast\n * idiom as the other fixture validators in this module (see e.g. the factory\n * guard at {@link resolveResponse}).\n */\nexport function resolveFixtureBlocks(blocks: FixtureFileBlock[]): FixtureBlock[] {\n if (!Array.isArray(blocks)) {\n throw new Error(`Invalid fixture blocks: expected an array, got ${typeof blocks}`);\n }\n // Validate each block and return a normalized COPY. Builders iterate the\n // result and must not observe later mutations of — nor be able to mutate —\n // the caller's stored fixture array, and block objects are consumed read-only\n // downstream, so we never mutate the input in place: any normalization (e.g.\n // stringifying object `arguments`) is applied to a fresh per-block copy.\n return blocks.map((block, i) => {\n if (block === null || typeof block !== \"object\") {\n throw new Error(`Invalid fixture block at index ${i}: expected an object`);\n }\n const b = block as Record<string, unknown>;\n if (b.type === \"text\") {\n if (typeof b.text !== \"string\") {\n throw new Error(\n `Invalid fixture block at index ${i}: \"text\" block requires a string \"text\" field`,\n );\n }\n return { type: \"text\", text: b.text };\n } else if (b.type === \"toolCall\") {\n if (typeof b.name !== \"string\") {\n throw new Error(\n `Invalid fixture block at index ${i}: \"toolCall\" block requires a string \"name\" field`,\n );\n }\n if (b.id !== undefined && typeof b.id !== \"string\") {\n throw new Error(\n `Invalid fixture block at index ${i}: \"toolCall\" block \"id\" must be a string when present`,\n );\n }\n // `arguments` is a JSON string in normalized (file-load) form. The\n // programmatic path (addFixture/addFixtures/prependFixture) stores RAW\n // fixtures with no normalizeResponse pass, so an OBJECT `arguments` can\n // reach here. Be tolerant: stringify an object/array (mirroring\n // normalizeResponse's `JSON.stringify`) into a fresh block copy so the\n // programmatic path is safe and the caller's stored fixture is untouched.\n // A string stays byte-identical (file-load path unchanged); any other\n // type is still rejected.\n if (typeof b.arguments === \"object\" && b.arguments !== null) {\n return { ...b, arguments: JSON.stringify(b.arguments) } as unknown as FixtureBlock;\n }\n if (typeof b.arguments !== \"string\") {\n throw new Error(\n `Invalid fixture block at index ${i}: \"toolCall\" block requires a string or object \"arguments\" field`,\n );\n }\n return { ...b, type: \"toolCall\", name: b.name, arguments: b.arguments } as FixtureBlock;\n } else {\n throw new Error(\n `Invalid fixture block at index ${i}: unknown type ${JSON.stringify(b.type)} (expected \"text\" or \"toolCall\")`,\n );\n }\n });\n}\n\n/** Project normalized ordered blocks without changing the fixture or generating ids. */\nexport function resolveFixtureBlockOutcome(blocks: FixtureFileBlock[]) {\n const ordered = resolveFixtureBlocks(blocks);\n let content = \"\";\n const toolCalls: ToolCall[] = [];\n for (const block of ordered) {\n if (block.type === \"text\") content += block.text;\n else\n toolCalls.push({\n name: block.name,\n arguments: block.arguments,\n ...(block.id !== undefined ? { id: block.id } : {}),\n });\n }\n return { ordered, content, toolCalls, hasToolCalls: toolCalls.length > 0 };\n}\n\nexport function isErrorResponse(r: FixtureResponse): r is ErrorResponse {\n return (\n \"error\" in r &&\n (r as ErrorResponse).error !== null &&\n typeof (r as ErrorResponse).error === \"object\" &&\n \"message\" in ((r as ErrorResponse).error as Record<string, unknown>) &&\n typeof ((r as ErrorResponse).error as Record<string, unknown>).message === \"string\"\n );\n}\n\n/**\n * Serialize an ErrorResponse to JSON, stripping the internal-only `status`\n * field that controls the HTTP status code but should never appear in the\n * response body. Real LLM APIs don't include it.\n */\nexport function serializeErrorResponse(response: ErrorResponse): string {\n return JSON.stringify({\n error: {\n message: response.error.message,\n type: response.error.type ?? \"server_error\",\n param: response.error.param ?? null,\n code: response.error.code ?? null,\n },\n });\n}\n\nexport function isEmbeddingResponse(r: FixtureResponse): r is EmbeddingResponse {\n return \"embedding\" in r && Array.isArray((r as EmbeddingResponse).embedding);\n}\n\nexport function isImageResponse(r: FixtureResponse): r is ImageResponse {\n return (\n (\"image\" in r && typeof r.image === \"object\" && r.image != null) ||\n (\"images\" in r && Array.isArray((r as ImageResponse).images))\n );\n}\n\nexport function isAudioResponse(r: FixtureResponse): r is AudioResponse {\n if (!(\"audio\" in r)) return false;\n const a = (r as AudioResponse).audio;\n return typeof a === \"string\" || (typeof a === \"object\" && a !== null && \"b64Json\" in a);\n}\n\n/**\n * Map audio format shorthand to MIME content types.\n * Shared between speech, ElevenLabs, and fal audio handlers.\n */\nexport const FORMAT_TO_CONTENT_TYPE: Record<string, string> = {\n mp3: \"audio/mpeg\",\n opus: \"audio/opus\",\n aac: \"audio/aac\",\n flac: \"audio/flac\",\n wav: \"audio/wav\",\n pcm: \"audio/pcm\",\n};\n\n/**\n * Resolve a format string (e.g. \"mp3\", \"opus\") to its MIME content type.\n * Falls back to \"application/octet-stream\" for unknown formats.\n */\nexport function formatToMime(format: string): string {\n return FORMAT_TO_CONTENT_TYPE[format] ?? \"application/octet-stream\";\n}\n\nexport function isTranscriptionResponse(r: FixtureResponse): r is TranscriptionResponse {\n return (\n \"transcription\" in r &&\n (r as TranscriptionResponse).transcription != null &&\n typeof (r as TranscriptionResponse).transcription === \"object\"\n );\n}\n\nexport function isVideoResponse(r: FixtureResponse): r is VideoResponse {\n return (\n \"video\" in r &&\n (r as VideoResponse).video != null &&\n typeof (r as VideoResponse).video === \"object\"\n );\n}\n\nexport function isJSONResponse(r: FixtureResponse): r is RawJSONResponse {\n return \"json\" in r && (r as RawJSONResponse).json !== undefined;\n}\n\nexport function extractOverrides(\n response: TextResponse | ToolCallResponse | ContentWithToolCallsResponse,\n): ResponseOverrides {\n const r = response;\n return {\n ...(r.id !== undefined && { id: r.id }),\n ...(r.created !== undefined && { created: r.created }),\n ...(r.model !== undefined && { model: r.model }),\n ...(r.usage !== undefined && { usage: r.usage }),\n ...(r.systemFingerprint !== undefined && { systemFingerprint: r.systemFingerprint }),\n ...(r.finishReason !== undefined && { finishReason: r.finishReason }),\n ...(r.role !== undefined && { role: r.role }),\n ...(r.provider !== undefined && { provider: r.provider }),\n ...(r.nativeFinishReason !== undefined && { nativeFinishReason: r.nativeFinishReason }),\n };\n}\n\n// ─── Token estimation ────────────────────────────────────────────────────\n\n/**\n * Rough token count estimation based on character length.\n * Uses the ~4 characters per token heuristic common for English text.\n */\nexport function estimateTokens(text: string): number {\n return Math.max(1, Math.ceil(text.length / 4));\n}\n\n/**\n * Estimate prompt tokens from a request's messages array.\n */\nexport function estimatePromptTokens(messages: ChatCompletionRequest[\"messages\"]): number {\n let totalChars = 0;\n for (const msg of messages) {\n if (typeof msg.content === \"string\") {\n totalChars += msg.content.length;\n } else if (Array.isArray(msg.content)) {\n for (const part of msg.content) {\n if (part.text) totalChars += part.text.length;\n }\n }\n }\n return Math.max(1, Math.ceil(totalChars / 4));\n}\n\n/**\n * Build usage object: use explicit overrides if provided, otherwise estimate.\n *\n * Shared by BOTH the non-streaming completion builders (below) and the\n * streaming usage-chunk sites in server.ts — the single source of truth for\n * \"explicit token override wins, cost-only override still estimates\". Exported\n * so the streaming path does not re-implement the estimation logic.\n */\nexport function resolveUsage(\n overrides: ResponseOverrides | undefined,\n promptText: string,\n completionText: string,\n): { prompt_tokens: number; completion_tokens: number; total_tokens: number } {\n if (overrides?.usage) {\n const u = overrides.usage;\n // A usage override that scripts only cost fields (e.g. `{ cost: 0.5 }`)\n // carries NO token counts. Real providers always report real token usage,\n // so estimate the counts rather than forcing them to 0 — otherwise a\n // cost-scripting fixture is unfaithful (cost present, tokens 0). When the\n // override DOES set any token count we preserve the explicit-or-zero merge\n // behavior, and any explicit token value always wins.\n const hasExplicitTokens =\n u.prompt_tokens !== undefined ||\n u.completion_tokens !== undefined ||\n u.total_tokens !== undefined ||\n u.input_tokens !== undefined ||\n u.output_tokens !== undefined ||\n u.promptTokenCount !== undefined ||\n u.candidatesTokenCount !== undefined ||\n u.totalTokenCount !== undefined;\n const fallbackPrompt = hasExplicitTokens ? 0 : estimateTokens(promptText || \"x\");\n const fallbackCompletion = hasExplicitTokens ? 0 : estimateTokens(completionText || \"x\");\n const prompt = u.prompt_tokens ?? u.input_tokens ?? u.promptTokenCount ?? fallbackPrompt;\n const completion =\n u.completion_tokens ?? u.output_tokens ?? u.candidatesTokenCount ?? fallbackCompletion;\n return {\n prompt_tokens: prompt,\n completion_tokens: completion,\n total_tokens: u.total_tokens ?? u.totalTokenCount ?? prompt + completion,\n };\n }\n const prompt = estimateTokens(promptText || \"x\");\n const completion = estimateTokens(completionText || \"x\");\n return {\n prompt_tokens: prompt,\n completion_tokens: completion,\n total_tokens: prompt + completion,\n };\n}\n\n/**\n * Build an SSE usage chunk for streaming responses.\n * OpenAI emits this as the final chunk before [DONE] when\n * stream_options.include_usage is true. It has an empty choices array.\n */\nexport function buildUsageChunk(\n id: string,\n model: string,\n created: number,\n usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number },\n fingerprint?: string,\n): SSEChunk {\n return {\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [],\n usage,\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n };\n}\n\nexport function buildTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): SSEChunk[] {\n const id = overrides?.id ?? generateId();\n const created = overrides?.created ?? Math.floor(Date.now() / 1000);\n const effectiveModel = overrides?.model ?? model;\n const chunks: SSEChunk[] = [];\n const fingerprint = overrides?.systemFingerprint;\n\n // Reasoning chunks (emitted before content, OpenRouter format)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n { index: 0, delta: { reasoning_content: slice }, logprobs: null, finish_reason: null },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Role chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: { role: overrides?.role ?? \"assistant\", content: \"\" },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Content chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [{ index: 0, delta: { content: slice }, logprobs: null, finish_reason: null }],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n\n // Finish chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n { index: 0, delta: {}, logprobs: null, finish_reason: overrides?.finishReason ?? \"stop\" },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n return chunks;\n}\n\nexport function buildToolCallChunks(\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): SSEChunk[] {\n const id = overrides?.id ?? generateId();\n const created = overrides?.created ?? Math.floor(Date.now() / 1000);\n const effectiveModel = overrides?.model ?? model;\n const chunks: SSEChunk[] = [];\n const fingerprint = overrides?.systemFingerprint;\n\n // Reasoning chunks (emitted before tool calls, OpenRouter format)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n { index: 0, delta: { reasoning_content: slice }, logprobs: null, finish_reason: null },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Role chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: { role: overrides?.role ?? \"assistant\", content: null },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Tool call chunks — one initial chunk per tool call, then argument chunks\n for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {\n const tc = toolCalls[tcIdx];\n const tcId = tc.id || generateToolCallId();\n\n // Initial tool call chunk (id + function name)\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: tcIdx,\n id: tcId,\n type: \"function\",\n function: { name: tc.name, arguments: \"\" },\n },\n ],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Argument streaming chunks\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n const slice = args.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [{ index: tcIdx, function: { arguments: slice } }],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Finish chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {},\n logprobs: null,\n finish_reason: overrides?.finishReason ?? \"tool_calls\",\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n return chunks;\n}\n\n// Non-streaming response builders\n\nexport function buildTextCompletion(\n content: string,\n model: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n requestMessages?: ChatCompletionRequest[\"messages\"],\n): ChatCompletion {\n const promptText = requestMessages\n ? requestMessages\n .map((m) =>\n typeof m.content === \"string\"\n ? m.content\n : Array.isArray(m.content)\n ? m.content.map((p) => p.text ?? \"\").join(\"\")\n : \"\",\n )\n .join(\"\")\n : \"\";\n return {\n id: overrides?.id ?? generateId(),\n object: \"chat.completion\",\n created: overrides?.created ?? Math.floor(Date.now() / 1000),\n model: overrides?.model ?? model,\n choices: [\n {\n index: 0,\n message: {\n role: overrides?.role ?? \"assistant\",\n content,\n refusal: null,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n },\n logprobs: null,\n finish_reason: overrides?.finishReason ?? \"stop\",\n },\n ],\n usage: resolveUsage(overrides, promptText, content),\n ...(overrides?.systemFingerprint !== undefined && {\n system_fingerprint: overrides.systemFingerprint,\n }),\n };\n}\n\nexport function buildToolCallCompletion(\n toolCalls: ToolCall[],\n model: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n requestMessages?: ChatCompletionRequest[\"messages\"],\n): ChatCompletion {\n const promptText = requestMessages\n ? requestMessages\n .map((m) =>\n typeof m.content === \"string\"\n ? m.content\n : Array.isArray(m.content)\n ? m.content.map((p) => p.text ?? \"\").join(\"\")\n : \"\",\n )\n .join(\"\")\n : \"\";\n const completionText = toolCalls.map((tc) => tc.name + tc.arguments).join(\"\");\n return {\n id: overrides?.id ?? generateId(),\n object: \"chat.completion\",\n created: overrides?.created ?? Math.floor(Date.now() / 1000),\n model: overrides?.model ?? model,\n choices: [\n {\n index: 0,\n message: {\n role: overrides?.role ?? \"assistant\",\n content: null,\n refusal: null,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n tool_calls: toolCalls.map((tc) => ({\n id: tc.id || generateToolCallId(),\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n })),\n },\n logprobs: null,\n finish_reason: overrides?.finishReason ?? \"tool_calls\",\n },\n ],\n usage: resolveUsage(overrides, promptText, completionText),\n ...(overrides?.systemFingerprint !== undefined && {\n system_fingerprint: overrides.systemFingerprint,\n }),\n };\n}\n\nexport function buildContentWithToolCallsChunks(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n blocks?: FixtureBlock[],\n): SSEChunk[] {\n const id = overrides?.id ?? generateId();\n const created = overrides?.created ?? Math.floor(Date.now() / 1000);\n const effectiveModel = overrides?.model ?? model;\n const chunks: SSEChunk[] = [];\n const fingerprint = overrides?.systemFingerprint;\n\n if (blocks && blocks.length > 0) {\n // NEW: emit chunks in fixture block array order.\n //\n // DEGENERATE PROVIDER NOTE: in OpenAI chat-completions, `delta.content` and\n // `delta.tool_calls` are SEPARATE channels that the client merges with no\n // positional interleaving. So \"tool-call-before-text\" is NOT semantically\n // observable to a real client — it reassembles content and tool calls into\n // their own buckets regardless of chunk order. We still emit honest\n // array-order chunks (the SSE chunk SEQUENCE is the contract this path\n // asserts), but we do NOT fake interleaving the channel cannot express.\n const outcome = resolveFixtureBlockOutcome(blocks);\n const ordered = outcome.ordered;\n\n // Reasoning chunks (emitted first, OpenRouter format) — unchanged from legacy.\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n ...(i === 0 && { role: overrides?.role ?? \"assistant\" }),\n reasoning_content: slice,\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Role chunk — preserved exactly as the legacy path.\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: { role: overrides?.role ?? \"assistant\", content: \"\" },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Tool-call `index` is assigned in encounter order across the block array.\n let tcIdx = 0;\n for (const block of ordered) {\n if (block.type === \"text\") {\n for (let i = 0; i < block.text.length; i += chunkSize) {\n const slice = block.text.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [{ index: 0, delta: { content: slice }, logprobs: null, finish_reason: null }],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n } else {\n const tcId = block.id || generateToolCallId();\n\n // Initial tool call chunk (id + function name)\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: tcIdx,\n id: tcId,\n type: \"function\",\n function: { name: block.name, arguments: \"\" },\n },\n ],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Argument streaming chunks\n const args = block.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n const slice = args.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [{ index: tcIdx, function: { arguments: slice } }],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n tcIdx++;\n }\n }\n\n // Derive the default terminal from the blocks actually emitted.\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {},\n logprobs: null,\n finish_reason: overrides?.finishReason ?? (outcome.hasToolCalls ? \"tool_calls\" : \"stop\"),\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n return chunks;\n }\n\n // EXISTING legacy code, byte-for-byte UNCHANGED.\n // Reasoning chunks (emitted before content, OpenRouter format)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n ...(i === 0 && { role: overrides?.role ?? \"assistant\" }),\n reasoning_content: slice,\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Role chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: { role: overrides?.role ?? \"assistant\", content: \"\" },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Content chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [{ index: 0, delta: { content: slice }, logprobs: null, finish_reason: null }],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n\n // Tool call chunks — one initial chunk per tool call, then argument chunks\n for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {\n const tc = toolCalls[tcIdx];\n const tcId = tc.id || generateToolCallId();\n\n // Initial tool call chunk (id + function name)\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: tcIdx,\n id: tcId,\n type: \"function\",\n function: { name: tc.name, arguments: \"\" },\n },\n ],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n // Argument streaming chunks\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n const slice = args.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [{ index: tcIdx, function: { arguments: slice } }],\n },\n logprobs: null,\n finish_reason: null,\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n }\n }\n\n // Finish chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model: effectiveModel,\n choices: [\n {\n index: 0,\n delta: {},\n logprobs: null,\n finish_reason: overrides?.finishReason ?? \"tool_calls\",\n },\n ],\n ...(fingerprint !== undefined && { system_fingerprint: fingerprint }),\n });\n\n return chunks;\n}\n\n// NOTE (#274): this NON-streaming OpenAI chat-completions builder is\n// intentionally degenerate w.r.t. `blocks` ordering. A chat.completion puts\n// `message.content` and `message.tool_calls` in SEPARATE fields on a single\n// message object — they are NOT a positionally-observable array, so a\n// tool-first `blocks` fixture cannot be expressed in the wire shape. Honoring\n// block order here would be a no-op, so the legacy content+tool_calls fields\n// are unchanged. (Order-observable surfaces — Claude `content[]`, Gemini\n// `parts[]`, Responses `output[]` — DO honor block order; see those builders.)\nexport function buildContentWithToolCallsCompletion(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n requestMessages?: ChatCompletionRequest[\"messages\"],\n): ChatCompletion {\n const promptText = requestMessages\n ? requestMessages\n .map((m) =>\n typeof m.content === \"string\"\n ? m.content\n : Array.isArray(m.content)\n ? m.content.map((p) => p.text ?? \"\").join(\"\")\n : \"\",\n )\n .join(\"\")\n : \"\";\n const completionText = content + toolCalls.map((tc) => tc.name + tc.arguments).join(\"\");\n return {\n id: overrides?.id ?? generateId(),\n object: \"chat.completion\",\n created: overrides?.created ?? Math.floor(Date.now() / 1000),\n model: overrides?.model ?? model,\n choices: [\n {\n index: 0,\n message: {\n role: overrides?.role ?? \"assistant\",\n content,\n refusal: null,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n tool_calls: toolCalls.map((tc) => ({\n id: tc.id || generateToolCallId(),\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n })),\n },\n logprobs: null,\n finish_reason: overrides?.finishReason ?? \"tool_calls\",\n },\n ],\n usage: resolveUsage(overrides, promptText, completionText),\n ...(overrides?.systemFingerprint !== undefined && {\n system_fingerprint: overrides.systemFingerprint,\n }),\n };\n}\n\n// ─── HTTP helpers ─────────────────────────────────────────────────────────\n\nconst DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB\n\n/**\n * A body read that stopped because the CLIENT sent more bytes than the route\n * allows. The socket is already destroyed when this rejects, so no status line\n * ever reaches the caller — but the fault is the caller's, and error arms that\n * journal and log the failure need to say so. Carrying the classification on\n * the error type (rather than re-matching the message text) is what lets them:\n * the message is unchanged from the plain `Error` this replaced.\n */\nexport class RequestBodyTooLargeError extends Error {\n constructor(readonly maxBytes: number) {\n super(`Request body exceeded size limit of ${maxBytes} bytes`);\n this.name = \"RequestBodyTooLargeError\";\n }\n}\n\n/**\n * Read a request body as raw bytes, preserving every octet.\n *\n * This is the byte-level primitive {@link readBody} is built on: routes that\n * must not lose bytes (binary file uploads) take the Buffer, everything else\n * keeps taking the decoded string. Splitting it this way leaves the text path\n * byte-identical — `readBody` performs exactly the same `Buffer.concat(...)`\n * + default (utf8) `toString()` it always did, just one call later.\n */\nexport function readBodyBuffer(\n req: http.IncomingMessage,\n maxBytes: number = DEFAULT_MAX_BODY_BYTES,\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n let settled = false;\n req.on(\"data\", (chunk: Buffer) => {\n if (settled) return;\n totalBytes += chunk.length;\n if (totalBytes > maxBytes) {\n settled = true;\n req.destroy();\n reject(new RequestBodyTooLargeError(maxBytes));\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => {\n if (!settled) {\n settled = true;\n resolve(Buffer.concat(chunks));\n }\n });\n req.on(\"error\", (err) => {\n if (!settled) {\n settled = true;\n reject(err);\n }\n });\n });\n}\n\n/**\n * A body read with a buffering bound that does NOT cost the caller its socket.\n *\n * `buffer` is `null` when the body went over the buffering bound: the bytes\n * read so far were released and the rest was drained, so the request still\n * reaches `end` and the route can answer a real status (with CORS) instead of\n * the client seeing an `ECONNRESET`.\n */\nexport interface BoundedBody {\n buffer: Buffer | null;\n /** Bytes seen on the wire, counted past the buffering bound. */\n bytesRead: number;\n}\n\n/**\n * Read a request body that may legitimately be over-size, without either\n * buffering it all or dropping the socket.\n *\n * {@link readBodyBuffer} answers an over-size body with `req.destroy()`: no\n * status line, no body, no CORS headers. That is the right default for routes\n * whose limit is a pure DoS bound, but it is the wrong answer for a route that\n * owes the caller a `400` describing what was wrong — the caller cannot read an\n * error it never receives. So this variant splits the two concerns:\n *\n * - `maxBytes` bounds MEMORY. Past it the buffered chunks are dropped and the\n * body is only counted, so peak retention never exceeds `maxBytes` no matter\n * how large the body is. The caller gets `buffer: null` and answers itself.\n * - `drainMaxBytes` bounds WORK, and is the only socket drop here: a body that\n * keeps coming past it is not worth draining and rejects as `readBodyBuffer`\n * would.\n */\nexport function readBodyBufferBounded(\n req: http.IncomingMessage,\n maxBytes: number,\n drainMaxBytes: number,\n): Promise<BoundedBody> {\n return new Promise((resolve, reject) => {\n let chunks: Buffer[] | null = [];\n let totalBytes = 0;\n let settled = false;\n req.on(\"data\", (chunk: Buffer) => {\n if (settled) return;\n totalBytes += chunk.length;\n if (chunks !== null && totalBytes > maxBytes) {\n // Release on the FIRST chunk over the bound, before pushing it: the\n // bytes are unusable now, and holding them is what would make peak\n // memory track the attacker's body size instead of `maxBytes`.\n chunks = null;\n }\n if (totalBytes > drainMaxBytes) {\n settled = true;\n req.destroy();\n reject(new RequestBodyTooLargeError(drainMaxBytes));\n return;\n }\n if (chunks !== null) chunks.push(chunk);\n });\n req.on(\"end\", () => {\n if (settled) return;\n settled = true;\n resolve({ buffer: chunks === null ? null : Buffer.concat(chunks), bytesRead: totalBytes });\n });\n req.on(\"error\", (err) => {\n if (!settled) {\n settled = true;\n reject(err);\n }\n });\n });\n}\n\nexport function readBody(\n req: http.IncomingMessage,\n maxBytes: number = DEFAULT_MAX_BODY_BYTES,\n): Promise<string> {\n return readBodyBuffer(req, maxBytes).then((buf) => buf.toString());\n}\n\n// ─── Pattern matching ─────────────────────────────────────────────────────\n\n/**\n * Case-insensitive substring/regex match used for search, rerank, and\n * moderation endpoints where exact casing rarely matters. String patterns\n * are lowercased on both sides before comparison.\n *\n * Note: This intentionally differs from the case-sensitive matching in\n * {@link matchFixture} (router.ts), where fixture authors expect exact\n * string matching against chat completion user messages.\n */\nexport function matchesPattern(text: string, pattern: string | RegExp): boolean {\n if (typeof pattern === \"string\") {\n return text.toLowerCase().includes(pattern.toLowerCase());\n }\n // A global/sticky RegExp carries mutable `lastIndex` state that `.test()`\n // advances. Save and restore it so callers reusing the same regex object\n // (search/rerank/moderation filter loops) are not left with mutated state\n // and get consistent results across repeated calls.\n const savedLastIndex = pattern.lastIndex;\n pattern.lastIndex = 0;\n const result = pattern.test(text);\n pattern.lastIndex = savedLastIndex;\n return result;\n}\n\n/**\n * Which test LLM and other HTTP-route traffic belongs to: the `X-Test-Id`\n * header (raw, not percent-decoded) wins, then `?testId=` in the query string\n * (read through `URLSearchParams`, so percent-decoded and with `+` as a\n * space), then `DEFAULT_TEST_ID`. Every per-test axis of those routes\n * (fixture match-counts, the journal filter, chaos scoping) resolves through\n * this. MCP mounts resolve the test id with {@link resolveMcpIdentity}\n * instead, which percent-decodes the header, so `X-Test-Id: a%20b` is `a%20b`\n * here and `a b` there. Both read a `+` in the query as a space.\n */\nexport function resolveTestId(headers: IncomingHttpHeaders, url: string | undefined): string {\n const headerValue = headers[\"x-test-id\"];\n if (Array.isArray(headerValue)) {\n if (headerValue.length > 0 && headerValue[0]) return headerValue[0];\n } else if (typeof headerValue === \"string\" && headerValue) {\n return headerValue;\n }\n\n const qIdx = (url ?? \"/\").indexOf(\"?\");\n if (qIdx !== -1) {\n const params = new URLSearchParams((url ?? \"/\").slice(qIdx + 1));\n const queryValue = params.get(\"testId\");\n if (queryValue) return queryValue;\n }\n\n return DEFAULT_TEST_ID;\n}\n\nexport function getTestId(req: http.IncomingMessage): string {\n return resolveTestId(req.headers, req.url);\n}\n\nexport function getContext(req: http.IncomingMessage): string | undefined {\n const headerValue = req.headers[\"x-aimock-context\"];\n if (Array.isArray(headerValue)) {\n if (headerValue.length > 0 && headerValue[0]) return headerValue[0];\n } else if (typeof headerValue === \"string\" && headerValue) {\n return headerValue;\n }\n return undefined;\n}\n\n/**\n * Decode one MCP identity value: an `X-Test-Id` / `X-AIMock-Context` header\n * value, and also each query name and (through {@link decodeMcpQueryValue})\n * each `?testId=` / `?context=` / `?undeclared=` query value. Clients send `encodeURIComponent(id)` because\n * `fetch` rejects characters such as `›` in a header. A value that is not\n * valid percent-encoding (a raw `applies 50% discount`) is used as-is, with\n * `fellBack: true` so the caller can log the fallback. Only that `URIError`\n * is caught; any other throw propagates. MCP requests only: LLM routes read\n * the header raw and the query through `URLSearchParams` (see\n * {@link resolveTestId}).\n */\nexport function decodeMcpHeaderValue(raw: string): { value: string; fellBack: boolean } {\n try {\n return { value: decodeURIComponent(raw), fellBack: false };\n } catch (err) {\n if (!(err instanceof URIError)) throw err;\n return { value: raw, fellBack: true };\n }\n}\n\n/**\n * Decode one MCP query value (`?testId=`, `?context=`, `?undeclared=`) the way\n * `URLSearchParams` reads it (spec I1): a `+` is a space, then the value is\n * percent-decoded by the {@link decodeMcpHeaderValue} rule. So a query built\n * with `URL.searchParams.set(...)` (a space sent as `+`) and one built with\n * `encodeURIComponent` both resolve to the id itself. A value that is not\n * valid percent-encoding is used as-is (its `+` still a space), with\n * `fellBack: true`.\n */\nfunction decodeMcpQueryValue(raw: string): { value: string; fellBack: boolean } {\n return decodeMcpHeaderValue(raw.replace(/\\+/g, \" \"));\n}\n\n/** The parts of a request that {@link resolveMcpIdentity} reads. */\nexport type McpIdentityRequest = Pick<http.IncomingMessage, \"headers\" | \"url\"> &\n Partial<Pick<http.IncomingMessage, \"headersDistinct\">>;\n\n/**\n * Every value of the header `name`, one per header line: `[]` when absent.\n * Node joins a repeated header into one comma-joined `req.headers` string, so\n * the values come from `req.headersDistinct` (one element per line) when the\n * request has it. A caller without it (a hand-built request) is read from\n * `req.headers`: one element for a string, each element for an array.\n */\nfunction headerValues(req: McpIdentityRequest, name: string): string[] {\n const distinct = req.headersDistinct?.[name];\n if (distinct !== undefined) return distinct;\n const value = req.headers[name];\n if (value === undefined) return [];\n return Array.isArray(value) ? value : [value];\n}\n\n/** One `name=value` pair of a query string, its name decoded. */\ninterface McpQueryPair {\n /** The decoded name, or `null` when the name is not valid percent-encoding. */\n name: string | null;\n /** The raw (still percent-encoded) value; `\"\"` for a bare name with no `=`. */\n value: string;\n}\n\n/**\n * Every pair of a query string, in order, each name decoded by the\n * `decodeMcpHeaderValue` rule. A bare `name` with no `=` counts as an\n * occurrence with an empty value, so `?testId&testId=t1` has two. Each value\n * is left raw for the caller to decode with {@link decodeMcpQueryValue}.\n * A name that is not valid percent-encoding (`test%Id`) gets `name: null`: it\n * matches no field, and the caller counts it.\n */\nfunction queryPairs(query: string): McpQueryPair[] {\n if (query === \"\") return [];\n return query.split(\"&\").map((pair) => {\n const eq = pair.indexOf(\"=\");\n const key = decodeMcpHeaderValue(eq === -1 ? pair : pair.slice(0, eq));\n return { name: key.fellBack ? null : key.value, value: eq === -1 ? \"\" : pair.slice(eq + 1) };\n });\n}\n\n/**\n * A request value as a message part, the way the message builder quotes every\n * user string: JSON-quoted (quotes and control characters escaped), then cut\n * once as JSON text, so the whole echo, quotes, escapes and the\n * `… (<n> more chars)` count included, fits in `MCP_FAKES_ECHO_LIMIT` and the\n * cut never splits a JSON escape or a surrogate pair.\n */\nfunction echoIdentityValue(value: string): MessageValue {\n return quote(value, MCP_FAKES_ECHO_LIMIT);\n}\n\n/** The session fields an MCP session binds at `initialize`. */\ntype McpIdentitySession = Pick<MCPSession, \"testId\" | \"context\" | \"undeclared\">;\n\n/** An MCP identity input: the test id, the context or the undeclared override. */\nexport type McpIdentityField = \"testId\" | \"context\" | \"undeclared\";\n\n/**\n * Why an MCP request's identity inputs cannot be honored. The MCP mount is\n * meant to answer `status` with `{ \"error\": message }` and serve nothing.\n * - `MCP_INVALID_UNDECLARED`: an undeclared override (`X-AIMock-MCP-Undeclared`\n * / `?undeclared=`) other than `allow` / `deny`, so a typo such as `denied`\n * can never turn a `deny` scenario into the permissive default. `raw` is\n * the value as sent (the query value still percent-encoded), not quoted,\n * cut by `cutText` to `MCP_FAKES_ECHO_LIMIT` chars, so a huge value is\n * never carried in full. A query value that\n * is not valid percent-encoding is also an error, and the message says so.\n * - `MCP_DUPLICATE_IDENTITY`: one field sent more than once on one path (a\n * header array with more than one element, or a repeated query name, empty\n * or not). No value is picked. `count` is how many were sent.\n */\nexport type McpIdentityError =\n | {\n code: \"MCP_INVALID_UNDECLARED\";\n status: 400;\n field: \"undeclared\";\n source: \"header\" | \"query\";\n raw: string;\n message: string;\n }\n | {\n code: \"MCP_DUPLICATE_IDENTITY\";\n status: 400;\n field: McpIdentityField;\n source: \"header\" | \"query\";\n count: number;\n message: string;\n };\n\n/** The result of {@link resolveMcpIdentity}: a resolved identity, or an error to answer with. */\nexport type McpIdentityResolution =\n | {\n ok: true;\n identity: McpFakeIdentity;\n /** Whether the request itself carried each field (raw header/query, not the session). */\n supplied: { testId: boolean; context: boolean; undeclared: boolean };\n /**\n * Whether a header or query value sent for each field was not valid\n * percent-encoding and fell back to the raw value. Set also when the\n * header supplied the field and the ignored query value did not decode,\n * so bad encoding is never silent. For the MCP mount to log a warning,\n * once per session per field.\n */\n fellBack: { testId: boolean; context: boolean };\n /**\n * How many query names were not valid percent-encoding (`?test%Id=`).\n * Such a name matches no field. Present only when at least one was\n * found, for the MCP mount to log.\n */\n undecodedQueryNames?: number;\n }\n | { ok: false; error: McpIdentityError };\n\nconst MCP_IDENTITY_INPUTS: Record<\n McpIdentityField,\n { header: string; query: string; label: string }\n> = {\n testId: { header: \"x-test-id\", query: \"testId\", label: \"X-Test-Id\" },\n context: { header: \"x-aimock-context\", query: \"context\", label: \"X-AIMock-Context\" },\n undeclared: {\n header: \"x-aimock-mcp-undeclared\",\n query: \"undeclared\",\n label: \"X-AIMock-MCP-Undeclared\",\n },\n};\n\n/**\n * Resolve the identity (test id, context, undeclared override) of one MCP request.\n * Each field resolves on its own, first of: the header, the query parameter,\n * the session value bound at `initialize`. A field sent more than once on\n * either path (more than one header line, read from `headersDistinct` because\n * Node joins repeated lines into one `headers` string; a header array from a\n * hand-built request; a repeated query name) is an error\n * (`MCP_DUPLICATE_IDENTITY`), even when the other path also carries it. For\n * the test id and the context, a header value is decoded with\n * `decodeURIComponent` (raw value on failure, with `fellBack` set) and a query\n * value the way `URLSearchParams` reads it (spec I1): a `+` is a space, then\n * the same `decodeURIComponent` rule. So `encodeURIComponent` output (what\n * clients send) and a query built with `URL.searchParams` both decode to the\n * id itself, while a raw `+` is a `+` in the header and a space in the query.\n * When both paths carry a test id or a context, the header is used and the\n * query value is ignored, even if the two differ, but an ignored query value\n * that does not decode still sets `fellBack`. The undeclared header is not\n * percent-decoded; its query value is, by the same query rule. Both are\n * trimmed and compared case-insensitively. `supplied` says whether the\n * request itself carried a non-empty header or query value for the field,\n * never `resolveTestId`'s result (which returns `\"__default__\"` when nothing\n * is sent). Blank values: an empty value is not supplied and falls through;\n * it is never rejected. A whitespace-only value is not supplied for the\n * undeclared override (it is trimmed), but is a real value for the test id\n * and the context, which match exactly and are never trimmed (Node trims a\n * header value, so only the query or a hand-built request can carry one). A\n * lone query name with no `=` carries no value. An empty session value counts\n * as none, and a missing `req.url` as `/`. A field with no value anywhere is\n * `null`. An unrecognized undeclared override on the header or the query is\n * an error (`ok: false`), not a fall-through: both are checked before the\n * header one is used, so a valid header does not hide a mistyped query value.\n * Bad percent-encoding is never silent: a test id or context value that does\n * not decode sets `fellBack`, on either path, used or ignored; an undeclared\n * query value that does not decode is that error, with the message saying\n * so; and a query name that does not decode matches no field and is counted\n * in `undecodedQueryNames`. `resolveTestId` / `getContext` stay unchanged for\n * LLM routes.\n */\nexport function resolveMcpIdentity(\n req: McpIdentityRequest,\n session?: McpIdentitySession,\n): McpIdentityResolution {\n const url = req.url ?? \"/\";\n const qIdx = url.indexOf(\"?\");\n const pairs = queryPairs(qIdx === -1 ? \"\" : url.slice(qIdx + 1));\n\n // Collect each field's one raw value per path, rejecting any repetition first.\n const raw = {} as Record<McpIdentityField, { header?: string; query?: string }>;\n for (const field of [\"testId\", \"context\", \"undeclared\"] as const) {\n const input = MCP_IDENTITY_INPUTS[field];\n const sent = [\n [\"header\", headerValues(req, input.header)],\n [\"query\", pairs.filter((p) => p.name === input.query).map((p) => p.value)],\n ] as const;\n for (const [source, values] of sent) {\n if (values.length > 1) {\n const label =\n source === \"header\"\n ? msg`${fixed(input.label)} header`\n : msg`?${fixed(input.query)}= query parameter`;\n return {\n ok: false,\n error: {\n code: \"MCP_DUPLICATE_IDENTITY\",\n status: 400,\n field,\n source,\n count: values.length,\n message: build(msg`Duplicate ${label}: ${values.length} values sent, expected one`),\n },\n };\n }\n }\n raw[field] = { header: sent[0][1][0], query: sent[1][1][0] };\n }\n\n const fromRequest = (\n field: \"testId\" | \"context\",\n ): { value: string | null; fellBack: boolean } => {\n const { header, query: q } = raw[field];\n const fromQuery = q ? decodeMcpQueryValue(q) : null;\n if (header) {\n // The header wins; an ignored query value that does not decode still flags.\n const used = decodeMcpHeaderValue(header);\n return { value: used.value, fellBack: used.fellBack || (fromQuery?.fellBack ?? false) };\n }\n return fromQuery ?? { value: null, fellBack: false };\n };\n\n const testId = fromRequest(\"testId\");\n const context = fromRequest(\"context\");\n\n // Check every supplied undeclared value before using one: the header wins,\n // but a mistyped query value next to it is still an error.\n let undeclared: McpFakeUndeclaredPolicy | null = null;\n for (const source of [\"header\", \"query\"] as const) {\n const sent = raw.undeclared[source];\n if (sent === undefined) continue;\n // Header decoding covers only X-Test-Id / X-AIMock-Context; this header is\n // not percent-decoded. The query value is decoded.\n const decoded =\n source === \"header\" ? { value: sent, fellBack: false } : decodeMcpQueryValue(sent);\n if (decoded.value.trim() === \"\") continue;\n const parsed = parseMcpUndeclared(decoded.value);\n if (parsed === null) {\n const echoed = echoIdentityValue(sent);\n const label =\n source === \"header\"\n ? msg`X-AIMock-MCP-Undeclared header`\n : msg`?undeclared= query parameter`;\n const note = decoded.fellBack ? msg` (not valid percent-encoding)` : msg``;\n return {\n ok: false,\n error: {\n code: \"MCP_INVALID_UNDECLARED\",\n status: 400,\n field: \"undeclared\",\n source,\n raw: cutText(sent),\n message: build(msg`Invalid ${label} value ${echoed}${note}: expected allow or deny`),\n },\n };\n }\n undeclared ??= parsed;\n }\n\n const undecodedQueryNames = pairs.filter((p) => p.name === null).length;\n return {\n ok: true,\n identity: {\n testId: testId.value ?? (session?.testId || null),\n context: context.value ?? (session?.context || null),\n undeclared: undeclared ?? session?.undeclared ?? null,\n },\n supplied: {\n testId: testId.value !== null,\n context: context.value !== null,\n undeclared: undeclared !== null,\n },\n fellBack: { testId: testId.fellBack, context: context.fellBack },\n ...(undecodedQueryNames > 0 ? { undecodedQueryNames } : {}),\n };\n}\n\n// ─── Snapshot recording helpers ──────────────────────────────────────────────\n\n/**\n * Convert a test ID (e.g. Playwright titlePath) into a filesystem-safe slug\n * suitable for use as a directory name in snapshot-style recording.\n */\nexport function slugifyTestId(testId: string): string {\n return testId\n .replace(/^.*?\\.(?:spec|test|e2e)\\.(?:tsx|ts|jsx|js|mjs|cjs)(?=\\s|›|$)\\s*›?\\s*/i, \"\") // strip test file extension prefix\n .replace(/\\s*[›>]\\s*/g, \"--\") // Playwright titlePath separator → double dash\n .replace(/[^\\w-]/g, \"-\") // non-word chars → dash\n .replace(/-{3,}/g, \"--\") // collapse 3+ dashes to double\n .replace(/^-+|-+$/g, \"\") // trim leading/trailing dashes\n .toLowerCase();\n}\n\n/**\n * Make a request context (the `X-AIMock-Context` header value) safe to use as\n * a single directory segment in a recorded-fixture path. The header is\n * attacker-controllable, so a raw value containing `../`, path separators, or\n * an absolute-path prefix would let the written fixture escape the configured\n * fixtures base directory. Mirrors `slugifyTestId`: non-word characters\n * (including `/`, `\\`, and `.`) collapse to dashes, so the result is always a\n * single flat segment with no traversal semantics. Returns \"\" when the value\n * sanitizes to nothing (caller treats that as \"no context segment\").\n */\nexport function slugifyContext(context: string): string {\n return context\n .replace(/[^\\w-]/g, \"-\") // non-word chars (incl. / \\ . :) → dash\n .replace(/-{3,}/g, \"--\") // collapse 3+ dashes to double\n .replace(/^-+|-+$/g, \"\") // trim leading/trailing dashes\n .toLowerCase();\n}\n\n// ─── Request shape validation ──────────────────────────────────────────────\n\n/**\n * Validate the shape of a chat `messages` array (Cohere / Ollama / Bedrock\n * style: entries with `content` and optional `tool_calls`).\n *\n * Returns an error detail string when the shape is wrong, or null when the\n * provider converter can safely consume it. Wrong types here used to throw\n * inside the converters (`content.filter` on a number, `.map` on a string\n * `tools`, `msg.role` on a null entry) and surface as 500s. `null` content\n * is accepted — assistant messages legitimately carry `content: null`\n * alongside tool calls, and the converters coerce it to \"\".\n *\n * `checkToolCalls` must be false for providers whose converter never reads\n * `msg.tool_calls` (Bedrock: tool calls are `tool_use` content blocks and\n * `tool_calls` is not a field of the Anthropic Messages body at all). There a\n * stray `tool_calls` is inert — rejecting it turns a request that used to\n * match a fixture and return 200 into a 400.\n */\nexport function validateChatMessages(\n messages: unknown,\n { checkToolCalls = true }: { checkToolCalls?: boolean } = {},\n): string | null {\n // Matches the historic \"messages array is required\" detail so handlers can\n // compose the exact legacy message for a missing/non-array field.\n if (!Array.isArray(messages)) return \"messages array is required\";\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i] as Record<string, unknown> | null;\n if (msg === null || typeof msg !== \"object\" || Array.isArray(msg)) {\n return `messages[${i}] must be an object`;\n }\n const content = msg.content;\n if (\n content !== undefined &&\n content !== null &&\n typeof content !== \"string\" &&\n !Array.isArray(content)\n ) {\n return `messages[${i}].content must be a string or an array`;\n }\n if (Array.isArray(content) && content.some((p) => p === null || p === undefined)) {\n return `messages[${i}].content must be a string or an array`;\n }\n const toolCalls = msg.tool_calls;\n if (checkToolCalls && toolCalls !== undefined && toolCalls !== null) {\n if (!Array.isArray(toolCalls)) {\n return `messages[${i}].tool_calls must be an array`;\n }\n for (const tc of toolCalls) {\n if (tc === null || typeof tc !== \"object\" || Array.isArray(tc)) {\n return `messages[${i}].tool_calls entries must be objects`;\n }\n const fn = (tc as Record<string, unknown>).function;\n if (fn === null || typeof fn !== \"object\" || Array.isArray(fn)) {\n return `messages[${i}].tool_calls entries must have a function object`;\n }\n }\n }\n }\n return null;\n}\n\n/**\n * Validate an optional top-level `tools` field. A string (or other\n * non-array) value passes the `req.tools && req.tools.length` gate\n * (`\"hi\".length > 0`) and then throws on `.map` — a 500 instead of a 400.\n * Returns an error detail string, or null when absent or a valid array.\n */\nexport function validateToolsField(tools: unknown): string | null {\n if (tools !== undefined && tools !== null && !Array.isArray(tools)) {\n return \"tools must be an array\";\n }\n if (Array.isArray(tools)) {\n for (let i = 0; i < tools.length; i++) {\n const t = tools[i] as Record<string, unknown> | null;\n if (t === null || typeof t !== \"object\" || Array.isArray(t)) {\n return `tools[${i}] must be an object`;\n }\n }\n }\n return null;\n}\n\n// ─── Request body helpers ──────────────────────────────────────────────────\n\n/**\n * True when a parsed JSON request body is a plain object.\n *\n * `JSON.parse(\"null\")` yields `null`, and `\"123\"` / `\"\\\"hi\\\"\"` yield\n * non-objects — every handler that then reads `body.field` throws a TypeError\n * that surfaces as a 500. Call this right after `JSON.parse` and answer 400\n * (\"Request body must be a JSON object\") instead. Arrays are rejected too:\n * `[].field` reads as `undefined` today, which degrades into a misleading\n * \"missing parameter\" 400 — rejecting up front names the real problem.\n */\nexport function isJsonObject(body: unknown): body is Record<string, unknown> {\n return body !== null && typeof body === \"object\" && !Array.isArray(body);\n}\n\n// ─── Embedding helpers ─────────────────────────────────────────────────────\n\nconst DEFAULT_EMBEDDING_DIMENSIONS = 1536;\n\n/**\n * Maximum embedding dimensions accepted by POST /v1/embeddings.\n *\n * A serialization budget, not an allocation bound. The ECMAScript array-length\n * bound (2**32 - 1) is where `new Array(n)` throws RangeError, but the handler\n * does not stop at allocating: `generateDeterministicEmbedding` fills the array\n * and the response is JSON-serialized, so at that bound a single request aborts\n * the process (\"FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript\n * heap out of memory\") before any response is written — taking every other test\n * sharing the server with it. A cap the server cannot serve is not a cap.\n *\n * Derivation, measured on this tree: a response body costs 19.58 bytes per\n * dimension (4096 -> 80,456 B; 100,000 -> 1,958,539 B; 1,000,000 ->\n * 19,583,034 B). At 100,000 the body is ~1.96 MB, built in 7 ms at 83 MB RSS\n * under a 1 GB heap — comfortably serviceable.\n *\n * Deliberately still not a model width: /v1/embeddings also serves Azure and\n * every OpenAI-compatible server routed through COMPAT_SUFFIXES, where\n * 4096-dimension models are ordinary. 100,000 leaves >12x headroom over the\n * widest width in circulation while keeping every accepted request answerable.\n */\nexport const MAX_EMBEDDING_DIMENSIONS = 100_000;\n\n/**\n * Validate an embeddings `dimensions` parameter.\n * Returns the effective dimensions (default 1536) or null when invalid.\n * Valid: undefined/null (→ default), integer in [1, MAX_EMBEDDING_DIMENSIONS].\n */\nexport function validateEmbeddingDimensions(raw: unknown): number | null {\n // null means \"unset\" too — the previous code was `dimensions ?? 1536`.\n if (raw === undefined || raw === null) return 1536;\n if (typeof raw !== \"number\" || !Number.isInteger(raw)) return null;\n if (raw < 1 || raw > MAX_EMBEDDING_DIMENSIONS) return null;\n return raw;\n}\n\n/**\n * Normalize an embeddings `input` into the texts to embed.\n * Accepts every shape EmbeddingCreateParams.input declares\n * (openai/resources/embeddings.d.ts): `string | string[] | number[] | number[][]`,\n * the last two being pre-tokenized input. Returns null for shapes the API\n * rejects (non-string scalars, objects, mixed arrays).\n */\nexport function normalizeEmbeddingInput(raw: unknown): string[] | null {\n if (typeof raw === \"string\") return [raw];\n if (Array.isArray(raw)) {\n if (raw.every((el) => typeof el === \"string\")) return raw as string[];\n // number[] — one pre-tokenized input.\n if (raw.every((el) => typeof el === \"number\")) return [raw.join(\" \")];\n // number[][] — a batch of pre-tokenized inputs.\n if (raw.every((el) => Array.isArray(el) && el.every((t) => typeof t === \"number\"))) {\n return (raw as number[][]).map((tokens) => tokens.join(\" \"));\n }\n return null;\n }\n return null;\n}\n\n/**\n * Normalize a free-text field (moderation input, search/rerank query).\n * Accepts a string, or any array — joined with \" \" as before, so no array that\n * used to return 200 starts failing, and ModerationCreateParams' multimodal\n * parts (openai/resources/moderations.d.ts) contribute their `.text`. Returns\n * null only for non-string, non-array values, which used to 500.\n */\nexport function normalizeTextInput(raw: unknown): string | null {\n if (typeof raw === \"string\") return raw;\n if (Array.isArray(raw)) {\n return raw\n .map((el) => {\n if (typeof el === \"string\") return el;\n if (el === null || el === undefined) return \"\";\n if (typeof el === \"object\") {\n const part = el as { type?: unknown; text?: unknown };\n return part.type === \"text\" && typeof part.text === \"string\" ? part.text : \"\";\n }\n return String(el);\n })\n .join(\" \");\n }\n return null;\n}\n\n/**\n * Generate a deterministic embedding vector from input text.\n * Hashes the input with SHA-256 and spreads the hash bytes across\n * the requested number of dimensions, producing values in [-1, 1].\n */\nexport function generateDeterministicEmbedding(\n input: string,\n dimensions: number = DEFAULT_EMBEDDING_DIMENSIONS,\n): number[] {\n let currentHash = createHash(\"sha256\").update(input).digest();\n const embedding: number[] = new Array(dimensions);\n for (let i = 0; i < dimensions; i++) {\n if (i > 0 && i % 32 === 0) {\n currentHash = createHash(\"sha256\").update(currentHash).digest();\n }\n // Map 0-255 → -1.0 to 1.0\n embedding[i] = currentHash[i % 32] / 127.5 - 1;\n }\n return embedding;\n}\n\nexport interface EmbeddingAPIResponse {\n object: \"list\";\n data: { object: \"embedding\"; index: number; embedding: number[] }[];\n model: string;\n usage: { prompt_tokens: number; total_tokens: number };\n}\n\n/**\n * Build an OpenAI-format embeddings API response for one or more inputs.\n */\nexport function buildEmbeddingResponse(\n embeddings: number[][],\n model: string,\n usage?: { prompt_tokens?: number; total_tokens?: number },\n): EmbeddingAPIResponse {\n return {\n object: \"list\",\n data: embeddings.map((embedding, index) => ({\n object: \"embedding\" as const,\n index,\n embedding,\n })),\n model,\n usage: { prompt_tokens: usage?.prompt_tokens ?? 0, total_tokens: usage?.total_tokens ?? 0 },\n };\n}\n\n/**\n * Build a stable, human-readable identifier for a fixture's match shape, for\n * any log line that has to name WHICH fixture it means. The `Fixture` type\n * carries no `id`/`name`, so the matchers are the only handle a reader has.\n *\n * Used by the relaxed-turnIndex warning (`router.ts`) and by the chaos\n * rejected-value warning (`chaos.ts`); one implementation so the two name the\n * same fixture the same way. The obvious `JSON.stringify(match)` is unfit: it\n * DROPS `predicate` functions (non-serialisable) and serialises any RegExp\n * matcher to `{}`, so a predicate- or regex-gated fixture's warning collapsed to\n * an uninformative \"served fixture {}\" / `{\"userMessage\":{}}` blob.\n *\n * Instead we list the PRESENT matcher keys in declaration order, annotating each\n * by VALUE KIND so predicates and regexes survive: `predicate(fn)`,\n * `userMessage(regex)`, `userMessage(\"hello\")`, `turnIndex=0`, etc. The\n * fixture's array `index` is prefixed as a stable positional identifier when\n * the caller knows it (i.e. `>= 0`); callers holding only the fixture object\n * pass `-1` and get the matcher summary alone. String/number values are shown\n * inline (truncated) so a content match remains recognisable; the whole string\n * is capped to keep the log line bounded.\n */\nconst DESCRIBE_MATCH_MAX = 160;\n\nexport function describeMatch(match: FixtureMatch, index: number): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(match)) {\n if (value === undefined) continue;\n if (typeof value === \"function\") {\n parts.push(key === \"predicate\" ? \"[predicate]\" : `${key}[fn]`);\n } else if (value instanceof RegExp) {\n parts.push(`${key}(${value})`);\n } else if (typeof value === \"string\") {\n const v = value.length > 40 ? `${value.slice(0, 40)}…` : value;\n parts.push(`${key}(${JSON.stringify(v)})`);\n } else if (Array.isArray(value)) {\n parts.push(`${key}(${value.length} item${value.length === 1 ? \"\" : \"s\"})`);\n } else if (typeof value === \"object\" && value !== null) {\n // `String(obj)` is \"[object Object]\"; show the (bounded) JSON instead.\n const json = JSON.stringify(value);\n parts.push(`${key}(${json.length > 40 ? `${json.slice(0, 40)}…` : json})`);\n } else {\n parts.push(`${key}=${String(value)}`);\n }\n }\n const keys = parts.length > 0 ? parts.join(\", \") : \"no matchers\";\n const prefix = index >= 0 ? `#${index} ` : \"\";\n const full = `${prefix}{ ${keys} }`;\n if (full.length <= DESCRIBE_MATCH_MAX) return full;\n // Cut at a matcher boundary (never mid-token) and mark the elision; a lone\n // oversized matcher is hard-cut but still marked.\n const head = full.slice(0, DESCRIBE_MATCH_MAX - 4);\n const cut = head.lastIndexOf(\", \");\n return `${cut > 0 ? head.slice(0, cut) : head}, … }`;\n}\n\n// ─── MCP fakes: undeclared-tool override ────────────────────────────────────\n\n/**\n * Parse an `X-AIMock-MCP-Undeclared` header or `?undeclared=` value.\n * Modeled on {@link resolveStrictMode}: case-insensitive, surrounding\n * whitespace trimmed. Returns `null` when unrecognised; unlike the strict\n * header, {@link resolveMcpIdentity} turns that into an error rather than a\n * fall-through, because falling through would turn a mistyped `deny` into\n * the permissive default.\n */\nfunction parseMcpUndeclared(raw: string): McpFakeUndeclaredPolicy | null {\n const val = raw.trim().toLowerCase();\n if (val === \"allow\" || val === \"deny\") return val;\n return null;\n}\n"],"mappings":"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"}
package/dist/images.cjs CHANGED
@@ -78,7 +78,8 @@ async function handleImages(req, res, raw, fixtures, journal, defaults, setCorsH
78
78
  } }));
79
79
  return;
80
80
  }
81
- if (!prompt) {
81
+ const promptValue = prompt;
82
+ if (!promptValue || typeof promptValue !== "string" && !(Array.isArray(promptValue) && promptValue.length > 0 && promptValue.every((part) => require_helpers.isJsonObject(part) && typeof part.type === "string" && (part.text === void 0 || typeof part.text === "string")))) {
82
83
  journal.add({
83
84
  method,
84
85
  path,
@@ -90,7 +91,7 @@ async function handleImages(req, res, raw, fixtures, journal, defaults, setCorsH
90
91
  }
91
92
  });
92
93
  require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
93
- message: "Missing required parameter: 'prompt'",
94
+ message: !prompt ? "Missing required parameter: 'prompt'" : "Invalid parameter: 'prompt' must be a string or non-empty content-parts array",
94
95
  type: "invalid_request_error"
95
96
  } }));
96
97
  return;