@copilotkit/aimock 1.44.0 → 1.45.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 (272) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +31 -0
  4. package/README.md +6 -0
  5. package/dist/agui-types.d.ts.map +1 -1
  6. package/dist/aimock-cli.cjs +31 -5
  7. package/dist/aimock-cli.cjs.map +1 -1
  8. package/dist/aimock-cli.d.cts.map +1 -1
  9. package/dist/aimock-cli.d.ts.map +1 -1
  10. package/dist/aimock-cli.js +31 -5
  11. package/dist/aimock-cli.js.map +1 -1
  12. package/dist/bedrock-converse.cjs +312 -39
  13. package/dist/bedrock-converse.cjs.map +1 -1
  14. package/dist/bedrock-converse.d.cts.map +1 -1
  15. package/dist/bedrock-converse.d.ts.map +1 -1
  16. package/dist/bedrock-converse.js +313 -40
  17. package/dist/bedrock-converse.js.map +1 -1
  18. package/dist/bedrock.cjs +195 -42
  19. package/dist/bedrock.cjs.map +1 -1
  20. package/dist/bedrock.d.cts.map +1 -1
  21. package/dist/bedrock.d.ts.map +1 -1
  22. package/dist/bedrock.js +196 -43
  23. package/dist/bedrock.js.map +1 -1
  24. package/dist/chaos.cjs +1 -0
  25. package/dist/chaos.js +1 -1
  26. package/dist/cli.cjs +152 -5
  27. package/dist/cli.cjs.map +1 -1
  28. package/dist/cli.js +155 -8
  29. package/dist/cli.js.map +1 -1
  30. package/dist/cohere.cjs +132 -71
  31. package/dist/cohere.cjs.map +1 -1
  32. package/dist/cohere.d.cts.map +1 -1
  33. package/dist/cohere.d.ts.map +1 -1
  34. package/dist/cohere.js +133 -72
  35. package/dist/cohere.js.map +1 -1
  36. package/dist/config-loader.cjs +62 -4
  37. package/dist/config-loader.cjs.map +1 -1
  38. package/dist/config-loader.d.cts +37 -4
  39. package/dist/config-loader.d.cts.map +1 -1
  40. package/dist/config-loader.d.ts +37 -4
  41. package/dist/config-loader.d.ts.map +1 -1
  42. package/dist/config-loader.js +62 -4
  43. package/dist/config-loader.js.map +1 -1
  44. package/dist/fakes-plugin.cjs +131 -0
  45. package/dist/fakes-plugin.cjs.map +1 -0
  46. package/dist/fakes-plugin.d.cts +23 -0
  47. package/dist/fakes-plugin.d.cts.map +1 -0
  48. package/dist/fakes-plugin.d.ts +23 -0
  49. package/dist/fakes-plugin.d.ts.map +1 -0
  50. package/dist/fakes-plugin.js +127 -0
  51. package/dist/fakes-plugin.js.map +1 -0
  52. package/dist/fixture-loader-services.cjs +1 -1
  53. package/dist/fixture-loader-services.cjs.map +1 -1
  54. package/dist/fixture-loader-services.js +1 -1
  55. package/dist/fixture-loader-services.js.map +1 -1
  56. package/dist/fixture-loader.cjs +389 -27
  57. package/dist/fixture-loader.cjs.map +1 -1
  58. package/dist/fixture-loader.d.cts +47 -3
  59. package/dist/fixture-loader.d.cts.map +1 -1
  60. package/dist/fixture-loader.d.ts +47 -3
  61. package/dist/fixture-loader.d.ts.map +1 -1
  62. package/dist/fixture-loader.js +384 -29
  63. package/dist/fixture-loader.js.map +1 -1
  64. package/dist/gemini-interactions.cjs +250 -52
  65. package/dist/gemini-interactions.cjs.map +1 -1
  66. package/dist/gemini-interactions.d.cts.map +1 -1
  67. package/dist/gemini-interactions.d.ts.map +1 -1
  68. package/dist/gemini-interactions.js +251 -53
  69. package/dist/gemini-interactions.js.map +1 -1
  70. package/dist/gemini.cjs +226 -37
  71. package/dist/gemini.cjs.map +1 -1
  72. package/dist/gemini.d.cts.map +1 -1
  73. package/dist/gemini.d.ts.map +1 -1
  74. package/dist/gemini.js +227 -38
  75. package/dist/gemini.js.map +1 -1
  76. package/dist/helpers.cjs +881 -7
  77. package/dist/helpers.cjs.map +1 -1
  78. package/dist/helpers.d.cts +85 -4
  79. package/dist/helpers.d.cts.map +1 -1
  80. package/dist/helpers.d.ts +85 -4
  81. package/dist/helpers.d.ts.map +1 -1
  82. package/dist/helpers.js +839 -8
  83. package/dist/helpers.js.map +1 -1
  84. package/dist/index.cjs +9 -0
  85. package/dist/index.d.cts +7 -5
  86. package/dist/index.d.ts +7 -5
  87. package/dist/index.js +4 -3
  88. package/dist/jest.cjs +31 -7
  89. package/dist/jest.cjs.map +1 -1
  90. package/dist/jest.d.cts +43 -3
  91. package/dist/jest.d.cts.map +1 -1
  92. package/dist/jest.d.ts +43 -3
  93. package/dist/jest.d.ts.map +1 -1
  94. package/dist/jest.js +33 -9
  95. package/dist/jest.js.map +1 -1
  96. package/dist/journal.cjs +135 -13
  97. package/dist/journal.cjs.map +1 -1
  98. package/dist/journal.d.cts +38 -4
  99. package/dist/journal.d.cts.map +1 -1
  100. package/dist/journal.d.ts +38 -4
  101. package/dist/journal.d.ts.map +1 -1
  102. package/dist/journal.js +135 -13
  103. package/dist/journal.js.map +1 -1
  104. package/dist/live-sanitize.cjs +4 -32
  105. package/dist/live-sanitize.cjs.map +1 -1
  106. package/dist/live-sanitize.js +4 -32
  107. package/dist/live-sanitize.js.map +1 -1
  108. package/dist/llmock.cjs +123 -30
  109. package/dist/llmock.cjs.map +1 -1
  110. package/dist/llmock.d.cts +16 -1
  111. package/dist/llmock.d.cts.map +1 -1
  112. package/dist/llmock.d.ts +16 -1
  113. package/dist/llmock.d.ts.map +1 -1
  114. package/dist/llmock.js +125 -32
  115. package/dist/llmock.js.map +1 -1
  116. package/dist/mcp-fakes-mount.cjs +1 -0
  117. package/dist/mcp-fakes-mount.cjs.map +1 -1
  118. package/dist/mcp-fakes-mount.js +1 -0
  119. package/dist/mcp-fakes-mount.js.map +1 -1
  120. package/dist/mcp-fakes-report.cjs +111 -0
  121. package/dist/mcp-fakes-report.cjs.map +1 -0
  122. package/dist/mcp-fakes-report.d.cts +40 -0
  123. package/dist/mcp-fakes-report.d.cts.map +1 -0
  124. package/dist/mcp-fakes-report.d.ts +40 -0
  125. package/dist/mcp-fakes-report.d.ts.map +1 -0
  126. package/dist/mcp-fakes-report.js +107 -0
  127. package/dist/mcp-fakes-report.js.map +1 -0
  128. package/dist/mcp-fakes.cjs +331 -8
  129. package/dist/mcp-fakes.cjs.map +1 -1
  130. package/dist/mcp-fakes.d.cts +406 -3
  131. package/dist/mcp-fakes.d.cts.map +1 -1
  132. package/dist/mcp-fakes.d.ts +406 -3
  133. package/dist/mcp-fakes.d.ts.map +1 -1
  134. package/dist/mcp-fakes.js +331 -8
  135. package/dist/mcp-fakes.js.map +1 -1
  136. package/dist/mcp-handler.cjs +209 -16
  137. package/dist/mcp-handler.cjs.map +1 -1
  138. package/dist/mcp-handler.d.cts +49 -0
  139. package/dist/mcp-handler.d.cts.map +1 -0
  140. package/dist/mcp-handler.d.ts +49 -0
  141. package/dist/mcp-handler.d.ts.map +1 -0
  142. package/dist/mcp-handler.js +207 -17
  143. package/dist/mcp-handler.js.map +1 -1
  144. package/dist/mcp-mock.cjs +112 -1
  145. package/dist/mcp-mock.cjs.map +1 -1
  146. package/dist/mcp-mock.d.cts +33 -2
  147. package/dist/mcp-mock.d.cts.map +1 -1
  148. package/dist/mcp-mock.d.ts +33 -2
  149. package/dist/mcp-mock.d.ts.map +1 -1
  150. package/dist/mcp-mock.js +112 -1
  151. package/dist/mcp-mock.js.map +1 -1
  152. package/dist/mcp-recorder.cjs +1224 -0
  153. package/dist/mcp-recorder.cjs.map +1 -0
  154. package/dist/mcp-recorder.d.cts +108 -0
  155. package/dist/mcp-recorder.d.cts.map +1 -0
  156. package/dist/mcp-recorder.d.ts +108 -0
  157. package/dist/mcp-recorder.d.ts.map +1 -0
  158. package/dist/mcp-recorder.js +1215 -0
  159. package/dist/mcp-recorder.js.map +1 -0
  160. package/dist/messages.cjs +362 -56
  161. package/dist/messages.cjs.map +1 -1
  162. package/dist/messages.d.cts.map +1 -1
  163. package/dist/messages.d.ts.map +1 -1
  164. package/dist/messages.js +363 -58
  165. package/dist/messages.js.map +1 -1
  166. package/dist/misbehavior.cjs +1523 -0
  167. package/dist/misbehavior.cjs.map +1 -0
  168. package/dist/misbehavior.d.cts +43 -0
  169. package/dist/misbehavior.d.cts.map +1 -0
  170. package/dist/misbehavior.d.ts +43 -0
  171. package/dist/misbehavior.d.ts.map +1 -0
  172. package/dist/misbehavior.js +1511 -0
  173. package/dist/misbehavior.js.map +1 -0
  174. package/dist/ollama.cjs +310 -112
  175. package/dist/ollama.cjs.map +1 -1
  176. package/dist/ollama.d.cts.map +1 -1
  177. package/dist/ollama.d.ts.map +1 -1
  178. package/dist/ollama.js +311 -113
  179. package/dist/ollama.js.map +1 -1
  180. package/dist/record-sanitize.cjs +231 -0
  181. package/dist/record-sanitize.cjs.map +1 -0
  182. package/dist/record-sanitize.js +224 -0
  183. package/dist/record-sanitize.js.map +1 -0
  184. package/dist/recorder.cjs +106 -5
  185. package/dist/recorder.cjs.map +1 -1
  186. package/dist/recorder.d.cts +2 -1
  187. package/dist/recorder.d.cts.map +1 -1
  188. package/dist/recorder.d.ts +2 -1
  189. package/dist/recorder.d.ts.map +1 -1
  190. package/dist/recorder.js +107 -7
  191. package/dist/recorder.js.map +1 -1
  192. package/dist/responses.cjs +667 -171
  193. package/dist/responses.cjs.map +1 -1
  194. package/dist/responses.d.cts +5 -13
  195. package/dist/responses.d.cts.map +1 -1
  196. package/dist/responses.d.ts +5 -13
  197. package/dist/responses.d.ts.map +1 -1
  198. package/dist/responses.js +665 -174
  199. package/dist/responses.js.map +1 -1
  200. package/dist/router.cjs +24 -2
  201. package/dist/router.cjs.map +1 -1
  202. package/dist/router.d.cts.map +1 -1
  203. package/dist/router.d.ts.map +1 -1
  204. package/dist/router.js +25 -4
  205. package/dist/router.js.map +1 -1
  206. package/dist/server.cjs +572 -76
  207. package/dist/server.cjs.map +1 -1
  208. package/dist/server.d.cts.map +1 -1
  209. package/dist/server.d.ts.map +1 -1
  210. package/dist/server.js +574 -78
  211. package/dist/server.js.map +1 -1
  212. package/dist/stream-collapse.cjs +236 -34
  213. package/dist/stream-collapse.cjs.map +1 -1
  214. package/dist/stream-collapse.d.cts +39 -4
  215. package/dist/stream-collapse.d.cts.map +1 -1
  216. package/dist/stream-collapse.d.ts +39 -4
  217. package/dist/stream-collapse.d.ts.map +1 -1
  218. package/dist/stream-collapse.js +235 -35
  219. package/dist/stream-collapse.js.map +1 -1
  220. package/dist/types.cjs.map +1 -1
  221. package/dist/types.d.cts +315 -4
  222. package/dist/types.d.cts.map +1 -1
  223. package/dist/types.d.ts +315 -4
  224. package/dist/types.d.ts.map +1 -1
  225. package/dist/types.js.map +1 -1
  226. package/dist/validate-cli.cjs +36 -4
  227. package/dist/validate-cli.cjs.map +1 -1
  228. package/dist/validate-cli.js +37 -5
  229. package/dist/validate-cli.js.map +1 -1
  230. package/dist/vector-types.d.ts.map +1 -1
  231. package/dist/version.cjs +45 -0
  232. package/dist/version.cjs.map +1 -0
  233. package/dist/version.js +44 -0
  234. package/dist/version.js.map +1 -0
  235. package/dist/vitest.cjs +66 -10
  236. package/dist/vitest.cjs.map +1 -1
  237. package/dist/vitest.d.cts +35 -4
  238. package/dist/vitest.d.cts.map +1 -1
  239. package/dist/vitest.d.ts +35 -4
  240. package/dist/vitest.d.ts.map +1 -1
  241. package/dist/vitest.js +67 -11
  242. package/dist/vitest.js.map +1 -1
  243. package/dist/watcher.cjs +6 -2
  244. package/dist/watcher.cjs.map +1 -1
  245. package/dist/watcher.js +6 -2
  246. package/dist/watcher.js.map +1 -1
  247. package/dist/ws-gemini-live.cjs +210 -101
  248. package/dist/ws-gemini-live.cjs.map +1 -1
  249. package/dist/ws-gemini-live.d.cts +9 -3
  250. package/dist/ws-gemini-live.d.cts.map +1 -1
  251. package/dist/ws-gemini-live.d.ts +9 -3
  252. package/dist/ws-gemini-live.d.ts.map +1 -1
  253. package/dist/ws-gemini-live.js +211 -102
  254. package/dist/ws-gemini-live.js.map +1 -1
  255. package/dist/ws-realtime.cjs +175 -62
  256. package/dist/ws-realtime.cjs.map +1 -1
  257. package/dist/ws-realtime.d.cts +8 -3
  258. package/dist/ws-realtime.d.cts.map +1 -1
  259. package/dist/ws-realtime.d.ts +8 -3
  260. package/dist/ws-realtime.d.ts.map +1 -1
  261. package/dist/ws-realtime.js +176 -63
  262. package/dist/ws-realtime.js.map +1 -1
  263. package/dist/ws-responses.cjs +109 -16
  264. package/dist/ws-responses.cjs.map +1 -1
  265. package/dist/ws-responses.d.cts +8 -12
  266. package/dist/ws-responses.d.cts.map +1 -1
  267. package/dist/ws-responses.d.ts +8 -12
  268. package/dist/ws-responses.d.ts.map +1 -1
  269. package/dist/ws-responses.js +111 -18
  270. package/dist/ws-responses.js.map +1 -1
  271. package/package.json +10 -2
  272. package/skills/write-fixtures/SKILL.md +179 -21
package/dist/server.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import { DEFAULT_TEST_ID } from "./constants.js";
2
2
  import { build, msg, quote } from "./message-text.js";
3
3
  import { markAuthenticatedRequest, resolveInboundAuth, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection } from "./api-key-auth.js";
4
- import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, describeMatch, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse, isContentWithToolCallsResponse, isEmbeddingResponse, isErrorResponse, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, readBody, readBodyBufferBounded, resolveFixtureBlockOutcome, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveStrictMode, resolveTestId, resolveUsage, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, validateToolsField, wouldProxyMiss } from "./helpers.js";
4
+ import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildOpenAIContentFilterChunks, buildOpenAIContentFilterCompletion, buildOpenAIReasoningChunks, buildOpenAIReasoningCompletion, buildOpenAIRefusalChunks, buildOpenAIRefusalCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, createServerFixtureList, describeMatch, extractOverrides, fixtureToolCallErrorCode, flattenHeaders, getContext, getTestId, googleFixtureToolCallErrorDetails, isAudioResponse, isCombinedFixtureResponse, isEmbeddingResponse, isErrorResponse, isExtendedResponsesToolsList, isFixtureToolCallError, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isServerOwnedFixtures, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, prepareOpenAIChatMisbehavior, readBody, readBodyBufferBounded, requireEmittedFunctionToolCalls, requireServedFunctionToolCalls, resolveFixtureBlockOutcome, resolveOpenAIChatMisbehaviorUsage, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveStrictMode, resolveTestId, resolveUsage, runWithToolArgumentsScope, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, validateToolsField, withoutResponsesToolKeys, wouldProxyMiss } from "./helpers.js";
5
5
  import { normalizeLiveOptions } from "./live-fixture.js";
6
6
  import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
7
7
  import { CHAOS_FIELDS, CHAOS_FIELD_NAMES, applyChaosAction, awaitChaosLatency, describeUnwritableReason, evaluateChaos, isChaosScope, parseChaosField, resetChaosWarnings, resolveChaosConfig, responseGoneReason } from "./chaos.js";
8
- import { FixtureLoadError, INJECTED_STATUS_RANGE, claimOneShotError, clearFixtureQueue, entryToFixture, isInjectableStatus, isOneShotError, queueOneShotError, releaseOneShotError, validateFixtures } from "./fixture-loader.js";
8
+ import { MISBEHAVIOR_CATALOG, WIRE_SUPPORT, getFixtureMisbehaviorPosition, parseMisbehavior, parseMisbehaviorHeader, planMisbehavior, recordMisbehaviorOutcome, resolveMisbehaviorSeed, resolveMisbehaviorShortCircuit, setFixtureMisbehaviorPosition } from "./misbehavior.js";
9
+ import { FixtureLoadError, INJECTED_STATUS_RANGE, MisbehaviorConfigError, claimOneShotError, clearFixtureQueue, enableHeldFixtureMisbehavior, entryToFixture, isInjectableStatus, isOneShotError, markFixtureResponsesToolsExtended, queueOneShotError, releaseOneShotError, responsesToolsExtendedView, responsesToolsViewSource, validateFixtures } from "./fixture-loader.js";
9
10
  import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
10
11
  import { proxyAndRecord } from "./recorder.js";
11
12
  import { Logger } from "./logger.js";
@@ -52,6 +53,7 @@ import { DESTROYED_STATUS_LABEL, createMetricsRegistry, normalizePathLabel } fro
52
53
  import { MCP_FAKES_DEFAULT_MOUNT, McpFakeStore, McpFakesAddError, blockIdOf } from "./mcp-fakes.js";
53
54
  import { MCPMock } from "./mcp-mock.js";
54
55
  import { ensureFakeMount, findFakeMount, handOffMcpFakes, isFakeMount, mountConflictDetail } from "./mcp-fakes-mount.js";
56
+ import { handleFakesReportRoute } from "./mcp-fakes-report.js";
55
57
  import * as http$1 from "node:http";
56
58
  import { AsyncLocalStorage } from "node:async_hooks";
57
59
 
@@ -159,6 +161,7 @@ function performFullReset(fixtures, targets) {
159
161
  targets.journal.clear();
160
162
  resetChaosWarnings(targets.defaults.logger);
161
163
  targets.defaults.chaos = void 0;
164
+ targets.defaults.misbehavior?.byTestId.clear();
162
165
  targets.videoStates.clear();
163
166
  targets.openRouterVideoJobs.clear();
164
167
  targets.veoVideoJobs.clear();
@@ -204,7 +207,7 @@ function fixtureResponseKind(response) {
204
207
  if (isImageResponse(response)) return "image";
205
208
  if (isEmbeddingResponse(response)) return "embedding";
206
209
  if (isJSONResponse(response)) return "json";
207
- if (isContentWithToolCallsResponse(response)) return "contentWithToolCalls";
210
+ if (isCombinedFixtureResponse(response)) return "contentWithToolCalls";
208
211
  if (isToolCallResponse(response)) return "toolCalls";
209
212
  if (isTextResponse(response)) return "text";
210
213
  return "unknown";
@@ -368,7 +371,8 @@ function planControlApiFakes(raw, mounts) {
368
371
  const target = ensureFakeMount(mounts, group.path, {
369
372
  journal: wiring.journal,
370
373
  registry: wiring.defaults.registry,
371
- logger: wiring.defaults.logger
374
+ logger: wiring.defaults.logger,
375
+ replaySpeed: wiring.defaults.replaySpeed
372
376
  }, wiring.defaults.logger);
373
377
  if ("conflict" in target) continue;
374
378
  const result = target.handler.addMcpFakes?.(group.sources, { kind: "control-api" });
@@ -419,6 +423,7 @@ function listControlApiFakes(mounts, testId, context, mount) {
419
423
  }] : []);
420
424
  return mount !== null && listed.length === 0 ? null : listed;
421
425
  }
426
+ const controlFixtureAdditions = /* @__PURE__ */ new WeakMap();
422
427
  /**
423
428
  * Handle requests under `/__aimock/`. Returns `true` if the request was
424
429
  * handled, `false` if the path doesn't match the control prefix.
@@ -490,7 +495,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
490
495
  "Content-Type": "application/json",
491
496
  "X-Total-Count": String(total)
492
497
  });
493
- res.end(JSON.stringify(entries));
498
+ res.end(journalJson(entries, journal));
494
499
  return true;
495
500
  }
496
501
  if (subPath === "/fixtures" && req.method === "GET") {
@@ -510,19 +515,70 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
510
515
  res.end(JSON.stringify({ error: "Invalid 'include': expected 'fixtures'" }));
511
516
  return true;
512
517
  }
518
+ const extendedTools = isExtendedResponsesToolsList(fixtures);
513
519
  res.writeHead(200, { "Content-Type": "application/json" });
514
520
  res.end(JSON.stringify({
515
521
  count: fixtures.length,
516
522
  fixtures: fixtures.map((fixture, index) => ({
517
523
  index,
518
524
  match: redactForInspection(fixture.match),
519
- responseKind: isOneShotError(fixture) ? "error" : fixtureResponseKind(fixture.response),
525
+ responseKind: isOneShotError(fixture) ? "error" : fixtureResponseKind(extendedTools || typeof fixture.response === "function" ? fixture.response : withoutResponsesToolKeys(fixture.response)),
520
526
  ...fixture.latency !== void 0 ? { latency: fixture.latency } : {},
521
527
  ...fixture.chaos !== void 0 ? { chaos: fixture.chaos } : {}
522
528
  }))
523
529
  }));
524
530
  return true;
525
531
  }
532
+ if (subPath === "/misbehavior/catalog" && req.method === "GET") {
533
+ res.writeHead(200, { "Content-Type": "application/json" });
534
+ res.end(JSON.stringify({
535
+ catalog: MISBEHAVIOR_CATALOG,
536
+ WIRE_SUPPORT
537
+ }));
538
+ return true;
539
+ }
540
+ if (subPath === "/misbehavior" && (req.method === "GET" || req.method === "POST" || req.method === "DELETE")) {
541
+ const scopeId = chaosScopeId(req);
542
+ if (scopeId === null) return writeBlankTestId(res);
543
+ const reply = (status, body) => {
544
+ res.writeHead(status, { "Content-Type": "application/json" });
545
+ res.end(JSON.stringify(body));
546
+ return true;
547
+ };
548
+ const scope = defaults.misbehavior;
549
+ if (!scope && req.method !== "GET") return reply(409, { error: "misbehavior is not enabled: start aimock with enableMisbehavior: true (CLI: --misbehavior)" });
550
+ if (req.method === "POST") {
551
+ let raw;
552
+ try {
553
+ raw = await readBody(req);
554
+ } catch (err) {
555
+ const msg = err instanceof Error ? err.message : String(err);
556
+ defaults.logger.error(`POST /__aimock/misbehavior: failed to read body: ${msg}`);
557
+ return reply(400, { error: `Failed to read request body: ${msg}` });
558
+ }
559
+ let input;
560
+ try {
561
+ input = JSON.parse(raw);
562
+ } catch (err) {
563
+ const msg = err instanceof Error ? err.message : String(err);
564
+ defaults.logger.error(`POST /__aimock/misbehavior: invalid JSON: ${msg}`);
565
+ return reply(400, { error: `Invalid JSON: ${msg}` });
566
+ }
567
+ if (isJsonObject(input) && Object.keys(input).length === 0) input = { faults: [] };
568
+ const parsed = parseMisbehavior(input);
569
+ if (!parsed.ok) return reply(400, {
570
+ error: "Validation failed",
571
+ rule: parsed.issue.rule,
572
+ message: parsed.issue.message
573
+ });
574
+ const config = parsed.config.seed === "random" ? {
575
+ ...parsed.config,
576
+ seed: resolveMisbehaviorSeed(parsed.config.seed, defaults.logger)
577
+ } : parsed.config;
578
+ scope?.byTestId.set(scopeId, config);
579
+ } else if (req.method === "DELETE") scope?.byTestId.delete(scopeId);
580
+ return reply(200, { misbehavior: scope?.byTestId.get(scopeId) ?? scope?.baseline ?? { faults: [] } });
581
+ }
526
582
  if (subPath === "/chaos" && req.method === "GET") {
527
583
  const scopeId = chaosScopeId(req);
528
584
  if (scopeId === null) return writeBlankTestId(res);
@@ -613,6 +669,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
613
669
  mounts: listed
614
670
  });
615
671
  }
672
+ if (subPath === "/mcp/fakes/report" && req.method === "GET") return handleFakesReportRoute(res, searchParams, mounts);
616
673
  if (subPath === "/fixtures" && req.method === "POST") {
617
674
  let raw;
618
675
  try {
@@ -653,8 +710,25 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
653
710
  res.end(JSON.stringify({ error: `Fixture at index ${invalidMatchIndex}: match must be an object` }));
654
711
  return true;
655
712
  }
656
- const converted = entries.map((e) => entryToFixture(e));
657
- const errors = validateFixtures(converted).filter((i) => i.severity === "error");
713
+ const extendedResponsesTools = defaults.responsesTools === "extended";
714
+ const markInPlace = extendedResponsesTools && isServerOwnedFixtures(fixtures);
715
+ const addition = controlFixtureAdditions.get(defaults) ?? 0;
716
+ controlFixtureAdditions.set(defaults, addition + 1);
717
+ const converted = [];
718
+ const loadErrors = [];
719
+ for (const [index, entry] of entries.entries()) try {
720
+ const fixture = entryToFixture(entry, void 0, void 0, {
721
+ file: `control-api#${addition}`,
722
+ index
723
+ });
724
+ if (defaults.misbehavior) enableHeldFixtureMisbehavior(fixture);
725
+ if (markInPlace) markFixtureResponsesToolsExtended(fixture);
726
+ converted.push(fixture);
727
+ } catch (error) {
728
+ if (!(error instanceof FixtureLoadError || error instanceof MisbehaviorConfigError)) throw error;
729
+ loadErrors.push(error);
730
+ }
731
+ const errors = (loadErrors.length === 0 ? validateFixtures(extendedResponsesTools ? converted.map(responsesToolsExtendedView) : converted) : []).filter((i) => i.severity === "error");
658
732
  const fakes = withFakes ? planControlApiFakes(parsed.mcpFakes, mounts) : null;
659
733
  const fakeErrors = fakes?.errors ?? [];
660
734
  const validationFailed = (details) => {
@@ -665,7 +739,11 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
665
739
  }));
666
740
  return true;
667
741
  };
668
- if (errors.length > 0 || fakeErrors.length > 0) return validationFailed([...errors, ...fakeErrors.map((e) => e.toJSON())]);
742
+ if (loadErrors.length > 0 || errors.length > 0 || fakeErrors.length > 0) return validationFailed([
743
+ ...loadErrors.map((e) => e.toJSON()),
744
+ ...errors,
745
+ ...fakeErrors.map((e) => e.toJSON())
746
+ ]);
669
747
  let mcpFakesAdded = 0;
670
748
  if (fakes) try {
671
749
  mcpFakesAdded = fakes.apply({
@@ -687,6 +765,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
687
765
  }
688
766
  if (subPath === "/fixtures" && req.method === "DELETE") {
689
767
  clearFixtureQueue(fixtures);
768
+ journal.clearMisbehaviorCounters();
690
769
  for (const { handler } of mounts) handler.clearMcpFakes?.();
691
770
  if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
692
771
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -803,8 +882,9 @@ function clientAbortedMidBody(req, res, err) {
803
882
  if (err instanceof RequestBodyTooLargeError) return false;
804
883
  return res.destroyed && !req.complete;
805
884
  }
806
- async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey, openRouter = false) {
885
+ async function handleCompletions(req, res, fixtures, journal, defaults, getRequestEntry, modelFallback, providerKey, openRouter = false, beforeFixtureSelection, bytePlusPath = false) {
807
886
  setCorsHeaders(res);
887
+ const wire = providerKey === "azure" ? "Azure OpenAI Chat Completions" : providerKey === "openrouter" ? "OpenRouter Chat Completions" : providerKey === "byteplus" || bytePlusPath ? "BytePlus ModelArk Chat Completions" : "OpenAI Chat Completions";
808
888
  let raw;
809
889
  try {
810
890
  raw = await readBody(req);
@@ -970,6 +1050,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
970
1050
  const openRouterFallback = openRouter && Array.isArray(body.models) && body.models.length > 0;
971
1051
  const selectFixture = (probe, counts) => {
972
1052
  for (;;) {
1053
+ beforeFixtureSelection?.();
973
1054
  const attempt = matchFixtureDiagnostic(fixtures, probe, counts, defaults.requestTransform, matchOptions);
974
1055
  if (attempt.fixture && isOneShotError(attempt.fixture) && !claimOneShotError(fixtures, attempt.fixture)) continue;
975
1056
  return attempt;
@@ -1053,13 +1134,32 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1053
1134
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1054
1135
  const missWouldProxy = wouldProxyMiss(effectiveStrict, defaults.record, providerKey);
1055
1136
  const noFixtureSource = missWouldProxy ? "proxy" : "internal";
1137
+ const recordBypass = (reason) => {
1138
+ const entry = getRequestEntry();
1139
+ if (!entry) return;
1140
+ recordMisbehaviorOutcome({
1141
+ entry,
1142
+ summary: resolveMisbehaviorShortCircuit({
1143
+ wire: "openai-chat",
1144
+ fixture: fixture ?? void 0,
1145
+ defaults,
1146
+ rawHeaders: req.headers,
1147
+ url: req.url,
1148
+ reason
1149
+ }),
1150
+ defaults,
1151
+ testId
1152
+ });
1153
+ };
1056
1154
  if (chaosAction === "drop" || chaosAction === "disconnect" || chaosAction === "rateLimit") {
1057
1155
  applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : noFixtureSource, defaults.registry, defaults.logger);
1156
+ recordBypass("chaos-fired");
1058
1157
  releaseOneShotError(fixtures, fixture);
1059
1158
  return;
1060
1159
  }
1061
1160
  if (chaosAction === "malformed" && (fixture || !missWouldProxy)) {
1062
1161
  applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "internal", defaults.registry, defaults.logger);
1162
+ recordBypass("chaos-fired");
1063
1163
  releaseOneShotError(fixtures, fixture);
1064
1164
  return;
1065
1165
  }
@@ -1091,6 +1191,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1091
1191
  const hookOptions = chaosAction === "malformed" ? {
1092
1192
  beforeWriteResponse: () => {
1093
1193
  applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry, defaults.logger);
1194
+ recordBypass("chaos-fired");
1094
1195
  return true;
1095
1196
  },
1096
1197
  onHookBypassed: (reason) => {
@@ -1133,10 +1234,12 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1133
1234
  } : {}
1134
1235
  }
1135
1236
  });
1237
+ recordBypass("proxied");
1136
1238
  return;
1137
1239
  }
1138
1240
  if (chaosAction === "malformed") {
1139
1241
  applyChaosAction(chaosAction, res, null, journal, chaosContext, "internal", defaults.registry, defaults.logger);
1242
+ recordBypass("chaos-fired");
1140
1243
  return;
1141
1244
  }
1142
1245
  }
@@ -1159,13 +1262,71 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1159
1262
  } }));
1160
1263
  return;
1161
1264
  }
1162
- const response = preResolvedResponse ?? await resolveResponse(fixture, body);
1265
+ const resolvedResponse = preResolvedResponse ?? await resolveResponse(fixture, body);
1266
+ const evaluation = planMisbehavior({
1267
+ wire: "openai-chat",
1268
+ fixture,
1269
+ response: resolvedResponse,
1270
+ request: body,
1271
+ stream: body.stream === true,
1272
+ defaults,
1273
+ rawHeaders: req.headers,
1274
+ url: req.url
1275
+ });
1276
+ if (evaluation.kind === "error") {
1277
+ recordMisbehaviorOutcome({
1278
+ entry: journal.add({
1279
+ method,
1280
+ path,
1281
+ headers: flatHeaders,
1282
+ body,
1283
+ response: {
1284
+ status: evaluation.status,
1285
+ fixture
1286
+ }
1287
+ }),
1288
+ summary: evaluation.summary,
1289
+ defaults,
1290
+ testId
1291
+ });
1292
+ if (!evaluation.summary?.evaluations.some((row) => row.outcome === "error")) defaults.logger.error(`${evaluation.code}: ${evaluation.message}`);
1293
+ writeErrorResponse(res, evaluation.status, openRouter ? serializeOpenRouterError(evaluation.status, `${evaluation.code}: ${evaluation.message}`) : JSON.stringify({ error: {
1294
+ message: evaluation.message,
1295
+ type: "invalid_request_error",
1296
+ param: null,
1297
+ code: evaluation.code
1298
+ } }));
1299
+ return;
1300
+ }
1301
+ const appliedPlan = evaluation.kind === "applied" ? prepareOpenAIChatMisbehavior(evaluation) : void 0;
1302
+ const response = appliedPlan?.response ?? resolvedResponse;
1303
+ const summary = appliedPlan?.summary ?? evaluation.summary;
1304
+ const recordOutcome = (entry) => {
1305
+ recordMisbehaviorOutcome({
1306
+ entry,
1307
+ summary,
1308
+ defaults,
1309
+ testId
1310
+ });
1311
+ };
1312
+ const appliedReasoning = appliedPlan && appliedPlan.reasoning === void 0 && (isTextResponse(response) || isToolCallResponse(response) || isCombinedFixtureResponse(response)) ? resolveReasoningForModel(response.reasoning, responseModel, resolveStrictMode(defaults.strict, req.headers), defaults.logger) : void 0;
1313
+ const appliedUsage = appliedPlan ? resolveOpenAIChatMisbehaviorUsage(appliedPlan, body, appliedPlan.reasoning === void 0 ? appliedReasoning !== void 0 : openRouter) : void 0;
1163
1314
  const latency = fixture.latency ?? defaults.latency;
1164
1315
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
1165
1316
  const emitStreamingUsage = body.stream === true && body.stream_options?.include_usage === true || openRouter;
1166
1317
  const streamingPromptText = body.messages.map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((p) => p.text ?? "").join("") : "").join("");
1167
- const resolveStreamingUsageTokens = (overrides, completionText) => resolveUsage(overrides, streamingPromptText, completionText);
1318
+ const resolveStreamingUsageTokens = (overrides, completionText) => appliedUsage ?? resolveUsage(overrides, streamingPromptText, completionText);
1319
+ const resolveRecordedTimings = (chunks) => {
1320
+ const timings = fixture.recordedTimings;
1321
+ if (!appliedPlan?.duplicateId || !timings) return timings;
1322
+ const gaps = timings.interChunkDelaysMs;
1323
+ return {
1324
+ ...timings,
1325
+ interChunkDelaysMs: Array.from({ length: Math.max(gaps.length, chunks.length - 1) }, (_, index) => gaps[index] ?? gaps.at(-1) ?? 0)
1326
+ };
1327
+ };
1168
1328
  const shapeORCompletion = (completion, overrides) => {
1329
+ if (appliedUsage) completion.usage = appliedUsage;
1169
1330
  if (!openRouter) return completion;
1170
1331
  return shapeOpenRouterCompletion(completion, resolveOpenRouterShaping(overrides, overrides?.model ?? responseModel), overrides?.id !== void 0);
1171
1332
  };
@@ -1178,7 +1339,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1178
1339
  const openRouterProcessing = !!(openRouter && fixture.openRouterProcessing);
1179
1340
  if (isErrorResponse(response)) {
1180
1341
  const status = response.status ?? 500;
1181
- journal.add({
1342
+ recordOutcome(journal.add({
1182
1343
  method: req.method ?? "POST",
1183
1344
  path: req.url ?? COMPLETIONS_PATH,
1184
1345
  headers: flattenHeaders(req.headers),
@@ -1187,12 +1348,12 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1187
1348
  status,
1188
1349
  fixture
1189
1350
  }
1190
- });
1351
+ }));
1191
1352
  writeErrorResponse(res, status, openRouter ? serializeOpenRouterError(status, response.error.message, response.error.metadata) : serializeErrorResponse(response), { retryAfter: response.retryAfter });
1192
1353
  return;
1193
1354
  }
1194
1355
  if (isAudioResponse(response)) {
1195
- journal.add({
1356
+ recordOutcome(journal.add({
1196
1357
  method: req.method ?? "POST",
1197
1358
  path: req.url ?? COMPLETIONS_PATH,
1198
1359
  headers: flattenHeaders(req.headers),
@@ -1201,18 +1362,58 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1201
1362
  status: 422,
1202
1363
  fixture
1203
1364
  }
1204
- });
1365
+ }));
1205
1366
  writeErrorResponse(res, 422, openRouter ? serializeOpenRouterError(422, "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.") : JSON.stringify({ error: {
1206
1367
  message: "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.",
1207
1368
  type: "invalid_request_error"
1208
1369
  } }));
1209
1370
  return;
1210
1371
  }
1211
- if (isContentWithToolCallsResponse(response)) {
1372
+ if (appliedPlan && isTextResponse(response) && (appliedPlan.stop === "refusal" || appliedPlan.stop === "content_filter" || appliedPlan.reasoning !== void 0)) {
1373
+ const overrides = extractOverrides(response);
1374
+ const entry = journal.add({
1375
+ method,
1376
+ path,
1377
+ headers: flatHeaders,
1378
+ body,
1379
+ response: {
1380
+ status: 200,
1381
+ fixture
1382
+ }
1383
+ });
1384
+ recordOutcome(entry);
1385
+ if (body.stream !== true) {
1386
+ const completion = appliedPlan.stop === "refusal" ? buildOpenAIRefusalCompletion(appliedPlan.refusal ?? "", responseModel, overrides, body.messages) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterCompletion(responseModel, overrides, body.messages) : buildOpenAIReasoningCompletion(appliedPlan.reasoning ?? "", responseModel, openRouter, overrides, body.messages);
1387
+ res.writeHead(200, { "Content-Type": "application/json" });
1388
+ res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1389
+ } else {
1390
+ const chunks = appliedPlan.stop === "refusal" ? buildOpenAIRefusalChunks(appliedPlan.refusal ?? "", responseModel, chunkSize, overrides) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterChunks(responseModel, overrides) : buildOpenAIReasoningChunks(appliedPlan.reasoning ?? "", responseModel, chunkSize, openRouter, overrides);
1391
+ const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0].id, overrides?.model ?? responseModel, chunks[0].created, resolveStreamingUsageTokens(overrides, ""), overrides?.systemFingerprint) : void 0;
1392
+ shapeORChunks(chunks, usageChunk, overrides);
1393
+ const interruption = createInterruptionSignal(fixture);
1394
+ if (!await writeSSEStream(res, chunks, {
1395
+ latency,
1396
+ streamingProfile: fixture.streamingProfile,
1397
+ signal: interruption?.signal,
1398
+ onChunkSent: interruption?.tick,
1399
+ usageChunk,
1400
+ recordedTimings: resolveRecordedTimings(chunks),
1401
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1402
+ openRouterProcessing
1403
+ })) {
1404
+ if (!res.writableEnded) res.destroy();
1405
+ entry.response.interrupted = true;
1406
+ entry.response.interruptReason = interruption?.reason();
1407
+ }
1408
+ interruption?.cleanup();
1409
+ }
1410
+ return;
1411
+ }
1412
+ if (isCombinedFixtureResponse(response)) {
1212
1413
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
1213
1414
  const overrides = extractOverrides(response);
1214
1415
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1215
- const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1416
+ const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1216
1417
  const streaming = body.stream === true;
1217
1418
  const blockOutcome = response.blocks && response.blocks.length > 0 ? resolveFixtureBlockOutcome(response.blocks) : void 0;
1218
1419
  const journalEntry = journal.add({
@@ -1225,16 +1426,18 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1225
1426
  fixture
1226
1427
  }
1227
1428
  });
1429
+ recordOutcome(journalEntry);
1430
+ const toolCalls = requireEmittedFunctionToolCalls(response, wire);
1228
1431
  if (!streaming) {
1229
- const completion = buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? response.toolCalls ?? [], responseModel, effReasoning, blockOutcome ? {
1432
+ const completion = buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? toolCalls, responseModel, effReasoning, blockOutcome ? {
1230
1433
  ...overrides,
1231
1434
  finishReason: overrides?.finishReason ?? (blockOutcome.hasToolCalls ? "tool_calls" : "stop")
1232
1435
  } : overrides, body.messages);
1233
1436
  res.writeHead(200, { "Content-Type": "application/json" });
1234
1437
  res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1235
1438
  } else {
1236
- const chunks = buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? response.toolCalls ?? [], responseModel, chunkSize, effReasoning, overrides, blockOutcome?.ordered);
1237
- const completionText = blockOutcome ? blockOutcome.content + blockOutcome.toolCalls.map((tc) => tc.name + tc.arguments).join("") : (response.content ?? "") + (response.toolCalls ?? []).map((tc) => tc.name + tc.arguments).join("");
1439
+ const chunks = buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? toolCalls, responseModel, chunkSize, effReasoning, overrides, blockOutcome?.ordered);
1440
+ const completionText = blockOutcome ? blockOutcome.content + blockOutcome.toolCalls.map((tc) => tc.name + tc.arguments).join("") : (response.content ?? "") + toolCalls.map((tc) => tc.name + tc.arguments).join("");
1238
1441
  const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
1239
1442
  shapeORChunks(chunks, usageChunk, overrides);
1240
1443
  const interruption = createInterruptionSignal(fixture);
@@ -1244,7 +1447,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1244
1447
  signal: interruption?.signal,
1245
1448
  onChunkSent: interruption?.tick,
1246
1449
  usageChunk,
1247
- recordedTimings: fixture.recordedTimings,
1450
+ recordedTimings: resolveRecordedTimings(chunks),
1248
1451
  replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1249
1452
  openRouterProcessing
1250
1453
  })) {
@@ -1260,7 +1463,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1260
1463
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
1261
1464
  const overrides = extractOverrides(response);
1262
1465
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1263
- const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1466
+ const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1264
1467
  const journalEntry = journal.add({
1265
1468
  method: req.method ?? "POST",
1266
1469
  path: req.url ?? COMPLETIONS_PATH,
@@ -1271,6 +1474,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1271
1474
  fixture
1272
1475
  }
1273
1476
  });
1477
+ recordOutcome(journalEntry);
1274
1478
  if (body.stream !== true) {
1275
1479
  const completion = buildTextCompletion(response.content, responseModel, effReasoning, overrides, body.messages);
1276
1480
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -1286,7 +1490,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1286
1490
  signal: interruption?.signal,
1287
1491
  onChunkSent: interruption?.tick,
1288
1492
  usageChunk,
1289
- recordedTimings: fixture.recordedTimings,
1493
+ recordedTimings: resolveRecordedTimings(chunks),
1290
1494
  replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1291
1495
  openRouterProcessing
1292
1496
  })) {
@@ -1302,7 +1506,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1302
1506
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
1303
1507
  const overrides = extractOverrides(response);
1304
1508
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1305
- const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1509
+ const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1306
1510
  const journalEntry = journal.add({
1307
1511
  method: req.method ?? "POST",
1308
1512
  path: req.url ?? COMPLETIONS_PATH,
@@ -1313,13 +1517,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1313
1517
  fixture
1314
1518
  }
1315
1519
  });
1520
+ recordOutcome(journalEntry);
1521
+ const toolCalls = requireServedFunctionToolCalls(response, wire);
1316
1522
  if (body.stream !== true) {
1317
- const completion = buildToolCallCompletion(response.toolCalls, responseModel, effReasoning, overrides, body.messages);
1523
+ const completion = buildToolCallCompletion(toolCalls, responseModel, effReasoning, overrides, body.messages);
1318
1524
  res.writeHead(200, { "Content-Type": "application/json" });
1319
1525
  res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1320
1526
  } else {
1321
- const chunks = buildToolCallChunks(response.toolCalls, responseModel, chunkSize, effReasoning, overrides);
1322
- const completionText = response.toolCalls.map((tc) => tc.name + tc.arguments).join("");
1527
+ const chunks = buildToolCallChunks(toolCalls, responseModel, chunkSize, effReasoning, overrides);
1528
+ const completionText = toolCalls.map((tc) => tc.name + tc.arguments).join("");
1323
1529
  const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
1324
1530
  shapeORChunks(chunks, usageChunk, overrides);
1325
1531
  const interruption = createInterruptionSignal(fixture);
@@ -1329,7 +1535,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1329
1535
  signal: interruption?.signal,
1330
1536
  onChunkSent: interruption?.tick,
1331
1537
  usageChunk,
1332
- recordedTimings: fixture.recordedTimings,
1538
+ recordedTimings: resolveRecordedTimings(chunks),
1333
1539
  replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1334
1540
  openRouterProcessing
1335
1541
  })) {
@@ -1341,7 +1547,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1341
1547
  }
1342
1548
  return;
1343
1549
  }
1344
- journal.add({
1550
+ recordOutcome(journal.add({
1345
1551
  method: req.method ?? "POST",
1346
1552
  path: req.url ?? COMPLETIONS_PATH,
1347
1553
  headers: flattenHeaders(req.headers),
@@ -1350,7 +1556,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1350
1556
  status: 500,
1351
1557
  fixture
1352
1558
  }
1353
- });
1559
+ }));
1354
1560
  writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, "Fixture response did not match any known type") : JSON.stringify({ error: {
1355
1561
  message: "Fixture response did not match any known type",
1356
1562
  type: "server_error"
@@ -1375,22 +1581,90 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1375
1581
  }
1376
1582
  }
1377
1583
  /**
1584
+ * The journal as JSON. An entry names the caller's fixture; the JSON shows
1585
+ * the fixture the request was served from (`Journal.servedFixture`): an
1586
+ * extended server's view, with the held keys applied, or LLMock's
1587
+ * per-addition copy with misbehavior enabled. The journal therefore reads as
1588
+ * it did when the entries held those objects.
1589
+ */
1590
+ function journalJson(entries, journal) {
1591
+ if (!entries.some((entry) => journal.servedFixture(entry) !== entry.response.fixture)) return JSON.stringify(entries);
1592
+ return JSON.stringify(entries.map((entry) => {
1593
+ const fixture = entry.response.fixture;
1594
+ let served = journal.servedFixture(entry);
1595
+ if (served === fixture || !served) return entry;
1596
+ const viewSource = responsesToolsViewSource(served);
1597
+ if (viewSource !== served) served = responsesToolsExtendedView(viewSource);
1598
+ return {
1599
+ ...entry,
1600
+ response: {
1601
+ ...entry.response,
1602
+ fixture: served
1603
+ }
1604
+ };
1605
+ }));
1606
+ }
1607
+ /**
1378
1608
  * The server of `createServerWithResolvedAuth`, after the MCP fakes hand-off.
1379
1609
  * `commitHandOff` runs once the server listens; if it throws, the server is
1380
1610
  * closed and start rejects.
1381
1611
  */
1382
1612
  async function startServer(fixtures, options, resolvedAuth, mountList, logger, serviceFixtures, commitHandOff) {
1613
+ const callerFixtures = fixtures;
1614
+ let rawFixtureAddition = 0;
1615
+ function ensureRawFixturePositions() {
1616
+ const existingPositions = new Set(callerFixtures.map(getFixtureMisbehaviorPosition).filter((position) => position !== void 0));
1617
+ for (const fixture of callerFixtures) {
1618
+ if (getFixtureMisbehaviorPosition(fixture) !== void 0) continue;
1619
+ let position;
1620
+ do
1621
+ position = `createServer#${rawFixtureAddition++}`;
1622
+ while (existingPositions.has(position));
1623
+ setFixtureMisbehaviorPosition(fixture, position);
1624
+ existingPositions.add(position);
1625
+ }
1626
+ }
1627
+ ensureRawFixturePositions();
1383
1628
  const host = options?.host ?? "127.0.0.1";
1384
1629
  const port = options?.port ?? 0;
1385
1630
  const registry = options?.metrics ? createMetricsRegistry() : void 0;
1386
1631
  const serverOptions = options ?? {};
1632
+ const validResponsesTools = (mode) => mode === "legacy" || mode === "extended";
1633
+ if (serverOptions.responsesTools !== void 0 && !validResponsesTools(serverOptions.responsesTools)) new Logger("warn").warn(`Ignoring responsesTools because it must be "legacy" or "extended", got ${JSON.stringify(serverOptions.responsesTools)}. Using "legacy".`);
1634
+ fixtures = createServerFixtureList(callerFixtures, () => serverOptions.responsesTools, responsesToolsExtendedView);
1387
1635
  const chaosByTestId = /* @__PURE__ */ new Map();
1636
+ let misbehaviorBaseline;
1637
+ const misbehavior = {
1638
+ byTestId: /* @__PURE__ */ new Map(),
1639
+ get baseline() {
1640
+ return this.byTestId.get(DEFAULT_TEST_ID) ?? misbehaviorBaseline;
1641
+ },
1642
+ set baseline(config) {
1643
+ misbehaviorBaseline = config;
1644
+ if (config === void 0) this.byTestId.delete(DEFAULT_TEST_ID);
1645
+ else if (this.byTestId.has(DEFAULT_TEST_ID)) this.byTestId.set(DEFAULT_TEST_ID, config);
1646
+ }
1647
+ };
1648
+ const misbehaviorEnabled = serverOptions.enableMisbehavior === true;
1649
+ if (misbehaviorEnabled) callerFixtures.forEach(enableHeldFixtureMisbehavior);
1650
+ if (misbehaviorEnabled && serverOptions.misbehavior !== void 0) {
1651
+ const parsed = parseMisbehavior(serverOptions.misbehavior);
1652
+ if (!parsed.ok) throw new TypeError(`${parsed.issue.rule}: ${parsed.issue.message}`);
1653
+ misbehavior.baseline = parsed.config.seed === "random" ? {
1654
+ ...parsed.config,
1655
+ seed: resolveMisbehaviorSeed(parsed.config.seed, logger)
1656
+ } : parsed.config;
1657
+ }
1388
1658
  const defaults = {
1389
1659
  latency: serverOptions.latency ?? 0,
1390
1660
  chunkSize: Math.max(1, serverOptions.chunkSize ?? DEFAULT_CHUNK_SIZE),
1391
1661
  replaySpeed: serverOptions.replaySpeed ?? 1,
1392
1662
  logger,
1393
1663
  chaosByTestId,
1664
+ misbehavior: misbehaviorEnabled ? misbehavior : void 0,
1665
+ get misbehaviorCounters() {
1666
+ return journal;
1667
+ },
1394
1668
  get chaos() {
1395
1669
  return {
1396
1670
  base: chaosByTestId.get(DEFAULT_TEST_ID) ?? serverOptions.chaos,
@@ -1413,6 +1687,13 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1413
1687
  get strict() {
1414
1688
  return serverOptions.strict;
1415
1689
  },
1690
+ get strictToolArguments() {
1691
+ return serverOptions.strictToolArguments;
1692
+ },
1693
+ get responsesTools() {
1694
+ const mode = serverOptions.responsesTools;
1695
+ return validResponsesTools(mode) ? mode : void 0;
1696
+ },
1416
1697
  get requestTransform() {
1417
1698
  return serverOptions.requestTransform;
1418
1699
  },
@@ -1503,6 +1784,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1503
1784
  if (req) ownJournalEntry.set(req, entry);
1504
1785
  }
1505
1786
  });
1787
+ if (serverOptions.responsesTools === "extended") journal.configureFixtureViewSource(responsesToolsViewSource);
1506
1788
  const videoStates = new VideoStateMap();
1507
1789
  const openRouterVideoJobs = new OpenRouterVideoJobMap();
1508
1790
  const veoVideoJobs = new VeoVideoJobMap();
@@ -1517,6 +1799,85 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1517
1799
  return status >= 400 && status < 500 ? "invalid_request_error" : "server_error";
1518
1800
  }
1519
1801
  /**
1802
+ * Envelope for a fixture tool-call error (see isFixtureToolCallError: invalid
1803
+ * JSON arguments on an object wire, a custom tool call on a non-Responses
1804
+ * wire, or a malformed fixture tool call). It is the provider envelope aimock
1805
+ * emits for that route, with the aimock `code` in it: Google's
1806
+ * `google.rpc.ErrorInfo` detail on Gemini and Vertex AI, a string
1807
+ * `error.code` on Gemini Interactions and Anthropic Messages, a `reason`
1808
+ * member on Bedrock (a `code` member would replace `__type` as the AWS SDK's
1809
+ * error name), and a sibling `code` on Ollama's bare string error. Every
1810
+ * other route, Cohere included, gets the OpenAI-style
1811
+ * `{error: {message, type, code}}`. OpenRouter routes pass their own
1812
+ * `toolCallEnvelope` instead of using this one.
1813
+ */
1814
+ function invalidToolArgumentsEnvelope(pathname, message, code) {
1815
+ if (pathname === OLLAMA_CHAT_PATH) return JSON.stringify({
1816
+ error: message,
1817
+ code
1818
+ });
1819
+ if (pathname === GEMINI_INTERACTIONS_PATH) return JSON.stringify({ error: {
1820
+ code,
1821
+ message
1822
+ } });
1823
+ if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return JSON.stringify({ error: {
1824
+ code: 500,
1825
+ message,
1826
+ status: "INTERNAL",
1827
+ details: googleFixtureToolCallErrorDetails(code)
1828
+ } });
1829
+ if (pathname === MESSAGES_PATH) return JSON.stringify({
1830
+ type: "error",
1831
+ error: {
1832
+ type: "api_error",
1833
+ code,
1834
+ message
1835
+ }
1836
+ });
1837
+ if (BEDROCK_INVOKE_RE.test(pathname) || BEDROCK_STREAM_RE.test(pathname) || BEDROCK_CONVERSE_RE.test(pathname) || BEDROCK_CONVERSE_STREAM_RE.test(pathname)) return JSON.stringify({
1838
+ __type: "InternalServerException",
1839
+ message,
1840
+ reason: code
1841
+ });
1842
+ return JSON.stringify({ error: {
1843
+ message,
1844
+ type: "server_error",
1845
+ code
1846
+ } });
1847
+ }
1848
+ function invalidMisbehaviorHeaderEnvelope(pathname, detail) {
1849
+ const code = "aimock_misbehavior_invalid";
1850
+ const message = `${code}: ${detail}`;
1851
+ if (pathname === OLLAMA_CHAT_PATH || pathname === OLLAMA_GENERATE_PATH) return JSON.stringify({ error: message });
1852
+ if (pathname === COHERE_CHAT_PATH) return JSON.stringify({ message });
1853
+ if (pathname === GEMINI_INTERACTIONS_PATH) return JSON.stringify({ error: {
1854
+ code,
1855
+ message
1856
+ } });
1857
+ if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return JSON.stringify({ error: {
1858
+ code: 400,
1859
+ message,
1860
+ status: "INVALID_ARGUMENT"
1861
+ } });
1862
+ if (pathname === MESSAGES_PATH) return JSON.stringify({
1863
+ type: "error",
1864
+ error: {
1865
+ type: "invalid_request_error",
1866
+ code,
1867
+ message
1868
+ }
1869
+ });
1870
+ if (BEDROCK_INVOKE_RE.test(pathname) || BEDROCK_STREAM_RE.test(pathname) || BEDROCK_CONVERSE_RE.test(pathname) || BEDROCK_CONVERSE_STREAM_RE.test(pathname)) return JSON.stringify({
1871
+ __type: "ValidationException",
1872
+ message
1873
+ });
1874
+ return JSON.stringify({ error: {
1875
+ message,
1876
+ type: "invalid_request_error",
1877
+ code
1878
+ } });
1879
+ }
1880
+ /**
1520
1881
  * The newest journal entry this request already produced, or null.
1521
1882
  *
1522
1883
  * Attribution is by the identity of the `IncomingMessage` the entry was
@@ -1567,6 +1928,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1567
1928
  function routeError(req, res, err, pathname, opts) {
1568
1929
  const route = `${req.method ?? "?"} ${pathname}`;
1569
1930
  const msg = err instanceof Error ? err.message : "Internal error";
1931
+ const invalidToolArguments = isFixtureToolCallError(err);
1570
1932
  const clientFault = err instanceof RequestBodyTooLargeError;
1571
1933
  const clientAbort = clientAbortedMidBody(req, res, err);
1572
1934
  const status = clientFault ? 400 : 500;
@@ -1607,8 +1969,10 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1607
1969
  return;
1608
1970
  }
1609
1971
  if (!res.headersSent) {
1610
- if (existing) existing.response.status = status;
1611
- else try {
1972
+ if (existing) {
1973
+ existing.response.status = status;
1974
+ if (invalidToolArguments) existing.response.error = msg;
1975
+ } else try {
1612
1976
  journal.add({
1613
1977
  method: req.method ?? "?",
1614
1978
  path: req.url ?? "?",
@@ -1618,14 +1982,15 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1618
1982
  response: {
1619
1983
  status,
1620
1984
  fixture: null,
1621
- source: "internal"
1985
+ source: "internal",
1986
+ ...invalidToolArguments ? { error: msg } : {}
1622
1987
  }
1623
1988
  });
1624
1989
  } catch (jErr) {
1625
1990
  logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1626
1991
  }
1627
1992
  setCorsHeaders(res);
1628
- writeErrorResponse(res, status, opts?.envelope ? opts.envelope(msg, status) : JSON.stringify({ error: {
1993
+ writeErrorResponse(res, status, invalidToolArguments ? (opts?.toolCallEnvelope ?? ((m, c) => invalidToolArgumentsEnvelope(pathname, m, c)))(msg, fixtureToolCallErrorCode(err)) : opts?.envelope ? opts.envelope(msg, status) : JSON.stringify({ error: {
1629
1994
  message: msg,
1630
1995
  type: errorTypeForStatus(status)
1631
1996
  } }));
@@ -1674,13 +2039,17 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1674
2039
  if (handler.setJournal) handler.setJournal(journal);
1675
2040
  if (registry && handler.setRegistry) handler.setRegistry(registry);
1676
2041
  if (handler.setLogger) handler.setLogger(logger);
2042
+ if (handler instanceof MCPMock) handler.setReplaySpeed(defaults.replaySpeed);
1677
2043
  }
1678
2044
  if (registry) registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
1679
2045
  const server = http$1.createServer((req, res) => {
1680
2046
  requestScope.run(req, () => {
1681
- handleHttpRequest(req, res).catch((err) => {
2047
+ runWithToolArgumentsScope({
2048
+ strict: defaults.strictToolArguments === true,
2049
+ logger
2050
+ }, () => handleHttpRequest(req, res).catch((err) => {
1682
2051
  routeError(req, res, err, req.url ?? "?");
1683
- });
2052
+ }));
1684
2053
  });
1685
2054
  });
1686
2055
  async function handleHttpRequest(req, res) {
@@ -1763,16 +2132,40 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1763
2132
  markAuthenticatedRequest(req, resolvedAuth.policy);
1764
2133
  }
1765
2134
  if (pathname.startsWith(CONTROL_PREFIX)) {
2135
+ ensureRawFixturePositions();
1766
2136
  await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mountList);
1767
2137
  return;
1768
2138
  }
2139
+ const misbehaviorHeader = defaults.misbehavior ? parseMisbehaviorHeader(req.headers["x-aimock-misbehavior"]) : void 0;
2140
+ if (misbehaviorHeader && !misbehaviorHeader.ok) {
2141
+ const detail = misbehaviorHeader.issue.message;
2142
+ const message = `aimock_misbehavior_invalid: ${detail}`;
2143
+ logger.error(`${req.method ?? "?"} ${pathname}: ${message}`);
2144
+ journal.add({
2145
+ method: req.method ?? "?",
2146
+ path: req.url ?? pathname,
2147
+ headers: flattenHeaders(req.headers),
2148
+ body: null,
2149
+ response: {
2150
+ status: 400,
2151
+ fixture: null,
2152
+ source: "internal",
2153
+ error: message
2154
+ }
2155
+ });
2156
+ setCorsHeaders(res);
2157
+ writeErrorResponse(res, 400, invalidMisbehaviorHeaderEnvelope(normalizeCompatPath(pathname), detail));
2158
+ return;
2159
+ }
1769
2160
  for (const { path: mountPath, handler } of mountList) if (pathname === mountPath || pathname.startsWith(mountPath + "/")) {
1770
2161
  const subPath = pathname.slice(mountPath.length) || "/";
1771
2162
  if (await handler.handleRequest(req, res, subPath)) return;
1772
2163
  }
1773
2164
  if (pathname === OLLAMA_CHAT_PATH && req.method === "POST") {
1774
2165
  try {
1775
- await handleOllama(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2166
+ const raw = await readBody(req);
2167
+ ensureRawFixturePositions();
2168
+ await handleOllama(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
1776
2169
  } catch (err) {
1777
2170
  routeError(req, res, err, pathname);
1778
2171
  }
@@ -1780,7 +2173,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1780
2173
  }
1781
2174
  if (pathname === OLLAMA_GENERATE_PATH && req.method === "POST") {
1782
2175
  try {
1783
- await handleOllamaGenerate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2176
+ const raw = await readBody(req);
2177
+ ensureRawFixturePositions();
2178
+ await handleOllamaGenerate(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
1784
2179
  } catch (err) {
1785
2180
  routeError(req, res, err, pathname);
1786
2181
  }
@@ -1788,7 +2183,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1788
2183
  }
1789
2184
  if ((pathname === OLLAMA_EMBEDDINGS_PATH || pathname === OLLAMA_EMBED_PATH) && req.method === "POST") {
1790
2185
  try {
1791
- await handleOllamaEmbeddings(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2186
+ const raw = await readBody(req);
2187
+ ensureRawFixturePositions();
2188
+ await handleOllamaEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
1792
2189
  } catch (err) {
1793
2190
  routeError(req, res, err, pathname);
1794
2191
  }
@@ -1821,6 +2218,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1821
2218
  }
1822
2219
  if (pathname === OPENROUTER_VIDEO_MODELS_PATH && req.method === "GET") {
1823
2220
  try {
2221
+ ensureRawFixturePositions();
1824
2222
  await handleOpenRouterVideoModels(req, res, fixtures, journal, defaults, setCorsHeaders);
1825
2223
  } catch (err) {
1826
2224
  routeError(req, res, err, pathname);
@@ -1830,6 +2228,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1830
2228
  const openRouterVideoStatusMatch = pathname.match(OPENROUTER_VIDEO_STATUS_RE);
1831
2229
  if (openRouterVideoStatusMatch && req.method === "GET") {
1832
2230
  try {
2231
+ ensureRawFixturePositions();
1833
2232
  await handleOpenRouterVideoStatus(req, res, openRouterVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
1834
2233
  } catch (err) {
1835
2234
  routeError(req, res, err, pathname);
@@ -1839,7 +2238,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1839
2238
  if (pathname === OPENROUTER_VIDEOS_PATH && req.method === "POST") {
1840
2239
  setCorsHeaders(res);
1841
2240
  try {
1842
- await handleOpenRouterVideoCreate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
2241
+ const raw = await readBody(req);
2242
+ ensureRawFixturePositions();
2243
+ await handleOpenRouterVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
1843
2244
  } catch (err) {
1844
2245
  routeError(req, res, err, pathname);
1845
2246
  }
@@ -1848,6 +2249,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1848
2249
  const bytePlusVideoStatusMatch = pathname.match(BYTEPLUS_VIDEO_STATUS_RE);
1849
2250
  if (bytePlusVideoStatusMatch && req.method === "GET") {
1850
2251
  try {
2252
+ ensureRawFixturePositions();
1851
2253
  await handleBytePlusVideoStatus(req, res, bytePlusVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
1852
2254
  } catch (err) {
1853
2255
  routeError(req, res, err, pathname);
@@ -1857,13 +2259,16 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1857
2259
  if (BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname) && req.method === "POST") {
1858
2260
  setCorsHeaders(res);
1859
2261
  try {
1860
- await handleBytePlusVideoCreate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
2262
+ const raw = await readBody(req);
2263
+ ensureRawFixturePositions();
2264
+ await handleBytePlusVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
1861
2265
  } catch (err) {
1862
2266
  routeError(req, res, err, pathname);
1863
2267
  }
1864
2268
  return;
1865
2269
  }
1866
2270
  if (pathname === OPENROUTER_MODELS_PATH && req.method === "GET") {
2271
+ ensureRawFixturePositions();
1867
2272
  handleOpenRouterModels(req, res, fixtures, journal, defaults, setCorsHeaders);
1868
2273
  return;
1869
2274
  }
@@ -2202,7 +2607,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2202
2607
  }
2203
2608
  const entries = journal.getAll(opts);
2204
2609
  res.writeHead(200, { "Content-Type": "application/json" });
2205
- res.end(JSON.stringify(entries));
2610
+ res.end(journalJson(entries, journal));
2206
2611
  return;
2207
2612
  }
2208
2613
  if (req.method === "DELETE") {
@@ -2216,7 +2621,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2216
2621
  }
2217
2622
  if (pathname === RESPONSES_PATH && req.method === "POST") {
2218
2623
  try {
2219
- await handleResponses(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2624
+ const raw = await readBody(req);
2625
+ ensureRawFixturePositions();
2626
+ await handleResponses(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2220
2627
  } catch (err) {
2221
2628
  routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
2222
2629
  }
@@ -2224,7 +2631,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2224
2631
  }
2225
2632
  if (pathname === MESSAGES_PATH && req.method === "POST") {
2226
2633
  try {
2227
- await handleMessages(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2634
+ const raw = await readBody(req);
2635
+ ensureRawFixturePositions();
2636
+ await handleMessages(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2228
2637
  } catch (err) {
2229
2638
  routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
2230
2639
  }
@@ -2232,7 +2641,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2232
2641
  }
2233
2642
  if (pathname === COHERE_CHAT_PATH && req.method === "POST") {
2234
2643
  try {
2235
- await handleCohere(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2644
+ const raw = await readBody(req);
2645
+ ensureRawFixturePositions();
2646
+ await handleCohere(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2236
2647
  } catch (err) {
2237
2648
  routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
2238
2649
  }
@@ -2240,7 +2651,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2240
2651
  }
2241
2652
  if (pathname === COHERE_EMBED_PATH && req.method === "POST") {
2242
2653
  try {
2243
- await handleCohereEmbed(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2654
+ const raw = await readBody(req);
2655
+ ensureRawFixturePositions();
2656
+ await handleCohereEmbed(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2244
2657
  } catch (err) {
2245
2658
  routeError(req, res, err, pathname);
2246
2659
  }
@@ -2260,6 +2673,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2260
2673
  } catch (err) {
2261
2674
  if (!(err instanceof SyntaxError)) defaults.logger.error(`Unexpected error in Azure model injection: ${err instanceof Error ? err.message : String(err)}`);
2262
2675
  }
2676
+ ensureRawFixturePositions();
2263
2677
  await handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider, originalPathname === EMBEDDINGS_PATH);
2264
2678
  } catch (err) {
2265
2679
  routeError(req, res, err, pathname);
@@ -2268,7 +2682,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2268
2682
  }
2269
2683
  if (pathname === IMAGES_PATH && req.method === "POST") {
2270
2684
  try {
2271
- await handleImages(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, "openai", void 0, isBytePlusArk ? "byteplus" : void 0);
2685
+ const raw = await readBody(req);
2686
+ ensureRawFixturePositions();
2687
+ await handleImages(req, res, raw, fixtures, journal, defaults, setCorsHeaders, "openai", void 0, isBytePlusArk ? "byteplus" : void 0);
2272
2688
  } catch (err) {
2273
2689
  routeError(req, res, err, pathname);
2274
2690
  }
@@ -2276,7 +2692,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2276
2692
  }
2277
2693
  if (pathname === IMAGES_EDIT_PATH && req.method === "POST") {
2278
2694
  try {
2279
- await handleImageEdit(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2695
+ const raw = await readBody(req);
2696
+ ensureRawFixturePositions();
2697
+ await handleImageEdit(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2280
2698
  } catch (err) {
2281
2699
  routeError(req, res, err, pathname);
2282
2700
  }
@@ -2284,7 +2702,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2284
2702
  }
2285
2703
  if (pathname === IMAGES_VARIATIONS_PATH && req.method === "POST") {
2286
2704
  try {
2287
- await handleImageVariations(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2705
+ const raw = await readBody(req);
2706
+ ensureRawFixturePositions();
2707
+ await handleImageVariations(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2288
2708
  } catch (err) {
2289
2709
  routeError(req, res, err, pathname);
2290
2710
  }
@@ -2292,7 +2712,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2292
2712
  }
2293
2713
  if (pathname === SPEECH_PATH && req.method === "POST") {
2294
2714
  try {
2295
- await handleSpeech(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2715
+ const raw = await readBody(req);
2716
+ ensureRawFixturePositions();
2717
+ await handleSpeech(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2296
2718
  } catch (err) {
2297
2719
  routeError(req, res, err, pathname);
2298
2720
  }
@@ -2300,7 +2722,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2300
2722
  }
2301
2723
  if (pathname === TRANSCRIPTIONS_PATH && req.method === "POST") {
2302
2724
  try {
2303
- await handleTranscription(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2725
+ const raw = await readBody(req);
2726
+ ensureRawFixturePositions();
2727
+ await handleTranscription(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2304
2728
  } catch (err) {
2305
2729
  routeError(req, res, err, pathname);
2306
2730
  }
@@ -2308,7 +2732,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2308
2732
  }
2309
2733
  if (pathname === TRANSLATIONS_PATH && req.method === "POST") {
2310
2734
  try {
2311
- await handleTranscription(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, "translation");
2735
+ const raw = await readBody(req);
2736
+ ensureRawFixturePositions();
2737
+ await handleTranscription(req, res, raw, fixtures, journal, defaults, setCorsHeaders, "translation");
2312
2738
  } catch (err) {
2313
2739
  routeError(req, res, err, pathname);
2314
2740
  }
@@ -2317,7 +2743,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2317
2743
  if (pathname === GROK_VIDEO_SUBMIT_PATH && req.method === "POST") {
2318
2744
  setCorsHeaders(res);
2319
2745
  try {
2320
- await handleGrokVideoCreate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, grokVideoJobs);
2746
+ const raw = await readBody(req);
2747
+ ensureRawFixturePositions();
2748
+ await handleGrokVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, grokVideoJobs);
2321
2749
  } catch (err) {
2322
2750
  routeError(req, res, err, pathname);
2323
2751
  }
@@ -2325,7 +2753,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2325
2753
  }
2326
2754
  if (pathname === VIDEOS_PATH && req.method === "POST") {
2327
2755
  try {
2328
- await handleVideoCreate(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, videoStates);
2756
+ const raw = await readBody(req);
2757
+ ensureRawFixturePositions();
2758
+ await handleVideoCreate(req, res, raw, fixtures, journal, defaults, setCorsHeaders, videoStates);
2329
2759
  } catch (err) {
2330
2760
  routeError(req, res, err, pathname);
2331
2761
  }
@@ -2334,6 +2764,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2334
2764
  const grokVideoStatusMatch = pathname.match(GROK_VIDEO_STATUS_RE);
2335
2765
  if (grokVideoStatusMatch && grokVideoStatusMatch[1] !== "generations" && req.method === "GET") {
2336
2766
  try {
2767
+ ensureRawFixturePositions();
2337
2768
  await handleGrokVideoStatus(req, res, grokVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, grokVideoJobs, videoStates);
2338
2769
  } catch (err) {
2339
2770
  routeError(req, res, err, pathname);
@@ -2344,7 +2775,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2344
2775
  if (veoPredictMatch && req.method === "POST") {
2345
2776
  setCorsHeaders(res);
2346
2777
  try {
2347
- await handleVeoVideoCreate(req, res, await readBody(req), veoPredictMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
2778
+ const raw = await readBody(req);
2779
+ ensureRawFixturePositions();
2780
+ await handleVeoVideoCreate(req, res, raw, veoPredictMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
2348
2781
  } catch (err) {
2349
2782
  routeError(req, res, err, pathname);
2350
2783
  }
@@ -2353,6 +2786,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2353
2786
  const veoOperationMatch = pathname.match(VEO_OPERATION_RE);
2354
2787
  if (veoOperationMatch && req.method === "GET") {
2355
2788
  try {
2789
+ ensureRawFixturePositions();
2356
2790
  await handleVeoVideoStatus(req, res, veoOperationMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
2357
2791
  } catch (err) {
2358
2792
  routeError(req, res, err, pathname);
@@ -2363,7 +2797,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2363
2797
  if (geminiPredictMatch && req.method === "POST") {
2364
2798
  const predictModel = geminiPredictMatch[1];
2365
2799
  try {
2366
- await handleImages(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders, "gemini", predictModel);
2800
+ const raw = await readBody(req);
2801
+ ensureRawFixturePositions();
2802
+ await handleImages(req, res, raw, fixtures, journal, defaults, setCorsHeaders, "gemini", predictModel);
2367
2803
  } catch (err) {
2368
2804
  routeError(req, res, err, pathname);
2369
2805
  }
@@ -2371,7 +2807,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2371
2807
  }
2372
2808
  if (pathname === GEMINI_INTERACTIONS_PATH && req.method === "POST") {
2373
2809
  try {
2374
- await handleGeminiInteractions(req, res, await readBody(req), fixtures, journal, defaults, setCorsHeaders);
2810
+ const raw = await readBody(req);
2811
+ ensureRawFixturePositions();
2812
+ await handleGeminiInteractions(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2375
2813
  } catch (err) {
2376
2814
  routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
2377
2815
  }
@@ -2381,7 +2819,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2381
2819
  if (geminiEmbedMatch && req.method === "POST") {
2382
2820
  const embedModel = geminiEmbedMatch[1];
2383
2821
  try {
2384
- await handleGeminiEmbedContent(req, res, await readBody(req), embedModel, fixtures, journal, defaults, setCorsHeaders);
2822
+ const raw = await readBody(req);
2823
+ ensureRawFixturePositions();
2824
+ await handleGeminiEmbedContent(req, res, raw, embedModel, fixtures, journal, defaults, setCorsHeaders);
2385
2825
  } catch (err) {
2386
2826
  routeError(req, res, err, pathname);
2387
2827
  }
@@ -2392,7 +2832,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2392
2832
  const geminiModel = geminiMatch[1];
2393
2833
  const streaming = geminiMatch[2] === "streamGenerateContent";
2394
2834
  try {
2395
- await handleGemini(req, res, await readBody(req), geminiModel, streaming, fixtures, journal, defaults, setCorsHeaders);
2835
+ const raw = await readBody(req);
2836
+ ensureRawFixturePositions();
2837
+ await handleGemini(req, res, raw, geminiModel, streaming, fixtures, journal, defaults, setCorsHeaders);
2396
2838
  } catch (err) {
2397
2839
  routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
2398
2840
  }
@@ -2403,7 +2845,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2403
2845
  const vertexModel = vertexMatch[1];
2404
2846
  const streaming = vertexMatch[2] === "streamGenerateContent";
2405
2847
  try {
2406
- await handleGemini(req, res, await readBody(req), vertexModel, streaming, fixtures, journal, defaults, setCorsHeaders, "vertexai");
2848
+ const raw = await readBody(req);
2849
+ ensureRawFixturePositions();
2850
+ await handleGemini(req, res, raw, vertexModel, streaming, fixtures, journal, defaults, setCorsHeaders, "vertexai");
2407
2851
  } catch (err) {
2408
2852
  routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
2409
2853
  }
@@ -2413,7 +2857,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2413
2857
  if (bedrockMatch && req.method === "POST") {
2414
2858
  const bedrockModelId = bedrockMatch[1];
2415
2859
  try {
2416
- await handleBedrock(req, res, await readBody(req), bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
2860
+ const raw = await readBody(req);
2861
+ ensureRawFixturePositions();
2862
+ await handleBedrock(req, res, raw, bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
2417
2863
  } catch (err) {
2418
2864
  routeError(req, res, err, pathname);
2419
2865
  }
@@ -2423,7 +2869,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2423
2869
  if (bedrockStreamMatch && req.method === "POST") {
2424
2870
  const bedrockModelId = bedrockStreamMatch[1];
2425
2871
  try {
2426
- await handleBedrockStream(req, res, await readBody(req), bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
2872
+ const raw = await readBody(req);
2873
+ ensureRawFixturePositions();
2874
+ await handleBedrockStream(req, res, raw, bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
2427
2875
  } catch (err) {
2428
2876
  routeError(req, res, err, pathname);
2429
2877
  }
@@ -2433,7 +2881,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2433
2881
  if (converseMatch && req.method === "POST") {
2434
2882
  const converseModelId = converseMatch[1];
2435
2883
  try {
2436
- await handleConverse(req, res, await readBody(req), converseModelId, fixtures, journal, defaults, setCorsHeaders);
2884
+ const raw = await readBody(req);
2885
+ ensureRawFixturePositions();
2886
+ await handleConverse(req, res, raw, converseModelId, fixtures, journal, defaults, setCorsHeaders);
2437
2887
  } catch (err) {
2438
2888
  routeError(req, res, err, pathname);
2439
2889
  }
@@ -2443,7 +2893,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2443
2893
  if (converseStreamMatch && req.method === "POST") {
2444
2894
  const converseStreamModelId = converseStreamMatch[1];
2445
2895
  try {
2446
- await handleConverseStream(req, res, await readBody(req), converseStreamModelId, fixtures, journal, defaults, setCorsHeaders);
2896
+ const raw = await readBody(req);
2897
+ ensureRawFixturePositions();
2898
+ await handleConverseStream(req, res, raw, converseStreamModelId, fixtures, journal, defaults, setCorsHeaders);
2447
2899
  } catch (err) {
2448
2900
  routeError(req, res, err, pathname);
2449
2901
  }
@@ -2476,7 +2928,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2476
2928
  if (pathname === ELEVENLABS_SOUND_GENERATION_PATH && req.method === "POST") {
2477
2929
  setCorsHeaders(res);
2478
2930
  try {
2479
- await handleElevenLabsAudio(req, res, await readBody(req), fixtures, defaults, journal, "sound-generation");
2931
+ const raw = await readBody(req);
2932
+ ensureRawFixturePositions();
2933
+ await handleElevenLabsAudio(req, res, raw, fixtures, defaults, journal, "sound-generation");
2480
2934
  } catch (err) {
2481
2935
  routeError(req, res, err, pathname);
2482
2936
  }
@@ -2485,7 +2939,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2485
2939
  if (pathname === ELEVENLABS_VOICE_DESIGN_PATH && req.method === "POST") {
2486
2940
  setCorsHeaders(res);
2487
2941
  try {
2488
- await handleElevenLabsVoiceDesign(req, res, await readBody(req), fixtures, defaults, journal);
2942
+ const raw = await readBody(req);
2943
+ ensureRawFixturePositions();
2944
+ await handleElevenLabsVoiceDesign(req, res, raw, fixtures, defaults, journal);
2489
2945
  } catch (err) {
2490
2946
  routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2491
2947
  }
@@ -2494,7 +2950,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2494
2950
  if (pathname === ELEVENLABS_VOICE_CREATE_PATH && req.method === "POST") {
2495
2951
  setCorsHeaders(res);
2496
2952
  try {
2497
- await handleElevenLabsVoiceCreate(req, res, await readBody(req), fixtures, defaults, journal);
2953
+ const raw = await readBody(req);
2954
+ ensureRawFixturePositions();
2955
+ await handleElevenLabsVoiceCreate(req, res, raw, fixtures, defaults, journal);
2498
2956
  } catch (err) {
2499
2957
  routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2500
2958
  }
@@ -2525,8 +2983,13 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2525
2983
  return;
2526
2984
  }
2527
2985
  try {
2528
- if (req.method === "GET") await handleElevenLabsVoiceGet(req, res, voiceId, fixtures, defaults, journal);
2529
- else await handleElevenLabsVoiceDelete(req, res, voiceId, fixtures, defaults, journal);
2986
+ if (req.method === "GET") {
2987
+ ensureRawFixturePositions();
2988
+ await handleElevenLabsVoiceGet(req, res, voiceId, fixtures, defaults, journal);
2989
+ } else {
2990
+ ensureRawFixturePositions();
2991
+ await handleElevenLabsVoiceDelete(req, res, voiceId, fixtures, defaults, journal);
2992
+ }
2530
2993
  } catch (err) {
2531
2994
  routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2532
2995
  }
@@ -2537,7 +3000,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2537
3000
  setCorsHeaders(res);
2538
3001
  const voiceId = elevenLabsTTSMatch[1];
2539
3002
  try {
2540
- await handleElevenLabsTTS(req, res, await readBody(req), fixtures, defaults, journal, voiceId);
3003
+ const raw = await readBody(req);
3004
+ ensureRawFixturePositions();
3005
+ await handleElevenLabsTTS(req, res, raw, fixtures, defaults, journal, voiceId);
2541
3006
  } catch (err) {
2542
3007
  routeError(req, res, err, pathname);
2543
3008
  }
@@ -2548,7 +3013,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2548
3013
  setCorsHeaders(res);
2549
3014
  const musicSubType = musicMatch[1] ?? "music";
2550
3015
  try {
2551
- await handleElevenLabsAudio(req, res, await readBody(req), fixtures, defaults, journal, musicSubType);
3016
+ const raw = await readBody(req);
3017
+ ensureRawFixturePositions();
3018
+ await handleElevenLabsAudio(req, res, raw, fixtures, defaults, journal, musicSubType);
2552
3019
  } catch (err) {
2553
3020
  routeError(req, res, err, pathname);
2554
3021
  }
@@ -2579,6 +3046,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2579
3046
  return;
2580
3047
  }
2581
3048
  }
3049
+ ensureRawFixturePositions();
2582
3050
  if (await handleFal(req, res, raw, pathname, fixtures, defaults, journal) === "handled") return;
2583
3051
  } catch (err) {
2584
3052
  routeError(req, res, err, pathname);
@@ -2588,7 +3056,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2588
3056
  if (pathname.match(FAL_QUEUE_SUBMIT_RE) && req.method === "POST") {
2589
3057
  setCorsHeaders(res);
2590
3058
  try {
2591
- await handleFalQueue(req, res, falBody ?? await readBody(req), pathname, fixtures, defaults, journal);
3059
+ const raw = falBody ?? await readBody(req);
3060
+ ensureRawFixturePositions();
3061
+ await handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
2592
3062
  } catch (err) {
2593
3063
  routeError(req, res, err, pathname);
2594
3064
  }
@@ -2615,6 +3085,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2615
3085
  }, "internal", defaults.registry, defaults.logger);
2616
3086
  return;
2617
3087
  }
3088
+ ensureRawFixturePositions();
2618
3089
  await handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
2619
3090
  } catch (err) {
2620
3091
  routeError(req, res, err, pathname);
@@ -2624,7 +3095,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2624
3095
  if (pathname.match(FAL_RUN_RE) && req.method === "POST") {
2625
3096
  setCorsHeaders(res);
2626
3097
  try {
2627
- await handleFalQueue(req, res, falBody ?? await readBody(req), pathname, fixtures, defaults, journal);
3098
+ const raw = falBody ?? await readBody(req);
3099
+ ensureRawFixturePositions();
3100
+ await handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
2628
3101
  } catch (err) {
2629
3102
  routeError(req, res, err, pathname);
2630
3103
  }
@@ -2642,13 +3115,14 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2642
3115
  }
2643
3116
  const completionsProvider = azureDeploymentId ? "azure" : isOpenRouter ? "openrouter" : isBytePlusArk ? "byteplus" : "openai";
2644
3117
  try {
2645
- await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider, isOpenRouter);
3118
+ await handleCompletions(req, res, fixtures, journal, defaults, () => lastJournalEntryFor(req), azureDeploymentId, completionsProvider, isOpenRouter, ensureRawFixturePositions, originalPathname.startsWith("/api/v3/"));
2646
3119
  } catch (err) {
2647
3120
  routeError(req, res, err, pathname, {
2648
3121
  envelope: (m, status) => isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
2649
3122
  message: m,
2650
3123
  type: errorTypeForStatus(status)
2651
3124
  } }),
3125
+ ...isOpenRouter ? { toolCallEnvelope: (m, code) => serializeOpenRouterError(500, m, { reason: code }) } : {},
2652
3126
  streamEvent: (m, status) => `data: ${isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
2653
3127
  message: m,
2654
3128
  type: errorTypeForStatus(status)
@@ -2736,6 +3210,25 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2736
3210
  socket.destroy();
2737
3211
  return;
2738
3212
  }
3213
+ if (defaults.misbehavior && pathname !== LIVE_PATH && req.headers["x-aimock-misbehavior"] !== void 0) {
3214
+ const code = "aimock_misbehavior_invalid";
3215
+ const message = "use the runtime scope: POST /__aimock/misbehavior with X-Test-Id";
3216
+ const body = JSON.stringify({ error: pathname === GEMINI_LIVE_PATH ? {
3217
+ code: 400,
3218
+ status: "INVALID_ARGUMENT",
3219
+ message: `${code}: ${message}`
3220
+ } : {
3221
+ type: "invalid_request_error",
3222
+ code,
3223
+ message
3224
+ } });
3225
+ socket.end(`HTTP/1.1 400 Bad Request\r
3226
+ Content-Type: application/json\r
3227
+ Content-Length: ${Buffer.byteLength(body)}\r\nConnection: close\r
3228
+ \r
3229
+ ` + body);
3230
+ return;
3231
+ }
2739
3232
  let liveId;
2740
3233
  if (pathname === LIVE_PATH) {
2741
3234
  if (req.method !== "GET" || parsedUrl.searchParams.has("model")) {
@@ -2797,7 +3290,8 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2797
3290
  ...defaults,
2798
3291
  model: "gpt-4",
2799
3292
  testId: wsTestId,
2800
- upgradeHeaders: req.headers
3293
+ upgradeHeaders: req.headers,
3294
+ beforeProcessMessage: ensureRawFixturePositions
2801
3295
  });
2802
3296
  else if (pathname === REALTIME_PATH) {
2803
3297
  const transcriptionIntent = parsedUrl.searchParams.get("intent") === "transcription";
@@ -2807,13 +3301,15 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2807
3301
  model,
2808
3302
  transcriptionIntent,
2809
3303
  testId: wsTestId,
2810
- upgradeHeaders: req.headers
3304
+ upgradeHeaders: req.headers,
3305
+ beforeProcessMessage: ensureRawFixturePositions
2811
3306
  });
2812
3307
  } else if (pathname === GEMINI_LIVE_PATH) handleWebSocketGeminiLive(ws, fixtures, journal, {
2813
3308
  ...defaults,
2814
3309
  model: "gemini-2.0-flash",
2815
3310
  testId: wsTestId,
2816
- upgradeHeaders: req.headers
3311
+ upgradeHeaders: req.headers,
3312
+ beforeProcessMessage: ensureRawFixturePositions
2817
3313
  });
2818
3314
  }
2819
3315
  const originalClose = server.close.bind(server);