@copilotkit/aimock 1.43.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 (396) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +46 -0
  4. package/README.md +14 -2
  5. package/dist/agui-handler.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 +344 -43
  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 +345 -44
  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 +9 -5
  25. package/dist/chaos.cjs.map +1 -1
  26. package/dist/chaos.d.cts.map +1 -1
  27. package/dist/chaos.d.ts.map +1 -1
  28. package/dist/chaos.js +10 -7
  29. package/dist/chaos.js.map +1 -1
  30. package/dist/cli.cjs +238 -9
  31. package/dist/cli.cjs.map +1 -1
  32. package/dist/cli.js +242 -13
  33. package/dist/cli.js.map +1 -1
  34. package/dist/cohere.cjs +159 -72
  35. package/dist/cohere.cjs.map +1 -1
  36. package/dist/cohere.d.cts.map +1 -1
  37. package/dist/cohere.d.ts.map +1 -1
  38. package/dist/cohere.js +160 -73
  39. package/dist/cohere.js.map +1 -1
  40. package/dist/config-loader.cjs +63 -5
  41. package/dist/config-loader.cjs.map +1 -1
  42. package/dist/config-loader.d.cts +37 -4
  43. package/dist/config-loader.d.cts.map +1 -1
  44. package/dist/config-loader.d.ts +37 -4
  45. package/dist/config-loader.d.ts.map +1 -1
  46. package/dist/config-loader.js +63 -5
  47. package/dist/config-loader.js.map +1 -1
  48. package/dist/constants.cjs +8 -0
  49. package/dist/constants.cjs.map +1 -1
  50. package/dist/constants.d.cts +6 -2
  51. package/dist/constants.d.ts +6 -2
  52. package/dist/constants.js +8 -1
  53. package/dist/constants.js.map +1 -1
  54. package/dist/echo-text.cjs +65 -0
  55. package/dist/echo-text.cjs.map +1 -0
  56. package/dist/echo-text.d.cts +22 -0
  57. package/dist/echo-text.d.cts.map +1 -0
  58. package/dist/echo-text.d.ts +22 -0
  59. package/dist/echo-text.d.ts.map +1 -0
  60. package/dist/echo-text.js +65 -0
  61. package/dist/echo-text.js.map +1 -0
  62. package/dist/embeddings.cjs +18 -1
  63. package/dist/embeddings.cjs.map +1 -1
  64. package/dist/embeddings.d.cts +1 -1
  65. package/dist/embeddings.d.cts.map +1 -1
  66. package/dist/embeddings.d.ts +1 -1
  67. package/dist/embeddings.d.ts.map +1 -1
  68. package/dist/embeddings.js +18 -1
  69. package/dist/embeddings.js.map +1 -1
  70. package/dist/fakes-plugin.cjs +131 -0
  71. package/dist/fakes-plugin.cjs.map +1 -0
  72. package/dist/fakes-plugin.d.cts +23 -0
  73. package/dist/fakes-plugin.d.cts.map +1 -0
  74. package/dist/fakes-plugin.d.ts +23 -0
  75. package/dist/fakes-plugin.d.ts.map +1 -0
  76. package/dist/fakes-plugin.js +127 -0
  77. package/dist/fakes-plugin.js.map +1 -0
  78. package/dist/files.cjs +9 -11
  79. package/dist/files.cjs.map +1 -1
  80. package/dist/files.d.cts.map +1 -1
  81. package/dist/files.d.ts.map +1 -1
  82. package/dist/files.js +10 -13
  83. package/dist/files.js.map +1 -1
  84. package/dist/fine-tuning.cjs +1 -19
  85. package/dist/fine-tuning.cjs.map +1 -1
  86. package/dist/fine-tuning.d.cts.map +1 -1
  87. package/dist/fine-tuning.d.ts.map +1 -1
  88. package/dist/fine-tuning.js +2 -20
  89. package/dist/fine-tuning.js.map +1 -1
  90. package/dist/fixture-loader-services.cjs +116 -0
  91. package/dist/fixture-loader-services.cjs.map +1 -0
  92. package/dist/fixture-loader-services.d.cts +48 -0
  93. package/dist/fixture-loader-services.d.cts.map +1 -0
  94. package/dist/fixture-loader-services.d.ts +48 -0
  95. package/dist/fixture-loader-services.d.ts.map +1 -0
  96. package/dist/fixture-loader-services.js +114 -0
  97. package/dist/fixture-loader-services.js.map +1 -0
  98. package/dist/fixture-loader.cjs +552 -29
  99. package/dist/fixture-loader.cjs.map +1 -1
  100. package/dist/fixture-loader.d.cts +140 -2
  101. package/dist/fixture-loader.d.cts.map +1 -1
  102. package/dist/fixture-loader.d.ts +140 -2
  103. package/dist/fixture-loader.d.ts.map +1 -1
  104. package/dist/fixture-loader.js +542 -31
  105. package/dist/fixture-loader.js.map +1 -1
  106. package/dist/gemini-embeddings.cjs +42 -2
  107. package/dist/gemini-embeddings.cjs.map +1 -1
  108. package/dist/gemini-embeddings.js +43 -3
  109. package/dist/gemini-embeddings.js.map +1 -1
  110. package/dist/gemini-interactions.cjs +288 -52
  111. package/dist/gemini-interactions.cjs.map +1 -1
  112. package/dist/gemini-interactions.d.cts.map +1 -1
  113. package/dist/gemini-interactions.d.ts.map +1 -1
  114. package/dist/gemini-interactions.js +289 -53
  115. package/dist/gemini-interactions.js.map +1 -1
  116. package/dist/gemini.cjs +234 -38
  117. package/dist/gemini.cjs.map +1 -1
  118. package/dist/gemini.d.cts.map +1 -1
  119. package/dist/gemini.d.ts.map +1 -1
  120. package/dist/gemini.js +235 -39
  121. package/dist/gemini.js.map +1 -1
  122. package/dist/helpers.cjs +1157 -12
  123. package/dist/helpers.cjs.map +1 -1
  124. package/dist/helpers.d.cts +85 -4
  125. package/dist/helpers.d.cts.map +1 -1
  126. package/dist/helpers.d.ts +85 -4
  127. package/dist/helpers.d.ts.map +1 -1
  128. package/dist/helpers.js +1113 -14
  129. package/dist/helpers.js.map +1 -1
  130. package/dist/images.cjs +3 -2
  131. package/dist/images.cjs.map +1 -1
  132. package/dist/images.js +3 -2
  133. package/dist/images.js.map +1 -1
  134. package/dist/index.cjs +39 -1
  135. package/dist/index.d.cts +11 -4
  136. package/dist/index.d.ts +11 -4
  137. package/dist/index.js +11 -4
  138. package/dist/jest.cjs +43 -14
  139. package/dist/jest.cjs.map +1 -1
  140. package/dist/jest.d.cts +47 -5
  141. package/dist/jest.d.cts.map +1 -1
  142. package/dist/jest.d.ts +47 -5
  143. package/dist/jest.d.ts.map +1 -1
  144. package/dist/jest.js +45 -16
  145. package/dist/jest.js.map +1 -1
  146. package/dist/journal.cjs +155 -13
  147. package/dist/journal.cjs.map +1 -1
  148. package/dist/journal.d.cts +54 -4
  149. package/dist/journal.d.cts.map +1 -1
  150. package/dist/journal.d.ts +54 -4
  151. package/dist/journal.d.ts.map +1 -1
  152. package/dist/journal.js +155 -13
  153. package/dist/journal.js.map +1 -1
  154. package/dist/live-sanitize.cjs +4 -32
  155. package/dist/live-sanitize.cjs.map +1 -1
  156. package/dist/live-sanitize.js +4 -32
  157. package/dist/live-sanitize.js.map +1 -1
  158. package/dist/llmock.cjs +204 -39
  159. package/dist/llmock.cjs.map +1 -1
  160. package/dist/llmock.d.cts +51 -1
  161. package/dist/llmock.d.cts.map +1 -1
  162. package/dist/llmock.d.ts +51 -1
  163. package/dist/llmock.d.ts.map +1 -1
  164. package/dist/llmock.js +206 -41
  165. package/dist/llmock.js.map +1 -1
  166. package/dist/mcp-fakes-mount.cjs +217 -0
  167. package/dist/mcp-fakes-mount.cjs.map +1 -0
  168. package/dist/mcp-fakes-mount.js +211 -0
  169. package/dist/mcp-fakes-mount.js.map +1 -0
  170. package/dist/mcp-fakes-report.cjs +111 -0
  171. package/dist/mcp-fakes-report.cjs.map +1 -0
  172. package/dist/mcp-fakes-report.d.cts +40 -0
  173. package/dist/mcp-fakes-report.d.cts.map +1 -0
  174. package/dist/mcp-fakes-report.d.ts +40 -0
  175. package/dist/mcp-fakes-report.d.ts.map +1 -0
  176. package/dist/mcp-fakes-report.js +107 -0
  177. package/dist/mcp-fakes-report.js.map +1 -0
  178. package/dist/mcp-fakes.cjs +1942 -0
  179. package/dist/mcp-fakes.cjs.map +1 -0
  180. package/dist/mcp-fakes.d.cts +504 -0
  181. package/dist/mcp-fakes.d.cts.map +1 -0
  182. package/dist/mcp-fakes.d.ts +504 -0
  183. package/dist/mcp-fakes.d.ts.map +1 -0
  184. package/dist/mcp-fakes.js +1937 -0
  185. package/dist/mcp-fakes.js.map +1 -0
  186. package/dist/mcp-handler.cjs +534 -27
  187. package/dist/mcp-handler.cjs.map +1 -1
  188. package/dist/mcp-handler.d.cts +49 -0
  189. package/dist/mcp-handler.d.cts.map +1 -0
  190. package/dist/mcp-handler.d.ts +49 -0
  191. package/dist/mcp-handler.d.ts.map +1 -0
  192. package/dist/mcp-handler.js +530 -28
  193. package/dist/mcp-handler.js.map +1 -1
  194. package/dist/mcp-mock.cjs +326 -47
  195. package/dist/mcp-mock.cjs.map +1 -1
  196. package/dist/mcp-mock.d.cts +97 -1
  197. package/dist/mcp-mock.d.cts.map +1 -1
  198. package/dist/mcp-mock.d.ts +97 -1
  199. package/dist/mcp-mock.d.ts.map +1 -1
  200. package/dist/mcp-mock.js +327 -48
  201. package/dist/mcp-mock.js.map +1 -1
  202. package/dist/mcp-recorder.cjs +1224 -0
  203. package/dist/mcp-recorder.cjs.map +1 -0
  204. package/dist/mcp-recorder.d.cts +108 -0
  205. package/dist/mcp-recorder.d.cts.map +1 -0
  206. package/dist/mcp-recorder.d.ts +108 -0
  207. package/dist/mcp-recorder.d.ts.map +1 -0
  208. package/dist/mcp-recorder.js +1215 -0
  209. package/dist/mcp-recorder.js.map +1 -0
  210. package/dist/mcp-stub.cjs +5 -1
  211. package/dist/mcp-stub.d.cts +3 -1
  212. package/dist/mcp-stub.d.ts +3 -1
  213. package/dist/mcp-stub.js +3 -1
  214. package/dist/mcp-types.d.cts +6 -0
  215. package/dist/mcp-types.d.ts +6 -0
  216. package/dist/message-text.cjs +192 -0
  217. package/dist/message-text.cjs.map +1 -0
  218. package/dist/message-text.d.cts +31 -0
  219. package/dist/message-text.d.cts.map +1 -0
  220. package/dist/message-text.d.ts +31 -0
  221. package/dist/message-text.d.ts.map +1 -0
  222. package/dist/message-text.js +184 -0
  223. package/dist/message-text.js.map +1 -0
  224. package/dist/messages.cjs +400 -56
  225. package/dist/messages.cjs.map +1 -1
  226. package/dist/messages.d.cts.map +1 -1
  227. package/dist/messages.d.ts.map +1 -1
  228. package/dist/messages.js +401 -58
  229. package/dist/messages.js.map +1 -1
  230. package/dist/metrics.cjs +46 -66
  231. package/dist/metrics.cjs.map +1 -1
  232. package/dist/metrics.d.cts +14 -6
  233. package/dist/metrics.d.cts.map +1 -1
  234. package/dist/metrics.d.ts +14 -6
  235. package/dist/metrics.d.ts.map +1 -1
  236. package/dist/metrics.js +32 -40
  237. package/dist/metrics.js.map +1 -1
  238. package/dist/misbehavior.cjs +1523 -0
  239. package/dist/misbehavior.cjs.map +1 -0
  240. package/dist/misbehavior.d.cts +43 -0
  241. package/dist/misbehavior.d.cts.map +1 -0
  242. package/dist/misbehavior.d.ts +43 -0
  243. package/dist/misbehavior.d.ts.map +1 -0
  244. package/dist/misbehavior.js +1511 -0
  245. package/dist/misbehavior.js.map +1 -0
  246. package/dist/ollama.cjs +315 -113
  247. package/dist/ollama.cjs.map +1 -1
  248. package/dist/ollama.d.cts.map +1 -1
  249. package/dist/ollama.d.ts.map +1 -1
  250. package/dist/ollama.js +316 -114
  251. package/dist/ollama.js.map +1 -1
  252. package/dist/openapi.cjs +3405 -0
  253. package/dist/openapi.cjs.map +1 -0
  254. package/dist/openapi.d.cts +30 -0
  255. package/dist/openapi.d.cts.map +1 -0
  256. package/dist/openapi.d.ts +30 -0
  257. package/dist/openapi.d.ts.map +1 -0
  258. package/dist/openapi.js +3404 -0
  259. package/dist/openapi.js.map +1 -0
  260. package/dist/openrouter-video.cjs +4 -4
  261. package/dist/openrouter-video.cjs.map +1 -1
  262. package/dist/openrouter-video.d.cts.map +1 -1
  263. package/dist/openrouter-video.d.ts.map +1 -1
  264. package/dist/openrouter-video.js +4 -4
  265. package/dist/openrouter-video.js.map +1 -1
  266. package/dist/record-sanitize.cjs +231 -0
  267. package/dist/record-sanitize.cjs.map +1 -0
  268. package/dist/record-sanitize.js +224 -0
  269. package/dist/record-sanitize.js.map +1 -0
  270. package/dist/recorder.cjs +115 -6
  271. package/dist/recorder.cjs.map +1 -1
  272. package/dist/recorder.d.cts +2 -1
  273. package/dist/recorder.d.cts.map +1 -1
  274. package/dist/recorder.d.ts +2 -1
  275. package/dist/recorder.d.ts.map +1 -1
  276. package/dist/recorder.js +116 -8
  277. package/dist/recorder.js.map +1 -1
  278. package/dist/responses.cjs +701 -166
  279. package/dist/responses.cjs.map +1 -1
  280. package/dist/responses.d.cts +5 -13
  281. package/dist/responses.d.cts.map +1 -1
  282. package/dist/responses.d.ts +5 -13
  283. package/dist/responses.d.ts.map +1 -1
  284. package/dist/responses.js +699 -169
  285. package/dist/responses.js.map +1 -1
  286. package/dist/route-registry.cjs +951 -0
  287. package/dist/route-registry.cjs.map +1 -0
  288. package/dist/route-registry.d.cts +33 -0
  289. package/dist/route-registry.d.cts.map +1 -0
  290. package/dist/route-registry.d.ts +33 -0
  291. package/dist/route-registry.d.ts.map +1 -0
  292. package/dist/route-registry.js +866 -0
  293. package/dist/route-registry.js.map +1 -0
  294. package/dist/router.cjs +24 -2
  295. package/dist/router.cjs.map +1 -1
  296. package/dist/router.d.cts.map +1 -1
  297. package/dist/router.d.ts.map +1 -1
  298. package/dist/router.js +25 -4
  299. package/dist/router.js.map +1 -1
  300. package/dist/server.cjs +1159 -264
  301. package/dist/server.cjs.map +1 -1
  302. package/dist/server.d.cts +4 -1
  303. package/dist/server.d.cts.map +1 -1
  304. package/dist/server.d.ts +4 -1
  305. package/dist/server.d.ts.map +1 -1
  306. package/dist/server.js +1067 -172
  307. package/dist/server.js.map +1 -1
  308. package/dist/speech.cjs +4 -2
  309. package/dist/speech.cjs.map +1 -1
  310. package/dist/speech.js +4 -2
  311. package/dist/speech.js.map +1 -1
  312. package/dist/stream-collapse.cjs +236 -34
  313. package/dist/stream-collapse.cjs.map +1 -1
  314. package/dist/stream-collapse.d.cts +39 -4
  315. package/dist/stream-collapse.d.cts.map +1 -1
  316. package/dist/stream-collapse.d.ts +39 -4
  317. package/dist/stream-collapse.d.ts.map +1 -1
  318. package/dist/stream-collapse.js +235 -35
  319. package/dist/stream-collapse.js.map +1 -1
  320. package/dist/suite.cjs +1 -1
  321. package/dist/suite.js +1 -1
  322. package/dist/transcription.cjs +2 -2
  323. package/dist/transcription.cjs.map +1 -1
  324. package/dist/transcription.d.cts.map +1 -1
  325. package/dist/transcription.d.ts.map +1 -1
  326. package/dist/transcription.js +2 -2
  327. package/dist/transcription.js.map +1 -1
  328. package/dist/types.cjs +18 -0
  329. package/dist/types.cjs.map +1 -0
  330. package/dist/types.d.cts +516 -5
  331. package/dist/types.d.cts.map +1 -1
  332. package/dist/types.d.ts +516 -5
  333. package/dist/types.d.ts.map +1 -1
  334. package/dist/types.js +17 -0
  335. package/dist/types.js.map +1 -0
  336. package/dist/validate-cli.cjs +123 -33
  337. package/dist/validate-cli.cjs.map +1 -1
  338. package/dist/validate-cli.js +125 -35
  339. package/dist/validate-cli.js.map +1 -1
  340. package/dist/vector-stores.cjs +1180 -0
  341. package/dist/vector-stores.cjs.map +1 -0
  342. package/dist/vector-stores.d.cts +106 -0
  343. package/dist/vector-stores.d.cts.map +1 -0
  344. package/dist/vector-stores.d.ts +106 -0
  345. package/dist/vector-stores.d.ts.map +1 -0
  346. package/dist/vector-stores.js +1164 -0
  347. package/dist/vector-stores.js.map +1 -0
  348. package/dist/vector-types.d.cts.map +1 -1
  349. package/dist/version.cjs +45 -0
  350. package/dist/version.cjs.map +1 -0
  351. package/dist/version.js +44 -0
  352. package/dist/version.js.map +1 -0
  353. package/dist/video.cjs +19 -0
  354. package/dist/video.cjs.map +1 -1
  355. package/dist/video.d.cts.map +1 -1
  356. package/dist/video.d.ts.map +1 -1
  357. package/dist/video.js +19 -0
  358. package/dist/video.js.map +1 -1
  359. package/dist/vitest.cjs +77 -16
  360. package/dist/vitest.cjs.map +1 -1
  361. package/dist/vitest.d.cts +38 -5
  362. package/dist/vitest.d.cts.map +1 -1
  363. package/dist/vitest.d.ts +38 -5
  364. package/dist/vitest.d.ts.map +1 -1
  365. package/dist/vitest.js +78 -17
  366. package/dist/vitest.js.map +1 -1
  367. package/dist/watcher.cjs +6 -2
  368. package/dist/watcher.cjs.map +1 -1
  369. package/dist/watcher.js +6 -2
  370. package/dist/watcher.js.map +1 -1
  371. package/dist/ws-gemini-live.cjs +210 -101
  372. package/dist/ws-gemini-live.cjs.map +1 -1
  373. package/dist/ws-gemini-live.d.cts +9 -3
  374. package/dist/ws-gemini-live.d.cts.map +1 -1
  375. package/dist/ws-gemini-live.d.ts +9 -3
  376. package/dist/ws-gemini-live.d.ts.map +1 -1
  377. package/dist/ws-gemini-live.js +211 -102
  378. package/dist/ws-gemini-live.js.map +1 -1
  379. package/dist/ws-realtime.cjs +175 -62
  380. package/dist/ws-realtime.cjs.map +1 -1
  381. package/dist/ws-realtime.d.cts +8 -3
  382. package/dist/ws-realtime.d.cts.map +1 -1
  383. package/dist/ws-realtime.d.ts +8 -3
  384. package/dist/ws-realtime.d.ts.map +1 -1
  385. package/dist/ws-realtime.js +176 -63
  386. package/dist/ws-realtime.js.map +1 -1
  387. package/dist/ws-responses.cjs +121 -16
  388. package/dist/ws-responses.cjs.map +1 -1
  389. package/dist/ws-responses.d.cts +8 -12
  390. package/dist/ws-responses.d.cts.map +1 -1
  391. package/dist/ws-responses.d.ts +8 -12
  392. package/dist/ws-responses.d.ts.map +1 -1
  393. package/dist/ws-responses.js +123 -18
  394. package/dist/ws-responses.js.map +1 -1
  395. package/package.json +13 -2
  396. package/skills/write-fixtures/SKILL.md +179 -21
package/dist/server.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { DEFAULT_TEST_ID } from "./constants.js";
2
+ import { build, msg, quote } from "./message-text.js";
2
3
  import { markAuthenticatedRequest, resolveInboundAuth, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection } from "./api-key-auth.js";
3
- 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, 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";
4
5
  import { normalizeLiveOptions } from "./live-fixture.js";
5
6
  import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
6
7
  import { CHAOS_FIELDS, CHAOS_FIELD_NAMES, applyChaosAction, awaitChaosLatency, describeUnwritableReason, evaluateChaos, isChaosScope, parseChaosField, resetChaosWarnings, resolveChaosConfig, responseGoneReason } from "./chaos.js";
7
- import { 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";
8
10
  import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
9
11
  import { proxyAndRecord } from "./recorder.js";
10
12
  import { Logger } from "./logger.js";
@@ -37,6 +39,7 @@ import { handleSearch } from "./search.js";
37
39
  import { handleRerank } from "./rerank.js";
38
40
  import { handleModeration } from "./moderation.js";
39
41
  import { clearFineTuningStore, handleFineTuningCancel, handleFineTuningCreate, handleFineTuningEvents, handleFineTuningList, handleFineTuningRetrieve } from "./fine-tuning.js";
42
+ import { clearVectorStoreStore, handleVectorFileBatchesCancel, handleVectorFileBatchesCreate, handleVectorFileBatchesFiles, handleVectorFileBatchesRetrieve, handleVectorStoreFilesCreate, handleVectorStoreFilesDelete, handleVectorStoreFilesList, handleVectorStoreFilesRetrieve, handleVectorStoresCreate, handleVectorStoresDelete, handleVectorStoresList, handleVectorStoresModify, handleVectorStoresRetrieve, handleVectorStoresSearch } from "./vector-stores.js";
40
43
  import { FILES_BODY_DRAIN_MAX_BYTES, FILES_BODY_MAX_BYTES, FILES_BODY_OVERSIZED, clearFileStore, handleFilesContent, handleFilesCreate, handleFilesDelete, handleFilesList, handleFilesRetrieve } from "./files.js";
41
44
  import { clearBatchStore, handleBatchesCancel, handleBatchesCreate, handleBatchesList, handleBatchesRetrieve } from "./batches.js";
42
45
  import { upgradeToWebSocket } from "./ws-framing.js";
@@ -44,41 +47,18 @@ import { handleLiveSession } from "./ws-live.js";
44
47
  import { handleWebSocketResponses } from "./ws-responses.js";
45
48
  import { handleWebSocketRealtime } from "./ws-realtime.js";
46
49
  import { handleWebSocketGeminiLive } from "./ws-gemini-live.js";
47
- import { BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE, DESTROYED_STATUS_LABEL, FAL_ROUTE_RE, FILES_CONTENT_RE, FILES_ID_RE, GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_STATUS_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, createMetricsRegistry, normalizePathLabel } from "./metrics.js";
50
+ import { AZURE_DEPLOYMENT_RE, BATCHES_CANCEL_RE, BATCHES_ID_RE, BATCHES_PATH, BEDROCK_CONVERSE_RE, BEDROCK_CONVERSE_STREAM_RE, BEDROCK_INVOKE_RE, BEDROCK_STREAM_RE, BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE, COHERE_CHAT_PATH, COHERE_EMBED_PATH, COMPLETIONS_PATH, CONTROL_PREFIX, ELEVENLABS_MUSIC_RE, ELEVENLABS_SOUND_GENERATION_PATH, ELEVENLABS_TTS_RE, ELEVENLABS_VOICE_CREATE_PATH, ELEVENLABS_VOICE_DESIGN_PATH, ELEVENLABS_VOICE_RE, EMBEDDINGS_PATH, FAL_QUEUE_REQUESTS_RE, FAL_QUEUE_SUBMIT_RE, FAL_ROUTE_RE, FAL_RUN_RE, FILES_CONTENT_RE, FILES_ID_RE, FILES_PATH, FINE_TUNING_CANCEL_RE, FINE_TUNING_EVENTS_RE, FINE_TUNING_ID_RE, FINE_TUNING_JOBS_PATH, GEMINI_EMBED_RE, GEMINI_INTERACTIONS_PATH, GEMINI_LIVE_PATH, GEMINI_PATH_RE, GEMINI_PREDICT_RE, GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, HEALTH_PATH, IMAGES_EDIT_PATH, IMAGES_PATH, IMAGES_VARIATIONS_PATH, LIVE_PATH, MESSAGES_PATH, METRICS_PATH, MODELS_PATH, MODERATIONS_PATH, OLLAMA_CHAT_PATH, OLLAMA_EMBEDDINGS_PATH, OLLAMA_EMBED_PATH, OLLAMA_GENERATE_PATH, OLLAMA_TAGS_PATH, OPENROUTER_CREDITS_PATH, OPENROUTER_KEY_PATH, OPENROUTER_MODELS_PATH, OPENROUTER_VIDEOS_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_MODELS_PATH, OPENROUTER_VIDEO_STATUS_RE, READY_PATH, REALTIME_PATH, REQUESTS_PATH, RERANK_PATH, RESPONSES_PATH, SEARCH_PATH, SPEECH_PATH, TRANSCRIPTIONS_PATH, TRANSLATIONS_PATH, VECTOR_STORES_BATCH_CANCEL_RE, VECTOR_STORES_BATCH_FILES_RE, VECTOR_STORES_BATCH_RE, VECTOR_STORES_FILES_RE, VECTOR_STORES_FILE_BATCHES_RE, VECTOR_STORES_FILE_RE, VECTOR_STORES_ID_RE, VECTOR_STORES_PATH, VECTOR_STORES_SEARCH_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, VERTEX_AI_RE, VIDEOS_PATH, matchRouteDefinition } from "./route-registry.js";
51
+ import { CATALOG_ROUTES, buildOpenApiDocument } from "./openapi.js";
52
+ import { DESTROYED_STATUS_LABEL, createMetricsRegistry, normalizePathLabel } from "./metrics.js";
53
+ import { MCP_FAKES_DEFAULT_MOUNT, McpFakeStore, McpFakesAddError, blockIdOf } from "./mcp-fakes.js";
54
+ import { MCPMock } from "./mcp-mock.js";
55
+ import { ensureFakeMount, findFakeMount, handOffMcpFakes, isFakeMount, mountConflictDetail } from "./mcp-fakes-mount.js";
56
+ import { handleFakesReportRoute } from "./mcp-fakes-report.js";
48
57
  import * as http$1 from "node:http";
49
58
  import { AsyncLocalStorage } from "node:async_hooks";
50
59
 
51
60
  //#region src/server.ts
52
61
  const liveClosers = /* @__PURE__ */ new WeakMap();
53
- const COMPLETIONS_PATH = "/v1/chat/completions";
54
- const RESPONSES_PATH = "/v1/responses";
55
- const LIVE_PATH = "/v1/live/sessions";
56
- const REALTIME_PATH = "/v1/realtime";
57
- const GEMINI_LIVE_PATH = "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent";
58
- const MESSAGES_PATH = "/v1/messages";
59
- const EMBEDDINGS_PATH = "/v1/embeddings";
60
- const COHERE_CHAT_PATH = "/v2/chat";
61
- const COHERE_EMBED_PATH = "/v2/embed";
62
- const SEARCH_PATH = "/search";
63
- const RERANK_PATH = "/v2/rerank";
64
- const MODERATIONS_PATH = "/v1/moderations";
65
- const IMAGES_PATH = "/v1/images/generations";
66
- const IMAGES_EDIT_PATH = "/v1/images/edits";
67
- const IMAGES_VARIATIONS_PATH = "/v1/images/variations";
68
- const SPEECH_PATH = "/v1/audio/speech";
69
- const TRANSCRIPTIONS_PATH = "/v1/audio/transcriptions";
70
- const TRANSLATIONS_PATH = "/v1/audio/translations";
71
- const VIDEOS_PATH = "/v1/videos";
72
- const GEMINI_PREDICT_RE = /^\/v1beta\/models\/([^:]+):predict$/;
73
- const ELEVENLABS_SOUND_GENERATION_PATH = "/v1/sound-generation";
74
- const ELEVENLABS_TTS_RE = /^\/v1\/text-to-speech\/([^/]+)$/;
75
- const ELEVENLABS_MUSIC_RE = /^\/v1\/music(?:\/(.+))?$/;
76
- const ELEVENLABS_VOICE_DESIGN_PATH = "/v1/text-to-voice/design";
77
- const ELEVENLABS_VOICE_CREATE_PATH = "/v1/text-to-voice";
78
- const ELEVENLABS_VOICE_RE = /^\/v1\/voices\/([^/]+)$/;
79
- const FAL_QUEUE_SUBMIT_RE = /^\/fal\/queue\/submit\/(.+)$/;
80
- const FAL_QUEUE_REQUESTS_RE = /^\/fal\/queue\/requests\/(.+)$/;
81
- const FAL_RUN_RE = /^\/fal\/run\/(.+)$/;
82
62
  const DEFAULT_CHUNK_SIZE = 20;
83
63
  const COMPAT_SUFFIXES = [
84
64
  "/chat/completions",
@@ -107,34 +87,6 @@ function normalizeCompatPath(pathname, logger) {
107
87
  }
108
88
  return pathname;
109
89
  }
110
- const GEMINI_INTERACTIONS_PATH = "/v1beta/interactions";
111
- const GEMINI_PATH_RE = /^\/v1beta\/models\/([^:]+):(generateContent|streamGenerateContent)$/;
112
- const GEMINI_EMBED_RE = /^\/v1beta\/models\/([^:]+):embedContent$/;
113
- const AZURE_DEPLOYMENT_RE = /^\/openai\/deployments\/([^/]+)\/(chat\/completions|embeddings)$/;
114
- const BEDROCK_INVOKE_RE = /^\/model\/([^/]+)\/invoke$/;
115
- const BEDROCK_STREAM_RE = /^\/model\/([^/]+)\/invoke-with-response-stream$/;
116
- const BEDROCK_CONVERSE_RE = /^\/model\/([^/]+)\/converse$/;
117
- const BEDROCK_CONVERSE_STREAM_RE = /^\/model\/([^/]+)\/converse-stream$/;
118
- const VERTEX_AI_RE = /^\/v1\/projects\/[^/]+\/locations\/[^/]+\/publishers\/google\/models\/([^/:]+):(generateContent|streamGenerateContent)$/;
119
- const OLLAMA_CHAT_PATH = "/api/chat";
120
- const OLLAMA_GENERATE_PATH = "/api/generate";
121
- const OLLAMA_EMBEDDINGS_PATH = "/api/embeddings";
122
- const OLLAMA_EMBED_PATH = "/api/embed";
123
- const OLLAMA_TAGS_PATH = "/api/tags";
124
- const OPENROUTER_VIDEOS_PATH = "/api/v1/videos";
125
- const OPENROUTER_VIDEO_MODELS_PATH = "/api/v1/videos/models";
126
- const HEALTH_PATH = "/health";
127
- const READY_PATH = "/ready";
128
- const MODELS_PATH = "/v1/models";
129
- const REQUESTS_PATH = "/v1/_requests";
130
- const BATCHES_PATH = "/v1/batches";
131
- const BATCHES_ID_RE = /^\/v1\/batches\/([^/]+)$/;
132
- const BATCHES_CANCEL_RE = /^\/v1\/batches\/([^/]+)\/cancel$/;
133
- const FILES_PATH = "/v1/files";
134
- const FINE_TUNING_JOBS_PATH = "/v1/fine_tuning/jobs";
135
- const FINE_TUNING_ID_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)$/;
136
- const FINE_TUNING_CANCEL_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)\/cancel$/;
137
- const FINE_TUNING_EVENTS_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)\/events$/;
138
90
  const DEFAULT_MODELS = [
139
91
  "gpt-4",
140
92
  "gpt-4o",
@@ -144,7 +96,7 @@ const DEFAULT_MODELS = [
144
96
  ];
145
97
  const CORS_HEADERS = {
146
98
  "Access-Control-Allow-Origin": "*",
147
- "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
99
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
148
100
  "Access-Control-Allow-Headers": "*",
149
101
  "Access-Control-Expose-Headers": "X-Total-Count, X-Request-Id"
150
102
  };
@@ -163,7 +115,6 @@ function handleNotFound(res, message) {
163
115
  type: "not_found"
164
116
  } }));
165
117
  }
166
- const CONTROL_PREFIX = "/__aimock";
167
118
  /** The complete `GET /__aimock/fixtures` query-param vocabulary; anything else 400s. */
168
119
  const FIXTURES_PARAMS = new Set(["include"]);
169
120
  /** The complete `GET /__aimock/journal` query-param vocabulary; anything else 400s. */
@@ -202,6 +153,7 @@ function performFullReset(fixtures, targets) {
202
153
  clearElevenLabsVoices();
203
154
  clearFileStore();
204
155
  clearFineTuningStore();
156
+ clearVectorStoreStore();
205
157
  resetInteractionCounter();
206
158
  resetEventIdCounter();
207
159
  if (!targets) return;
@@ -209,11 +161,13 @@ function performFullReset(fixtures, targets) {
209
161
  targets.journal.clear();
210
162
  resetChaosWarnings(targets.defaults.logger);
211
163
  targets.defaults.chaos = void 0;
164
+ targets.defaults.misbehavior?.byTestId.clear();
212
165
  targets.videoStates.clear();
213
166
  targets.openRouterVideoJobs.clear();
214
167
  targets.veoVideoJobs.clear();
215
168
  targets.grokVideoJobs.clear();
216
169
  targets.bytePlusVideoJobs.clear();
170
+ for (const { handler } of targets.mounts ?? []) handler.clearMcpFakes?.();
217
171
  if (targets.defaults.registry) targets.defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
218
172
  }
219
173
  /**
@@ -253,7 +207,7 @@ function fixtureResponseKind(response) {
253
207
  if (isImageResponse(response)) return "image";
254
208
  if (isEmbeddingResponse(response)) return "embedding";
255
209
  if (isJSONResponse(response)) return "json";
256
- if (isContentWithToolCallsResponse(response)) return "contentWithToolCalls";
210
+ if (isCombinedFixtureResponse(response)) return "contentWithToolCalls";
257
211
  if (isToolCallResponse(response)) return "toolCalls";
258
212
  if (isTextResponse(response)) return "text";
259
213
  return "unknown";
@@ -268,9 +222,14 @@ function fixtureResponseKind(response) {
268
222
  * scoping path does not also do. (Splitting on "," additionally broke every
269
223
  * legitimately comma-bearing id.) A raw `path.includes("testId=t1")` both
270
224
  * prefix-collides with `t10` and matches unrelated params like `notTestId`.
225
+ *
226
+ * An MCP mount records the test id it resolved on the entry (I9, B3): a
227
+ * decoded header or query value, or the one bound to the session at
228
+ * `initialize`. That value wins; LLM entries carry none and are attributed as
229
+ * before.
271
230
  */
272
231
  function journalEntryTestId(entry) {
273
- return resolveTestId(entry.headers, entry.path);
232
+ return entry.testId ?? resolveTestId(entry.headers, entry.path);
274
233
  }
275
234
  /**
276
235
  * The testId a chaos override is stored under, and read back with — resolved by
@@ -309,19 +268,188 @@ function effectiveChaos(defaults, scopeId) {
309
268
  if (!current) return {};
310
269
  return isChaosScope(current) ? current.base ?? {} : current;
311
270
  }
271
+ /** The `GET /__aimock/mcp/fakes` query-param vocabulary; anything else 400s. */
272
+ const MCP_FAKES_PARAMS = new Set([
273
+ "testId",
274
+ "context",
275
+ "mount"
276
+ ]);
277
+ /** A block's `mount` path, read without running getters; the add validates it. */
278
+ function controlFakeMountOf(raw) {
279
+ if (typeof raw !== "object" || raw === null) return MCP_FAKES_DEFAULT_MOUNT;
280
+ const value = Object.getOwnPropertyDescriptor(raw, "mount")?.value;
281
+ return typeof value === "string" ? value : MCP_FAKES_DEFAULT_MOUNT;
282
+ }
283
+ /**
284
+ * Check one `POST /__aimock/fixtures` `mcpFakes` value against the live
285
+ * mounts (F10, W6) without changing anything. Returns every error in input
286
+ * order (an empty list when the value can be added), and `apply`, which adds
287
+ * the blocks (auto-mounting an MCPMock where no mount serves a block's path)
288
+ * and returns the number of blocks added.
289
+ *
290
+ * The whole value is checked before any mount is touched, and the check
291
+ * never adds to a real mount: each group is added to a throwaway store,
292
+ * whose errors are the ones reported (it numbers the add `control-api#1`, as
293
+ * a new mount would; for an existing MCPMock the rejected group is re-run on
294
+ * its atomic add, so the errors name that mount's next `<n>`, I7). A group
295
+ * whose path no request to it can reach (a mount that is not an MCPMock
296
+ * serves it, an MCPMock answers it as its root, or it is under the control
297
+ * prefix) is a mount conflict (one error per block).
298
+ */
299
+ function planControlApiFakes(raw, mounts) {
300
+ const items = Array.isArray(raw) ? raw.map((block, position) => ({
301
+ position,
302
+ source: {
303
+ source: "control-api",
304
+ blockIndex: position,
305
+ raw: block
306
+ }
307
+ })) : [{
308
+ position: 0,
309
+ source: {
310
+ source: "control-api",
311
+ blockIndex: null,
312
+ raw
313
+ }
314
+ }];
315
+ const groups = /* @__PURE__ */ new Map();
316
+ if (items.length === 0) groups.set(MCP_FAKES_DEFAULT_MOUNT, {
317
+ path: MCP_FAKES_DEFAULT_MOUNT,
318
+ positions: [],
319
+ sources: []
320
+ });
321
+ for (const { position, source } of items) {
322
+ const path = controlFakeMountOf(source.raw);
323
+ const group = groups.get(path) ?? {
324
+ path,
325
+ positions: [],
326
+ sources: []
327
+ };
328
+ group.positions.push(position);
329
+ group.sources.push(source);
330
+ groups.set(path, group);
331
+ }
332
+ const found = [];
333
+ /** The paths `apply` auto-mounts, in order. */
334
+ const pending = [];
335
+ for (const group of groups.values()) {
336
+ const target = findFakeMount(mounts, group.path, pending);
337
+ if (target.kind === "conflict") {
338
+ group.positions.forEach((position) => {
339
+ const where = Array.isArray(raw) ? msg`mcpFakes[${position}]` : msg`mcpFakes`;
340
+ found.push({
341
+ position,
342
+ error: new FixtureLoadError({
343
+ rule: "mcp-fakes/mount-conflict",
344
+ file: null,
345
+ blockId: null,
346
+ entryId: null,
347
+ detail: msg`${where}: ${mountConflictDetail(group.path, target)}`
348
+ })
349
+ });
350
+ });
351
+ continue;
352
+ }
353
+ let failure = addFailure(() => new McpFakeStore().add(group.sources, { kind: "control-api" }));
354
+ if (target.kind === "none") pending.push(group.path);
355
+ if (!failure) continue;
356
+ if (target.kind === "mcp" && target.handler.addMcpFakes === MCPMock.prototype.addMcpFakes) {
357
+ const handler = target.handler;
358
+ failure = addFailure(() => handler.addMcpFakes?.(group.sources, { kind: "control-api" })) ?? failure;
359
+ }
360
+ for (const error of failure.errors) found.push({
361
+ position: positionOf(error, group),
362
+ error
363
+ });
364
+ }
365
+ found.sort((a, b) => a.position - b.position);
366
+ return {
367
+ errors: found.map((f) => f.error),
368
+ apply(wiring) {
369
+ let added = 0;
370
+ for (const group of groups.values()) {
371
+ const target = ensureFakeMount(mounts, group.path, {
372
+ journal: wiring.journal,
373
+ registry: wiring.defaults.registry,
374
+ logger: wiring.defaults.logger,
375
+ replaySpeed: wiring.defaults.replaySpeed
376
+ }, wiring.defaults.logger);
377
+ if ("conflict" in target) continue;
378
+ const result = target.handler.addMcpFakes?.(group.sources, { kind: "control-api" });
379
+ for (const warning of result?.warnings ?? []) wiring.defaults.logger.warn(warning.message);
380
+ added += group.sources.length;
381
+ }
382
+ return added;
383
+ }
384
+ };
385
+ }
386
+ /** The `McpFakesAddError` that `add` throws, or `null` when it adds. */
387
+ function addFailure(add) {
388
+ try {
389
+ add();
390
+ return null;
391
+ } catch (err) {
392
+ if (err instanceof McpFakesAddError) return err;
393
+ throw err;
394
+ }
395
+ }
396
+ /**
397
+ * The request position of the block an add error names. The add numbers a
398
+ * run-time group's blocks by their place in the group (`[<j>]`), or gives no
399
+ * index to a single-object value; an error tied to no block sorts with the
400
+ * group's first block.
401
+ */
402
+ function positionOf(error, group) {
403
+ const first = group.positions[0] ?? 0;
404
+ if (error.blockId == null || error.file === null) return first;
405
+ const at = group.positions.findIndex((_, j) => error.blockId === blockIdOf(error.file ?? "", "", j));
406
+ return at === -1 ? first : group.positions[at];
407
+ }
408
+ function hasFakesSnapshot(handler) {
409
+ return "fakesSnapshot" in handler && typeof handler.fakesSnapshot === "function";
410
+ }
411
+ /**
412
+ * `GET /__aimock/mcp/fakes` (spec 6.5, I9): for each MCP mount (or the one
413
+ * named by `mount`), the blocks that apply to the requested test id and
414
+ * context, with every entry id and its consumed state for that test id.
415
+ * A mount is listed when it takes fakes (it implements `addMcpFakes`, the
416
+ * check every other fakes path uses); one with no `fakesSnapshot` lists no
417
+ * blocks.
418
+ */
419
+ function listControlApiFakes(mounts, testId, context, mount) {
420
+ const listed = mounts.flatMap(({ path, handler }) => isFakeMount(handler) && (mount === null || path === mount) ? [{
421
+ mount: path,
422
+ blocks: hasFakesSnapshot(handler) ? handler.fakesSnapshot(testId, context) : []
423
+ }] : []);
424
+ return mount !== null && listed.length === 0 ? null : listed;
425
+ }
426
+ const controlFixtureAdditions = /* @__PURE__ */ new WeakMap();
312
427
  /**
313
428
  * Handle requests under `/__aimock/`. Returns `true` if the request was
314
429
  * handled, `false` if the path doesn't match the control prefix.
315
430
  */
316
- async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults) {
431
+ async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mounts) {
317
432
  if (!pathname.startsWith(CONTROL_PREFIX)) return false;
318
- const subPath = pathname.slice(9);
433
+ const subPath = pathname.slice(CONTROL_PREFIX.length);
319
434
  setCorsHeaders(res);
320
435
  if (subPath === "/health" && req.method === "GET") {
321
436
  res.writeHead(200, { "Content-Type": "application/json" });
322
437
  res.end(JSON.stringify({ status: "ok" }));
323
438
  return true;
324
439
  }
440
+ if (subPath === "/openapi.json" && req.method === "GET") {
441
+ res.writeHead(200, { "Content-Type": "application/json" });
442
+ res.end(JSON.stringify(buildOpenApiDocument()));
443
+ return true;
444
+ }
445
+ if (subPath === "/routes" && req.method === "GET") {
446
+ res.writeHead(200, { "Content-Type": "application/json" });
447
+ res.end(JSON.stringify({
448
+ count: CATALOG_ROUTES.length,
449
+ routes: CATALOG_ROUTES
450
+ }));
451
+ return true;
452
+ }
325
453
  if (subPath === "/journal" && req.method === "GET") {
326
454
  const bad = (message) => {
327
455
  res.writeHead(400, { "Content-Type": "application/json" });
@@ -367,7 +495,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
367
495
  "Content-Type": "application/json",
368
496
  "X-Total-Count": String(total)
369
497
  });
370
- res.end(JSON.stringify(entries));
498
+ res.end(journalJson(entries, journal));
371
499
  return true;
372
500
  }
373
501
  if (subPath === "/fixtures" && req.method === "GET") {
@@ -387,19 +515,70 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
387
515
  res.end(JSON.stringify({ error: "Invalid 'include': expected 'fixtures'" }));
388
516
  return true;
389
517
  }
518
+ const extendedTools = isExtendedResponsesToolsList(fixtures);
390
519
  res.writeHead(200, { "Content-Type": "application/json" });
391
520
  res.end(JSON.stringify({
392
521
  count: fixtures.length,
393
522
  fixtures: fixtures.map((fixture, index) => ({
394
523
  index,
395
524
  match: redactForInspection(fixture.match),
396
- responseKind: isOneShotError(fixture) ? "error" : fixtureResponseKind(fixture.response),
525
+ responseKind: isOneShotError(fixture) ? "error" : fixtureResponseKind(extendedTools || typeof fixture.response === "function" ? fixture.response : withoutResponsesToolKeys(fixture.response)),
397
526
  ...fixture.latency !== void 0 ? { latency: fixture.latency } : {},
398
527
  ...fixture.chaos !== void 0 ? { chaos: fixture.chaos } : {}
399
528
  }))
400
529
  }));
401
530
  return true;
402
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
+ }
403
582
  if (subPath === "/chaos" && req.method === "GET") {
404
583
  const scopeId = chaosScopeId(req);
405
584
  if (scopeId === null) return writeBlankTestId(res);
@@ -472,6 +651,25 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
472
651
  res.end(JSON.stringify({ chaos: effectiveChaos(defaults, scopeId) }));
473
652
  return true;
474
653
  }
654
+ if (subPath === "/mcp/fakes" && req.method === "GET") {
655
+ const reply = (status, body) => {
656
+ res.writeHead(status, { "Content-Type": "application/json" });
657
+ res.end(JSON.stringify(body));
658
+ return true;
659
+ };
660
+ for (const key of searchParams.keys()) if (!MCP_FAKES_PARAMS.has(key)) return reply(400, { error: `Unknown query parameter: '${key}'. Supported: ${[...MCP_FAKES_PARAMS].join(", ")}` });
661
+ const testId = searchParams.get("testId") || null;
662
+ const context = searchParams.get("context") || null;
663
+ const mount = searchParams.get("mount") || null;
664
+ const listed = listControlApiFakes(mounts, testId, context, mount);
665
+ if (listed === null) return reply(404, { error: build(msg`No MCP mount at ${quote(mount ?? "")}`) });
666
+ return reply(200, {
667
+ testId,
668
+ context,
669
+ mounts: listed
670
+ });
671
+ }
672
+ if (subPath === "/mcp/fakes/report" && req.method === "GET") return handleFakesReportRoute(res, searchParams, mounts);
475
673
  if (subPath === "/fixtures" && req.method === "POST") {
476
674
  let raw;
477
675
  try {
@@ -493,29 +691,82 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
493
691
  res.end(JSON.stringify({ error: `Invalid JSON: ${msg}` }));
494
692
  return true;
495
693
  }
496
- if (!Array.isArray(parsed.fixtures)) {
694
+ const withFakes = typeof parsed === "object" && parsed !== null && Object.hasOwn(parsed, "mcpFakes");
695
+ if (parsed === null || !Array.isArray(parsed.fixtures) && !(withFakes && parsed.fixtures === void 0)) {
497
696
  res.writeHead(400, { "Content-Type": "application/json" });
498
697
  res.end(JSON.stringify({ error: "Missing or invalid \"fixtures\" array" }));
499
698
  return true;
500
699
  }
501
- const converted = parsed.fixtures.map((e) => entryToFixture(e));
502
- const errors = validateFixtures(converted).filter((i) => i.severity === "error");
503
- if (errors.length > 0) {
700
+ const entries = Array.isArray(parsed.fixtures) ? parsed.fixtures : [];
701
+ const missingMatchIndex = entries.findIndex((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && entry.match == null);
702
+ if (missingMatchIndex !== -1) {
703
+ res.writeHead(400, { "Content-Type": "application/json" });
704
+ res.end(JSON.stringify({ error: `Fixture at index ${missingMatchIndex} is missing match` }));
705
+ return true;
706
+ }
707
+ const invalidMatchIndex = entries.findIndex((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && entry.match != null && (typeof entry.match !== "object" || Array.isArray(entry.match)));
708
+ if (invalidMatchIndex !== -1) {
709
+ res.writeHead(400, { "Content-Type": "application/json" });
710
+ res.end(JSON.stringify({ error: `Fixture at index ${invalidMatchIndex}: match must be an object` }));
711
+ return true;
712
+ }
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");
732
+ const fakes = withFakes ? planControlApiFakes(parsed.mcpFakes, mounts) : null;
733
+ const fakeErrors = fakes?.errors ?? [];
734
+ const validationFailed = (details) => {
504
735
  res.writeHead(400, { "Content-Type": "application/json" });
505
736
  res.end(JSON.stringify({
506
737
  error: "Validation failed",
507
- details: errors
738
+ details
508
739
  }));
509
740
  return true;
741
+ };
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
+ ]);
747
+ let mcpFakesAdded = 0;
748
+ if (fakes) try {
749
+ mcpFakesAdded = fakes.apply({
750
+ journal,
751
+ defaults
752
+ });
753
+ } catch (err) {
754
+ if (!(err instanceof McpFakesAddError)) throw err;
755
+ return validationFailed(err.errors.map((e) => e.toJSON()));
510
756
  }
511
757
  fixtures.push(...converted);
512
758
  if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
513
759
  res.writeHead(200, { "Content-Type": "application/json" });
514
- res.end(JSON.stringify({ added: converted.length }));
760
+ res.end(JSON.stringify(withFakes ? {
761
+ added: converted.length,
762
+ mcpFakesAdded
763
+ } : { added: converted.length }));
515
764
  return true;
516
765
  }
517
766
  if (subPath === "/fixtures" && req.method === "DELETE") {
518
767
  clearFixtureQueue(fixtures);
768
+ journal.clearMisbehaviorCounters();
769
+ for (const { handler } of mounts) handler.clearMcpFakes?.();
519
770
  if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
520
771
  res.writeHead(200, { "Content-Type": "application/json" });
521
772
  res.end(JSON.stringify({ cleared: true }));
@@ -528,7 +779,8 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
528
779
  veoVideoJobs,
529
780
  grokVideoJobs,
530
781
  bytePlusVideoJobs,
531
- defaults
782
+ defaults,
783
+ mounts
532
784
  });
533
785
  if (subPath === "/reset" && req.method === "POST") {
534
786
  performFullReset(fixtures, resetTargets());
@@ -630,8 +882,9 @@ function clientAbortedMidBody(req, res, err) {
630
882
  if (err instanceof RequestBodyTooLargeError) return false;
631
883
  return res.destroyed && !req.complete;
632
884
  }
633
- 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) {
634
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";
635
888
  let raw;
636
889
  try {
637
890
  raw = await readBody(req);
@@ -737,6 +990,51 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
737
990
  } }));
738
991
  return;
739
992
  }
993
+ const nullMessageIndex = body.messages.findIndex((message) => message === null);
994
+ if (nullMessageIndex !== -1) {
995
+ const message = `Invalid request: messages[${nullMessageIndex}] must be an object`;
996
+ journal.add({
997
+ method: req.method ?? "POST",
998
+ path: req.url ?? COMPLETIONS_PATH,
999
+ headers: flattenHeaders(req.headers),
1000
+ body: null,
1001
+ response: {
1002
+ status: 400,
1003
+ fixture: null
1004
+ }
1005
+ });
1006
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, message) : JSON.stringify({ error: {
1007
+ message,
1008
+ type: "invalid_request_error",
1009
+ param: null,
1010
+ code: null
1011
+ } }));
1012
+ return;
1013
+ }
1014
+ if (new URL(req.url ?? COMPLETIONS_PATH, "http://localhost").pathname === COMPLETIONS_PATH) {
1015
+ let toolsError = validateToolsField(body.tools);
1016
+ if (!toolsError && Array.isArray(body.tools)) {
1017
+ const nullFunctionIndex = body.tools.findIndex((tool) => tool?.function === null);
1018
+ if (nullFunctionIndex !== -1) toolsError = `tools[${nullFunctionIndex}].function must not be null`;
1019
+ }
1020
+ if (toolsError) {
1021
+ journal.add({
1022
+ method: req.method ?? "POST",
1023
+ path: req.url ?? COMPLETIONS_PATH,
1024
+ headers: flattenHeaders(req.headers),
1025
+ body: null,
1026
+ response: {
1027
+ status: 400,
1028
+ fixture: null
1029
+ }
1030
+ });
1031
+ writeErrorResponse(res, 400, JSON.stringify({ error: {
1032
+ message: toolsError,
1033
+ type: "invalid_request_error"
1034
+ } }));
1035
+ return;
1036
+ }
1037
+ }
740
1038
  const method = req.method ?? "POST";
741
1039
  const path = req.url ?? COMPLETIONS_PATH;
742
1040
  const flatHeaders = flattenHeaders(req.headers);
@@ -752,6 +1050,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
752
1050
  const openRouterFallback = openRouter && Array.isArray(body.models) && body.models.length > 0;
753
1051
  const selectFixture = (probe, counts) => {
754
1052
  for (;;) {
1053
+ beforeFixtureSelection?.();
755
1054
  const attempt = matchFixtureDiagnostic(fixtures, probe, counts, defaults.requestTransform, matchOptions);
756
1055
  if (attempt.fixture && isOneShotError(attempt.fixture) && !claimOneShotError(fixtures, attempt.fixture)) continue;
757
1056
  return attempt;
@@ -835,13 +1134,32 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
835
1134
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
836
1135
  const missWouldProxy = wouldProxyMiss(effectiveStrict, defaults.record, providerKey);
837
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
+ };
838
1154
  if (chaosAction === "drop" || chaosAction === "disconnect" || chaosAction === "rateLimit") {
839
1155
  applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : noFixtureSource, defaults.registry, defaults.logger);
1156
+ recordBypass("chaos-fired");
840
1157
  releaseOneShotError(fixtures, fixture);
841
1158
  return;
842
1159
  }
843
1160
  if (chaosAction === "malformed" && (fixture || !missWouldProxy)) {
844
1161
  applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "internal", defaults.registry, defaults.logger);
1162
+ recordBypass("chaos-fired");
845
1163
  releaseOneShotError(fixtures, fixture);
846
1164
  return;
847
1165
  }
@@ -873,6 +1191,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
873
1191
  const hookOptions = chaosAction === "malformed" ? {
874
1192
  beforeWriteResponse: () => {
875
1193
  applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry, defaults.logger);
1194
+ recordBypass("chaos-fired");
876
1195
  return true;
877
1196
  },
878
1197
  onHookBypassed: (reason) => {
@@ -915,10 +1234,12 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
915
1234
  } : {}
916
1235
  }
917
1236
  });
1237
+ recordBypass("proxied");
918
1238
  return;
919
1239
  }
920
1240
  if (chaosAction === "malformed") {
921
1241
  applyChaosAction(chaosAction, res, null, journal, chaosContext, "internal", defaults.registry, defaults.logger);
1242
+ recordBypass("chaos-fired");
922
1243
  return;
923
1244
  }
924
1245
  }
@@ -941,13 +1262,71 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
941
1262
  } }));
942
1263
  return;
943
1264
  }
944
- 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;
945
1314
  const latency = fixture.latency ?? defaults.latency;
946
1315
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
947
1316
  const emitStreamingUsage = body.stream === true && body.stream_options?.include_usage === true || openRouter;
948
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("");
949
- 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
+ };
950
1328
  const shapeORCompletion = (completion, overrides) => {
1329
+ if (appliedUsage) completion.usage = appliedUsage;
951
1330
  if (!openRouter) return completion;
952
1331
  return shapeOpenRouterCompletion(completion, resolveOpenRouterShaping(overrides, overrides?.model ?? responseModel), overrides?.id !== void 0);
953
1332
  };
@@ -960,7 +1339,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
960
1339
  const openRouterProcessing = !!(openRouter && fixture.openRouterProcessing);
961
1340
  if (isErrorResponse(response)) {
962
1341
  const status = response.status ?? 500;
963
- journal.add({
1342
+ recordOutcome(journal.add({
964
1343
  method: req.method ?? "POST",
965
1344
  path: req.url ?? COMPLETIONS_PATH,
966
1345
  headers: flattenHeaders(req.headers),
@@ -969,12 +1348,12 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
969
1348
  status,
970
1349
  fixture
971
1350
  }
972
- });
1351
+ }));
973
1352
  writeErrorResponse(res, status, openRouter ? serializeOpenRouterError(status, response.error.message, response.error.metadata) : serializeErrorResponse(response), { retryAfter: response.retryAfter });
974
1353
  return;
975
1354
  }
976
1355
  if (isAudioResponse(response)) {
977
- journal.add({
1356
+ recordOutcome(journal.add({
978
1357
  method: req.method ?? "POST",
979
1358
  path: req.url ?? COMPLETIONS_PATH,
980
1359
  headers: flattenHeaders(req.headers),
@@ -983,18 +1362,58 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
983
1362
  status: 422,
984
1363
  fixture
985
1364
  }
986
- });
1365
+ }));
987
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: {
988
1367
  message: "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.",
989
1368
  type: "invalid_request_error"
990
1369
  } }));
991
1370
  return;
992
1371
  }
993
- 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)) {
994
1413
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
995
1414
  const overrides = extractOverrides(response);
996
1415
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
997
- const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1416
+ const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
998
1417
  const streaming = body.stream === true;
999
1418
  const blockOutcome = response.blocks && response.blocks.length > 0 ? resolveFixtureBlockOutcome(response.blocks) : void 0;
1000
1419
  const journalEntry = journal.add({
@@ -1007,16 +1426,18 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1007
1426
  fixture
1008
1427
  }
1009
1428
  });
1429
+ recordOutcome(journalEntry);
1430
+ const toolCalls = requireEmittedFunctionToolCalls(response, wire);
1010
1431
  if (!streaming) {
1011
- 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 ? {
1012
1433
  ...overrides,
1013
1434
  finishReason: overrides?.finishReason ?? (blockOutcome.hasToolCalls ? "tool_calls" : "stop")
1014
1435
  } : overrides, body.messages);
1015
1436
  res.writeHead(200, { "Content-Type": "application/json" });
1016
1437
  res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1017
1438
  } else {
1018
- const chunks = buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? response.toolCalls ?? [], responseModel, chunkSize, effReasoning, overrides, blockOutcome?.ordered);
1019
- 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("");
1020
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;
1021
1442
  shapeORChunks(chunks, usageChunk, overrides);
1022
1443
  const interruption = createInterruptionSignal(fixture);
@@ -1026,7 +1447,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1026
1447
  signal: interruption?.signal,
1027
1448
  onChunkSent: interruption?.tick,
1028
1449
  usageChunk,
1029
- recordedTimings: fixture.recordedTimings,
1450
+ recordedTimings: resolveRecordedTimings(chunks),
1030
1451
  replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1031
1452
  openRouterProcessing
1032
1453
  })) {
@@ -1042,7 +1463,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1042
1463
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
1043
1464
  const overrides = extractOverrides(response);
1044
1465
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1045
- const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1466
+ const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1046
1467
  const journalEntry = journal.add({
1047
1468
  method: req.method ?? "POST",
1048
1469
  path: req.url ?? COMPLETIONS_PATH,
@@ -1053,6 +1474,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1053
1474
  fixture
1054
1475
  }
1055
1476
  });
1477
+ recordOutcome(journalEntry);
1056
1478
  if (body.stream !== true) {
1057
1479
  const completion = buildTextCompletion(response.content, responseModel, effReasoning, overrides, body.messages);
1058
1480
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -1068,7 +1490,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1068
1490
  signal: interruption?.signal,
1069
1491
  onChunkSent: interruption?.tick,
1070
1492
  usageChunk,
1071
- recordedTimings: fixture.recordedTimings,
1493
+ recordedTimings: resolveRecordedTimings(chunks),
1072
1494
  replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1073
1495
  openRouterProcessing
1074
1496
  })) {
@@ -1084,7 +1506,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1084
1506
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
1085
1507
  const overrides = extractOverrides(response);
1086
1508
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1087
- const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1509
+ const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1088
1510
  const journalEntry = journal.add({
1089
1511
  method: req.method ?? "POST",
1090
1512
  path: req.url ?? COMPLETIONS_PATH,
@@ -1095,13 +1517,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1095
1517
  fixture
1096
1518
  }
1097
1519
  });
1520
+ recordOutcome(journalEntry);
1521
+ const toolCalls = requireServedFunctionToolCalls(response, wire);
1098
1522
  if (body.stream !== true) {
1099
- const completion = buildToolCallCompletion(response.toolCalls, responseModel, effReasoning, overrides, body.messages);
1523
+ const completion = buildToolCallCompletion(toolCalls, responseModel, effReasoning, overrides, body.messages);
1100
1524
  res.writeHead(200, { "Content-Type": "application/json" });
1101
1525
  res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1102
1526
  } else {
1103
- const chunks = buildToolCallChunks(response.toolCalls, responseModel, chunkSize, effReasoning, overrides);
1104
- 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("");
1105
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;
1106
1530
  shapeORChunks(chunks, usageChunk, overrides);
1107
1531
  const interruption = createInterruptionSignal(fixture);
@@ -1111,7 +1535,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1111
1535
  signal: interruption?.signal,
1112
1536
  onChunkSent: interruption?.tick,
1113
1537
  usageChunk,
1114
- recordedTimings: fixture.recordedTimings,
1538
+ recordedTimings: resolveRecordedTimings(chunks),
1115
1539
  replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1116
1540
  openRouterProcessing
1117
1541
  })) {
@@ -1123,7 +1547,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1123
1547
  }
1124
1548
  return;
1125
1549
  }
1126
- journal.add({
1550
+ recordOutcome(journal.add({
1127
1551
  method: req.method ?? "POST",
1128
1552
  path: req.url ?? COMPLETIONS_PATH,
1129
1553
  headers: flattenHeaders(req.headers),
@@ -1132,7 +1556,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1132
1556
  status: 500,
1133
1557
  fixture
1134
1558
  }
1135
- });
1559
+ }));
1136
1560
  writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, "Fixture response did not match any known type") : JSON.stringify({ error: {
1137
1561
  message: "Fixture response did not match any known type",
1138
1562
  type: "server_error"
@@ -1146,18 +1570,101 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1146
1570
  }
1147
1571
  /** @internal Config startup uses this to avoid parsing a selected auth source twice. */
1148
1572
  async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mounts, serviceFixtures) {
1573
+ const logger = new Logger(options?.logLevel ?? "silent");
1574
+ const mountList = mounts ?? [];
1575
+ const handOff = serviceFixtures?.mcpFakes && serviceFixtures.mcpFakes.length > 0 ? handOffMcpFakes(mountList, serviceFixtures.mcpFakes, logger) : null;
1576
+ try {
1577
+ return await startServer(fixtures, options, resolvedAuth, mountList, logger, serviceFixtures, () => handOff?.commit());
1578
+ } catch (err) {
1579
+ handOff?.undo();
1580
+ throw err;
1581
+ }
1582
+ }
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
+ /**
1608
+ * The server of `createServerWithResolvedAuth`, after the MCP fakes hand-off.
1609
+ * `commitHandOff` runs once the server listens; if it throws, the server is
1610
+ * closed and start rejects.
1611
+ */
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();
1149
1628
  const host = options?.host ?? "127.0.0.1";
1150
1629
  const port = options?.port ?? 0;
1151
- const logger = new Logger(options?.logLevel ?? "silent");
1152
1630
  const registry = options?.metrics ? createMetricsRegistry() : void 0;
1153
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);
1154
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
+ }
1155
1658
  const defaults = {
1156
1659
  latency: serverOptions.latency ?? 0,
1157
1660
  chunkSize: Math.max(1, serverOptions.chunkSize ?? DEFAULT_CHUNK_SIZE),
1158
1661
  replaySpeed: serverOptions.replaySpeed ?? 1,
1159
1662
  logger,
1160
1663
  chaosByTestId,
1664
+ misbehavior: misbehaviorEnabled ? misbehavior : void 0,
1665
+ get misbehaviorCounters() {
1666
+ return journal;
1667
+ },
1161
1668
  get chaos() {
1162
1669
  return {
1163
1670
  base: chaosByTestId.get(DEFAULT_TEST_ID) ?? serverOptions.chaos,
@@ -1180,6 +1687,13 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1180
1687
  get strict() {
1181
1688
  return serverOptions.strict;
1182
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
+ },
1183
1697
  get requestTransform() {
1184
1698
  return serverOptions.requestTransform;
1185
1699
  },
@@ -1270,6 +1784,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1270
1784
  if (req) ownJournalEntry.set(req, entry);
1271
1785
  }
1272
1786
  });
1787
+ if (serverOptions.responsesTools === "extended") journal.configureFixtureViewSource(responsesToolsViewSource);
1273
1788
  const videoStates = new VideoStateMap();
1274
1789
  const openRouterVideoJobs = new OpenRouterVideoJobMap();
1275
1790
  const veoVideoJobs = new VeoVideoJobMap();
@@ -1284,6 +1799,85 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1284
1799
  return status >= 400 && status < 500 ? "invalid_request_error" : "server_error";
1285
1800
  }
1286
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
+ /**
1287
1881
  * The newest journal entry this request already produced, or null.
1288
1882
  *
1289
1883
  * Attribution is by the identity of the `IncomingMessage` the entry was
@@ -1334,6 +1928,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1334
1928
  function routeError(req, res, err, pathname, opts) {
1335
1929
  const route = `${req.method ?? "?"} ${pathname}`;
1336
1930
  const msg = err instanceof Error ? err.message : "Internal error";
1931
+ const invalidToolArguments = isFixtureToolCallError(err);
1337
1932
  const clientFault = err instanceof RequestBodyTooLargeError;
1338
1933
  const clientAbort = clientAbortedMidBody(req, res, err);
1339
1934
  const status = clientFault ? 400 : 500;
@@ -1374,8 +1969,10 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1374
1969
  return;
1375
1970
  }
1376
1971
  if (!res.headersSent) {
1377
- if (existing) existing.response.status = status;
1378
- else try {
1972
+ if (existing) {
1973
+ existing.response.status = status;
1974
+ if (invalidToolArguments) existing.response.error = msg;
1975
+ } else try {
1379
1976
  journal.add({
1380
1977
  method: req.method ?? "?",
1381
1978
  path: req.url ?? "?",
@@ -1385,14 +1982,15 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1385
1982
  response: {
1386
1983
  status,
1387
1984
  fixture: null,
1388
- source: "internal"
1985
+ source: "internal",
1986
+ ...invalidToolArguments ? { error: msg } : {}
1389
1987
  }
1390
1988
  });
1391
1989
  } catch (jErr) {
1392
1990
  logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1393
1991
  }
1394
1992
  setCorsHeaders(res);
1395
- 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: {
1396
1994
  message: msg,
1397
1995
  type: errorTypeForStatus(status)
1398
1996
  } }));
@@ -1437,16 +2035,21 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1437
2035
  else res.destroy();
1438
2036
  }
1439
2037
  }
1440
- if (mounts) for (const { handler } of mounts) {
2038
+ for (const { handler } of mountList) {
1441
2039
  if (handler.setJournal) handler.setJournal(journal);
1442
2040
  if (registry && handler.setRegistry) handler.setRegistry(registry);
2041
+ if (handler.setLogger) handler.setLogger(logger);
2042
+ if (handler instanceof MCPMock) handler.setReplaySpeed(defaults.replaySpeed);
1443
2043
  }
1444
2044
  if (registry) registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
1445
2045
  const server = http$1.createServer((req, res) => {
1446
2046
  requestScope.run(req, () => {
1447
- handleHttpRequest(req, res).catch((err) => {
2047
+ runWithToolArgumentsScope({
2048
+ strict: defaults.strictToolArguments === true,
2049
+ logger
2050
+ }, () => handleHttpRequest(req, res).catch((err) => {
1448
2051
  routeError(req, res, err, req.url ?? "?");
1449
- });
2052
+ }));
1450
2053
  });
1451
2054
  });
1452
2055
  async function handleHttpRequest(req, res) {
@@ -1462,7 +2065,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1462
2065
  if (recorded) return;
1463
2066
  recorded = true;
1464
2067
  try {
1465
- const normalizedPath = normalizePathLabel(pathname, mounts ? mounts.map((m) => m.path) : []);
2068
+ const normalizedPath = normalizePathLabel(pathname, mountList.map((m) => m.path));
1466
2069
  const method = req.method ?? "UNKNOWN";
1467
2070
  registry.incrementCounter("aimock_requests_total", {
1468
2071
  method,
@@ -1483,7 +2086,36 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1483
2086
  if (!res.writableFinished) recordOutcome(DESTROYED_STATUS_LABEL);
1484
2087
  });
1485
2088
  }
1486
- const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
2089
+ let parsedUrl;
2090
+ try {
2091
+ parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
2092
+ } catch (err) {
2093
+ const route = `${req.method ?? "?"} ${req.url ?? "?"}`;
2094
+ const msg = err instanceof Error ? err.message : "Internal error";
2095
+ logger.error(`${route}: ${msg}`);
2096
+ if (err instanceof Error && err.stack) logger.debug(err.stack);
2097
+ try {
2098
+ journal.add({
2099
+ method: req.method ?? "?",
2100
+ path: req.url ?? "?",
2101
+ headers: flattenHeaders(req.headers),
2102
+ body: null,
2103
+ response: {
2104
+ status: 400,
2105
+ fixture: null,
2106
+ source: "internal"
2107
+ }
2108
+ });
2109
+ } catch (jErr) {
2110
+ logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
2111
+ }
2112
+ setCorsHeaders(res);
2113
+ writeErrorResponse(res, 400, JSON.stringify({ error: {
2114
+ message: "Invalid request target",
2115
+ type: "invalid_request_error"
2116
+ } }));
2117
+ return;
2118
+ }
1487
2119
  pathname = parsedUrl.pathname;
1488
2120
  const originalPathname = pathname;
1489
2121
  const isOpenRouter = isOpenRouterPath(originalPathname);
@@ -1492,7 +2124,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1492
2124
  handleOptions(res);
1493
2125
  return;
1494
2126
  }
1495
- if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname === "/metrics"))) {
2127
+ if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname === METRICS_PATH))) {
1496
2128
  if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
1497
2129
  writeApiKeyHttpRejection(res, setCorsHeaders);
1498
2130
  return;
@@ -1500,18 +2132,40 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1500
2132
  markAuthenticatedRequest(req, resolvedAuth.policy);
1501
2133
  }
1502
2134
  if (pathname.startsWith(CONTROL_PREFIX)) {
1503
- await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults);
2135
+ ensureRawFixturePositions();
2136
+ await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mountList);
1504
2137
  return;
1505
2138
  }
1506
- if (mounts) {
1507
- for (const { path: mountPath, handler } of mounts) if (pathname === mountPath || pathname.startsWith(mountPath + "/")) {
1508
- const subPath = pathname.slice(mountPath.length) || "/";
1509
- if (await handler.handleRequest(req, res, subPath)) return;
1510
- }
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
+ }
2160
+ for (const { path: mountPath, handler } of mountList) if (pathname === mountPath || pathname.startsWith(mountPath + "/")) {
2161
+ const subPath = pathname.slice(mountPath.length) || "/";
2162
+ if (await handler.handleRequest(req, res, subPath)) return;
1511
2163
  }
1512
2164
  if (pathname === OLLAMA_CHAT_PATH && req.method === "POST") {
1513
2165
  try {
1514
- 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);
1515
2169
  } catch (err) {
1516
2170
  routeError(req, res, err, pathname);
1517
2171
  }
@@ -1519,7 +2173,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1519
2173
  }
1520
2174
  if (pathname === OLLAMA_GENERATE_PATH && req.method === "POST") {
1521
2175
  try {
1522
- 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);
1523
2179
  } catch (err) {
1524
2180
  routeError(req, res, err, pathname);
1525
2181
  }
@@ -1527,7 +2183,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1527
2183
  }
1528
2184
  if ((pathname === OLLAMA_EMBEDDINGS_PATH || pathname === OLLAMA_EMBED_PATH) && req.method === "POST") {
1529
2185
  try {
1530
- 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);
1531
2189
  } catch (err) {
1532
2190
  routeError(req, res, err, pathname);
1533
2191
  }
@@ -1560,6 +2218,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1560
2218
  }
1561
2219
  if (pathname === OPENROUTER_VIDEO_MODELS_PATH && req.method === "GET") {
1562
2220
  try {
2221
+ ensureRawFixturePositions();
1563
2222
  await handleOpenRouterVideoModels(req, res, fixtures, journal, defaults, setCorsHeaders);
1564
2223
  } catch (err) {
1565
2224
  routeError(req, res, err, pathname);
@@ -1569,6 +2228,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1569
2228
  const openRouterVideoStatusMatch = pathname.match(OPENROUTER_VIDEO_STATUS_RE);
1570
2229
  if (openRouterVideoStatusMatch && req.method === "GET") {
1571
2230
  try {
2231
+ ensureRawFixturePositions();
1572
2232
  await handleOpenRouterVideoStatus(req, res, openRouterVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
1573
2233
  } catch (err) {
1574
2234
  routeError(req, res, err, pathname);
@@ -1578,7 +2238,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1578
2238
  if (pathname === OPENROUTER_VIDEOS_PATH && req.method === "POST") {
1579
2239
  setCorsHeaders(res);
1580
2240
  try {
1581
- 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);
1582
2244
  } catch (err) {
1583
2245
  routeError(req, res, err, pathname);
1584
2246
  }
@@ -1587,6 +2249,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1587
2249
  const bytePlusVideoStatusMatch = pathname.match(BYTEPLUS_VIDEO_STATUS_RE);
1588
2250
  if (bytePlusVideoStatusMatch && req.method === "GET") {
1589
2251
  try {
2252
+ ensureRawFixturePositions();
1590
2253
  await handleBytePlusVideoStatus(req, res, bytePlusVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
1591
2254
  } catch (err) {
1592
2255
  routeError(req, res, err, pathname);
@@ -1596,21 +2259,24 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1596
2259
  if (BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname) && req.method === "POST") {
1597
2260
  setCorsHeaders(res);
1598
2261
  try {
1599
- 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);
1600
2265
  } catch (err) {
1601
2266
  routeError(req, res, err, pathname);
1602
2267
  }
1603
2268
  return;
1604
2269
  }
1605
- if (pathname === "/api/v1/models" && req.method === "GET") {
2270
+ if (pathname === OPENROUTER_MODELS_PATH && req.method === "GET") {
2271
+ ensureRawFixturePositions();
1606
2272
  handleOpenRouterModels(req, res, fixtures, journal, defaults, setCorsHeaders);
1607
2273
  return;
1608
2274
  }
1609
- if (pathname === "/api/v1/key" && req.method === "GET") {
2275
+ if (pathname === OPENROUTER_KEY_PATH && req.method === "GET") {
1610
2276
  handleOpenRouterKey(req, res, journal, setCorsHeaders);
1611
2277
  return;
1612
2278
  }
1613
- if (pathname === "/api/v1/credits" && req.method === "GET") {
2279
+ if (pathname === OPENROUTER_CREDITS_PATH && req.method === "GET") {
1614
2280
  handleOpenRouterCredits(req, res, journal, setCorsHeaders);
1615
2281
  return;
1616
2282
  }
@@ -1623,12 +2289,12 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1623
2289
  if (!azureDeploymentId) pathname = normalizeCompatPath(pathname, logger);
1624
2290
  if (pathname === HEALTH_PATH && req.method === "GET") {
1625
2291
  setCorsHeaders(res);
1626
- if (mounts && mounts.length > 0) {
2292
+ if (mountList.length > 0) {
1627
2293
  const services = { llm: {
1628
2294
  status: "ok",
1629
2295
  fixtures: fixtures.length
1630
2296
  } };
1631
- for (const { path: mountPath, handler } of mounts) if (handler.health) {
2297
+ for (const { path: mountPath, handler } of mountList) if (handler.health) {
1632
2298
  const name = mountPath.replace(/^\//, "");
1633
2299
  services[name] = handler.health();
1634
2300
  }
@@ -1649,7 +2315,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1649
2315
  res.end(JSON.stringify({ status: "ready" }));
1650
2316
  return;
1651
2317
  }
1652
- if (pathname === "/metrics" && req.method === "GET") {
2318
+ if (pathname === METRICS_PATH && req.method === "GET") {
1653
2319
  if (!registry) {
1654
2320
  handleNotFound(res, "Not found");
1655
2321
  return;
@@ -1797,14 +2463,140 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1797
2463
  }
1798
2464
  return;
1799
2465
  }
2466
+ const vsSearchMatch = pathname.match(VECTOR_STORES_SEARCH_RE);
2467
+ if (vsSearchMatch && req.method === "POST") {
2468
+ try {
2469
+ const raw = await readBody(req);
2470
+ await handleVectorStoresSearch(req, res, vsSearchMatch[1], raw, journal, defaults, setCorsHeaders);
2471
+ } catch (err) {
2472
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2473
+ }
2474
+ return;
2475
+ }
2476
+ const vsBatchFilesMatch = pathname.match(VECTOR_STORES_BATCH_FILES_RE);
2477
+ if (vsBatchFilesMatch && req.method === "GET") {
2478
+ try {
2479
+ await handleVectorFileBatchesFiles(req, res, vsBatchFilesMatch[1], vsBatchFilesMatch[2], journal, defaults, setCorsHeaders);
2480
+ } catch (err) {
2481
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2482
+ }
2483
+ return;
2484
+ }
2485
+ const vsBatchCancelMatch = pathname.match(VECTOR_STORES_BATCH_CANCEL_RE);
2486
+ if (vsBatchCancelMatch && req.method === "POST") {
2487
+ try {
2488
+ await readBody(req);
2489
+ await handleVectorFileBatchesCancel(req, res, vsBatchCancelMatch[1], vsBatchCancelMatch[2], journal, defaults, setCorsHeaders);
2490
+ } catch (err) {
2491
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2492
+ }
2493
+ return;
2494
+ }
2495
+ const vsBatchMatch = pathname.match(VECTOR_STORES_BATCH_RE);
2496
+ if (vsBatchMatch && req.method === "GET") {
2497
+ try {
2498
+ await handleVectorFileBatchesRetrieve(req, res, vsBatchMatch[1], vsBatchMatch[2], journal, defaults, setCorsHeaders);
2499
+ } catch (err) {
2500
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2501
+ }
2502
+ return;
2503
+ }
2504
+ const vsFileMatch = pathname.match(VECTOR_STORES_FILE_RE);
2505
+ if (vsFileMatch && req.method === "GET") {
2506
+ try {
2507
+ await handleVectorStoreFilesRetrieve(req, res, vsFileMatch[1], vsFileMatch[2], journal, defaults, setCorsHeaders);
2508
+ } catch (err) {
2509
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2510
+ }
2511
+ return;
2512
+ }
2513
+ if (vsFileMatch && req.method === "DELETE") {
2514
+ try {
2515
+ await handleVectorStoreFilesDelete(req, res, vsFileMatch[1], vsFileMatch[2], journal, defaults, setCorsHeaders);
2516
+ } catch (err) {
2517
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2518
+ }
2519
+ return;
2520
+ }
2521
+ const vsIdMatch = pathname.match(VECTOR_STORES_ID_RE);
2522
+ if (vsIdMatch && req.method === "GET") {
2523
+ try {
2524
+ await handleVectorStoresRetrieve(req, res, vsIdMatch[1], journal, defaults, setCorsHeaders);
2525
+ } catch (err) {
2526
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2527
+ }
2528
+ return;
2529
+ }
2530
+ if (vsIdMatch && req.method === "POST") {
2531
+ try {
2532
+ const raw = await readBody(req);
2533
+ await handleVectorStoresModify(req, res, vsIdMatch[1], raw, journal, defaults, setCorsHeaders);
2534
+ } catch (err) {
2535
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2536
+ }
2537
+ return;
2538
+ }
2539
+ if (vsIdMatch && req.method === "DELETE") {
2540
+ try {
2541
+ await handleVectorStoresDelete(req, res, vsIdMatch[1], journal, defaults, setCorsHeaders);
2542
+ } catch (err) {
2543
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2544
+ }
2545
+ return;
2546
+ }
2547
+ if (VECTOR_STORES_FILES_RE.test(pathname) && req.method === "POST") {
2548
+ try {
2549
+ const raw = await readBody(req);
2550
+ const storeId = pathname.split("/")[3];
2551
+ await handleVectorStoreFilesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders);
2552
+ } catch (err) {
2553
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2554
+ }
2555
+ return;
2556
+ }
2557
+ if (VECTOR_STORES_FILES_RE.test(pathname) && req.method === "GET") {
2558
+ try {
2559
+ const storeId = pathname.split("/")[3];
2560
+ await handleVectorStoreFilesList(req, res, storeId, journal, defaults, setCorsHeaders);
2561
+ } catch (err) {
2562
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2563
+ }
2564
+ return;
2565
+ }
2566
+ if (VECTOR_STORES_FILE_BATCHES_RE.test(pathname) && req.method === "POST") {
2567
+ try {
2568
+ const raw = await readBody(req);
2569
+ const storeId = pathname.split("/")[3];
2570
+ await handleVectorFileBatchesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders);
2571
+ } catch (err) {
2572
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2573
+ }
2574
+ return;
2575
+ }
2576
+ if (pathname === VECTOR_STORES_PATH && req.method === "GET") {
2577
+ try {
2578
+ await handleVectorStoresList(req, res, journal, defaults, setCorsHeaders);
2579
+ } catch (err) {
2580
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2581
+ }
2582
+ return;
2583
+ }
2584
+ if (pathname === VECTOR_STORES_PATH && req.method === "POST") {
2585
+ try {
2586
+ await handleVectorStoresCreate(req, res, await readBody(req), journal, defaults, setCorsHeaders);
2587
+ } catch (err) {
2588
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2589
+ }
2590
+ return;
2591
+ }
1800
2592
  if (pathname === REQUESTS_PATH) {
1801
2593
  setCorsHeaders(res);
1802
2594
  if (req.method === "GET") {
1803
2595
  const limitParam = parsedUrl.searchParams.get("limit");
1804
2596
  let opts;
1805
2597
  if (limitParam) {
1806
- const limit = parseInt(limitParam, 10);
1807
- if (Number.isNaN(limit) || limit <= 0) {
2598
+ const limit = Number(limitParam);
2599
+ if (!/^\+?\d+$/.test(limitParam.trim()) || !Number.isSafeInteger(limit) || limit <= 0) {
1808
2600
  writeErrorResponse(res, 400, JSON.stringify({ error: {
1809
2601
  message: `Invalid limit parameter: "${limitParam}"`,
1810
2602
  type: "invalid_request_error"
@@ -1815,7 +2607,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1815
2607
  }
1816
2608
  const entries = journal.getAll(opts);
1817
2609
  res.writeHead(200, { "Content-Type": "application/json" });
1818
- res.end(JSON.stringify(entries));
2610
+ res.end(journalJson(entries, journal));
1819
2611
  return;
1820
2612
  }
1821
2613
  if (req.method === "DELETE") {
@@ -1829,7 +2621,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1829
2621
  }
1830
2622
  if (pathname === RESPONSES_PATH && req.method === "POST") {
1831
2623
  try {
1832
- 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);
1833
2627
  } catch (err) {
1834
2628
  routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
1835
2629
  }
@@ -1837,7 +2631,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1837
2631
  }
1838
2632
  if (pathname === MESSAGES_PATH && req.method === "POST") {
1839
2633
  try {
1840
- 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);
1841
2637
  } catch (err) {
1842
2638
  routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
1843
2639
  }
@@ -1845,7 +2641,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1845
2641
  }
1846
2642
  if (pathname === COHERE_CHAT_PATH && req.method === "POST") {
1847
2643
  try {
1848
- 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);
1849
2647
  } catch (err) {
1850
2648
  routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
1851
2649
  }
@@ -1853,7 +2651,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1853
2651
  }
1854
2652
  if (pathname === COHERE_EMBED_PATH && req.method === "POST") {
1855
2653
  try {
1856
- 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);
1857
2657
  } catch (err) {
1858
2658
  routeError(req, res, err, pathname);
1859
2659
  }
@@ -1873,7 +2673,8 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1873
2673
  } catch (err) {
1874
2674
  if (!(err instanceof SyntaxError)) defaults.logger.error(`Unexpected error in Azure model injection: ${err instanceof Error ? err.message : String(err)}`);
1875
2675
  }
1876
- await handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider);
2676
+ ensureRawFixturePositions();
2677
+ await handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider, originalPathname === EMBEDDINGS_PATH);
1877
2678
  } catch (err) {
1878
2679
  routeError(req, res, err, pathname);
1879
2680
  }
@@ -1881,7 +2682,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1881
2682
  }
1882
2683
  if (pathname === IMAGES_PATH && req.method === "POST") {
1883
2684
  try {
1884
- 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);
1885
2688
  } catch (err) {
1886
2689
  routeError(req, res, err, pathname);
1887
2690
  }
@@ -1889,7 +2692,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1889
2692
  }
1890
2693
  if (pathname === IMAGES_EDIT_PATH && req.method === "POST") {
1891
2694
  try {
1892
- 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);
1893
2698
  } catch (err) {
1894
2699
  routeError(req, res, err, pathname);
1895
2700
  }
@@ -1897,7 +2702,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1897
2702
  }
1898
2703
  if (pathname === IMAGES_VARIATIONS_PATH && req.method === "POST") {
1899
2704
  try {
1900
- 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);
1901
2708
  } catch (err) {
1902
2709
  routeError(req, res, err, pathname);
1903
2710
  }
@@ -1905,7 +2712,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1905
2712
  }
1906
2713
  if (pathname === SPEECH_PATH && req.method === "POST") {
1907
2714
  try {
1908
- 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);
1909
2718
  } catch (err) {
1910
2719
  routeError(req, res, err, pathname);
1911
2720
  }
@@ -1913,7 +2722,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1913
2722
  }
1914
2723
  if (pathname === TRANSCRIPTIONS_PATH && req.method === "POST") {
1915
2724
  try {
1916
- 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);
1917
2728
  } catch (err) {
1918
2729
  routeError(req, res, err, pathname);
1919
2730
  }
@@ -1921,7 +2732,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1921
2732
  }
1922
2733
  if (pathname === TRANSLATIONS_PATH && req.method === "POST") {
1923
2734
  try {
1924
- 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");
1925
2738
  } catch (err) {
1926
2739
  routeError(req, res, err, pathname);
1927
2740
  }
@@ -1930,7 +2743,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1930
2743
  if (pathname === GROK_VIDEO_SUBMIT_PATH && req.method === "POST") {
1931
2744
  setCorsHeaders(res);
1932
2745
  try {
1933
- 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);
1934
2749
  } catch (err) {
1935
2750
  routeError(req, res, err, pathname);
1936
2751
  }
@@ -1938,7 +2753,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1938
2753
  }
1939
2754
  if (pathname === VIDEOS_PATH && req.method === "POST") {
1940
2755
  try {
1941
- 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);
1942
2759
  } catch (err) {
1943
2760
  routeError(req, res, err, pathname);
1944
2761
  }
@@ -1947,6 +2764,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1947
2764
  const grokVideoStatusMatch = pathname.match(GROK_VIDEO_STATUS_RE);
1948
2765
  if (grokVideoStatusMatch && grokVideoStatusMatch[1] !== "generations" && req.method === "GET") {
1949
2766
  try {
2767
+ ensureRawFixturePositions();
1950
2768
  await handleGrokVideoStatus(req, res, grokVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, grokVideoJobs, videoStates);
1951
2769
  } catch (err) {
1952
2770
  routeError(req, res, err, pathname);
@@ -1957,7 +2775,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1957
2775
  if (veoPredictMatch && req.method === "POST") {
1958
2776
  setCorsHeaders(res);
1959
2777
  try {
1960
- 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);
1961
2781
  } catch (err) {
1962
2782
  routeError(req, res, err, pathname);
1963
2783
  }
@@ -1966,6 +2786,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1966
2786
  const veoOperationMatch = pathname.match(VEO_OPERATION_RE);
1967
2787
  if (veoOperationMatch && req.method === "GET") {
1968
2788
  try {
2789
+ ensureRawFixturePositions();
1969
2790
  await handleVeoVideoStatus(req, res, veoOperationMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
1970
2791
  } catch (err) {
1971
2792
  routeError(req, res, err, pathname);
@@ -1976,7 +2797,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1976
2797
  if (geminiPredictMatch && req.method === "POST") {
1977
2798
  const predictModel = geminiPredictMatch[1];
1978
2799
  try {
1979
- 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);
1980
2803
  } catch (err) {
1981
2804
  routeError(req, res, err, pathname);
1982
2805
  }
@@ -1984,7 +2807,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1984
2807
  }
1985
2808
  if (pathname === GEMINI_INTERACTIONS_PATH && req.method === "POST") {
1986
2809
  try {
1987
- 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);
1988
2813
  } catch (err) {
1989
2814
  routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
1990
2815
  }
@@ -1994,7 +2819,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1994
2819
  if (geminiEmbedMatch && req.method === "POST") {
1995
2820
  const embedModel = geminiEmbedMatch[1];
1996
2821
  try {
1997
- 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);
1998
2825
  } catch (err) {
1999
2826
  routeError(req, res, err, pathname);
2000
2827
  }
@@ -2005,7 +2832,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2005
2832
  const geminiModel = geminiMatch[1];
2006
2833
  const streaming = geminiMatch[2] === "streamGenerateContent";
2007
2834
  try {
2008
- 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);
2009
2838
  } catch (err) {
2010
2839
  routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
2011
2840
  }
@@ -2016,7 +2845,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2016
2845
  const vertexModel = vertexMatch[1];
2017
2846
  const streaming = vertexMatch[2] === "streamGenerateContent";
2018
2847
  try {
2019
- 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");
2020
2851
  } catch (err) {
2021
2852
  routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
2022
2853
  }
@@ -2026,7 +2857,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2026
2857
  if (bedrockMatch && req.method === "POST") {
2027
2858
  const bedrockModelId = bedrockMatch[1];
2028
2859
  try {
2029
- 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);
2030
2863
  } catch (err) {
2031
2864
  routeError(req, res, err, pathname);
2032
2865
  }
@@ -2036,7 +2869,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2036
2869
  if (bedrockStreamMatch && req.method === "POST") {
2037
2870
  const bedrockModelId = bedrockStreamMatch[1];
2038
2871
  try {
2039
- 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);
2040
2875
  } catch (err) {
2041
2876
  routeError(req, res, err, pathname);
2042
2877
  }
@@ -2046,7 +2881,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2046
2881
  if (converseMatch && req.method === "POST") {
2047
2882
  const converseModelId = converseMatch[1];
2048
2883
  try {
2049
- 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);
2050
2887
  } catch (err) {
2051
2888
  routeError(req, res, err, pathname);
2052
2889
  }
@@ -2056,7 +2893,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2056
2893
  if (converseStreamMatch && req.method === "POST") {
2057
2894
  const converseStreamModelId = converseStreamMatch[1];
2058
2895
  try {
2059
- 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);
2060
2899
  } catch (err) {
2061
2900
  routeError(req, res, err, pathname);
2062
2901
  }
@@ -2089,7 +2928,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2089
2928
  if (pathname === ELEVENLABS_SOUND_GENERATION_PATH && req.method === "POST") {
2090
2929
  setCorsHeaders(res);
2091
2930
  try {
2092
- 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");
2093
2934
  } catch (err) {
2094
2935
  routeError(req, res, err, pathname);
2095
2936
  }
@@ -2098,7 +2939,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2098
2939
  if (pathname === ELEVENLABS_VOICE_DESIGN_PATH && req.method === "POST") {
2099
2940
  setCorsHeaders(res);
2100
2941
  try {
2101
- 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);
2102
2945
  } catch (err) {
2103
2946
  routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2104
2947
  }
@@ -2107,7 +2950,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2107
2950
  if (pathname === ELEVENLABS_VOICE_CREATE_PATH && req.method === "POST") {
2108
2951
  setCorsHeaders(res);
2109
2952
  try {
2110
- 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);
2111
2956
  } catch (err) {
2112
2957
  routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2113
2958
  }
@@ -2138,8 +2983,13 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2138
2983
  return;
2139
2984
  }
2140
2985
  try {
2141
- if (req.method === "GET") await handleElevenLabsVoiceGet(req, res, voiceId, fixtures, defaults, journal);
2142
- 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
+ }
2143
2993
  } catch (err) {
2144
2994
  routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2145
2995
  }
@@ -2150,7 +3000,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2150
3000
  setCorsHeaders(res);
2151
3001
  const voiceId = elevenLabsTTSMatch[1];
2152
3002
  try {
2153
- 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);
2154
3006
  } catch (err) {
2155
3007
  routeError(req, res, err, pathname);
2156
3008
  }
@@ -2161,7 +3013,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2161
3013
  setCorsHeaders(res);
2162
3014
  const musicSubType = musicMatch[1] ?? "music";
2163
3015
  try {
2164
- 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);
2165
3019
  } catch (err) {
2166
3020
  routeError(req, res, err, pathname);
2167
3021
  }
@@ -2192,6 +3046,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2192
3046
  return;
2193
3047
  }
2194
3048
  }
3049
+ ensureRawFixturePositions();
2195
3050
  if (await handleFal(req, res, raw, pathname, fixtures, defaults, journal) === "handled") return;
2196
3051
  } catch (err) {
2197
3052
  routeError(req, res, err, pathname);
@@ -2201,7 +3056,9 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2201
3056
  if (pathname.match(FAL_QUEUE_SUBMIT_RE) && req.method === "POST") {
2202
3057
  setCorsHeaders(res);
2203
3058
  try {
2204
- 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);
2205
3062
  } catch (err) {
2206
3063
  routeError(req, res, err, pathname);
2207
3064
  }
@@ -2228,6 +3085,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2228
3085
  }, "internal", defaults.registry, defaults.logger);
2229
3086
  return;
2230
3087
  }
3088
+ ensureRawFixturePositions();
2231
3089
  await handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
2232
3090
  } catch (err) {
2233
3091
  routeError(req, res, err, pathname);
@@ -2237,13 +3095,17 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2237
3095
  if (pathname.match(FAL_RUN_RE) && req.method === "POST") {
2238
3096
  setCorsHeaders(res);
2239
3097
  try {
2240
- 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);
2241
3101
  } catch (err) {
2242
3102
  routeError(req, res, err, pathname);
2243
3103
  }
2244
3104
  return;
2245
3105
  }
2246
3106
  if (pathname !== COMPLETIONS_PATH) {
3107
+ const catalogMatch = matchRouteDefinition(req.method ?? "", pathname);
3108
+ if (catalogMatch && !(pathname === METRICS_PATH && !registry)) defaults.logger.warn(`Route drift: ${req.method ?? "?"} ${pathname} matches the route catalog (${catalogMatch.method} ${catalogMatch.path} — ${catalogMatch.description}) but no dispatcher branch served it. The catalog entry or the dispatcher is stale — see route-registry.ts.`);
2247
3109
  handleNotFound(res, "Not found");
2248
3110
  return;
2249
3111
  }
@@ -2253,13 +3115,14 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2253
3115
  }
2254
3116
  const completionsProvider = azureDeploymentId ? "azure" : isOpenRouter ? "openrouter" : isBytePlusArk ? "byteplus" : "openai";
2255
3117
  try {
2256
- 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/"));
2257
3119
  } catch (err) {
2258
3120
  routeError(req, res, err, pathname, {
2259
3121
  envelope: (m, status) => isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
2260
3122
  message: m,
2261
3123
  type: errorTypeForStatus(status)
2262
3124
  } }),
3125
+ ...isOpenRouter ? { toolCallEnvelope: (m, code) => serializeOpenRouterError(500, m, { reason: code }) } : {},
2263
3126
  streamEvent: (m, status) => `data: ${isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
2264
3127
  message: m,
2265
3128
  type: errorTypeForStatus(status)
@@ -2323,17 +3186,23 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2323
3186
  server.emit("connection", socket);
2324
3187
  return;
2325
3188
  }
2326
- const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
3189
+ let parsedUrl;
3190
+ try {
3191
+ parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
3192
+ } catch (err) {
3193
+ const msg = err instanceof Error ? err.message : "Internal error";
3194
+ defaults.logger.warn(`Unhandled upgrade error: ${msg}`);
3195
+ socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
3196
+ return;
3197
+ }
2327
3198
  let pathname = parsedUrl.pathname;
2328
3199
  if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
2329
3200
  writeApiKeyUpgradeRejection(socket);
2330
3201
  return;
2331
3202
  }
2332
- if (mounts) {
2333
- for (const { path: mountPath, handler } of mounts) if ((pathname === mountPath || pathname.startsWith(mountPath + "/")) && handler.handleUpgrade) {
2334
- const subPath = pathname.slice(mountPath.length) || "/";
2335
- if (await handler.handleUpgrade(socket, head, subPath)) return;
2336
- }
3203
+ for (const { path: mountPath, handler } of mountList) if ((pathname === mountPath || pathname.startsWith(mountPath + "/")) && handler.handleUpgrade) {
3204
+ const subPath = pathname.slice(mountPath.length) || "/";
3205
+ if (await handler.handleUpgrade(socket, head, subPath)) return;
2337
3206
  }
2338
3207
  if (!pathname.match(AZURE_DEPLOYMENT_RE)) pathname = normalizeCompatPath(pathname, logger);
2339
3208
  if (pathname !== RESPONSES_PATH && pathname !== REALTIME_PATH && pathname !== GEMINI_LIVE_PATH && pathname !== LIVE_PATH) {
@@ -2341,6 +3210,25 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2341
3210
  socket.destroy();
2342
3211
  return;
2343
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
+ }
2344
3232
  let liveId;
2345
3233
  if (pathname === LIVE_PATH) {
2346
3234
  if (req.method !== "GET" || parsedUrl.searchParams.has("model")) {
@@ -2402,7 +3290,8 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2402
3290
  ...defaults,
2403
3291
  model: "gpt-4",
2404
3292
  testId: wsTestId,
2405
- upgradeHeaders: req.headers
3293
+ upgradeHeaders: req.headers,
3294
+ beforeProcessMessage: ensureRawFixturePositions
2406
3295
  });
2407
3296
  else if (pathname === REALTIME_PATH) {
2408
3297
  const transcriptionIntent = parsedUrl.searchParams.get("intent") === "transcription";
@@ -2412,13 +3301,15 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2412
3301
  model,
2413
3302
  transcriptionIntent,
2414
3303
  testId: wsTestId,
2415
- upgradeHeaders: req.headers
3304
+ upgradeHeaders: req.headers,
3305
+ beforeProcessMessage: ensureRawFixturePositions
2416
3306
  });
2417
3307
  } else if (pathname === GEMINI_LIVE_PATH) handleWebSocketGeminiLive(ws, fixtures, journal, {
2418
3308
  ...defaults,
2419
3309
  model: "gemini-2.0-flash",
2420
3310
  testId: wsTestId,
2421
- upgradeHeaders: req.headers
3311
+ upgradeHeaders: req.headers,
3312
+ beforeProcessMessage: ensureRawFixturePositions
2422
3313
  });
2423
3314
  }
2424
3315
  const originalClose = server.close.bind(server);
@@ -2443,9 +3334,13 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2443
3334
  return;
2444
3335
  }
2445
3336
  const url = `http://${addr.address}:${addr.port}`;
2446
- if (mounts) {
2447
- for (const { path: mountPath, handler } of mounts) if (handler.setBaseUrl) handler.setBaseUrl(url + mountPath);
3337
+ try {
3338
+ commitHandOff();
3339
+ } catch (err) {
3340
+ server.close(() => reject(err));
3341
+ return;
2448
3342
  }
3343
+ for (const { path: mountPath, handler } of mountList) if (handler.setBaseUrl) handler.setBaseUrl(url + mountPath);
2449
3344
  resolve({
2450
3345
  closeLiveSessions,
2451
3346
  server,