@copilotkit/aimock 1.42.1 → 1.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +65 -0
  4. package/README.md +108 -16
  5. package/dist/agui-handler.d.ts.map +1 -1
  6. package/dist/agui-mock.cjs +1 -1
  7. package/dist/agui-mock.cjs.map +1 -1
  8. package/dist/agui-mock.d.cts.map +1 -1
  9. package/dist/agui-mock.d.ts.map +1 -1
  10. package/dist/agui-mock.js +1 -1
  11. package/dist/agui-mock.js.map +1 -1
  12. package/dist/agui-recorder.cjs +33 -11
  13. package/dist/agui-recorder.cjs.map +1 -1
  14. package/dist/agui-recorder.d.cts +10 -1
  15. package/dist/agui-recorder.d.cts.map +1 -1
  16. package/dist/agui-recorder.d.ts +10 -1
  17. package/dist/agui-recorder.d.ts.map +1 -1
  18. package/dist/agui-recorder.js +33 -11
  19. package/dist/agui-recorder.js.map +1 -1
  20. package/dist/agui-types.d.cts +1 -0
  21. package/dist/agui-types.d.cts.map +1 -1
  22. package/dist/agui-types.d.ts +1 -0
  23. package/dist/agui-types.d.ts.map +1 -1
  24. package/dist/aimock-cli.cjs +38 -10
  25. package/dist/aimock-cli.cjs.map +1 -1
  26. package/dist/aimock-cli.d.cts +5 -0
  27. package/dist/aimock-cli.d.cts.map +1 -1
  28. package/dist/aimock-cli.d.ts +5 -0
  29. package/dist/aimock-cli.d.ts.map +1 -1
  30. package/dist/aimock-cli.js +38 -10
  31. package/dist/aimock-cli.js.map +1 -1
  32. package/dist/batches.cjs +458 -0
  33. package/dist/batches.cjs.map +1 -0
  34. package/dist/batches.d.cts +57 -0
  35. package/dist/batches.d.cts.map +1 -0
  36. package/dist/batches.d.ts +57 -0
  37. package/dist/batches.d.ts.map +1 -0
  38. package/dist/batches.js +453 -0
  39. package/dist/batches.js.map +1 -0
  40. package/dist/bedrock-converse.cjs +44 -12
  41. package/dist/bedrock-converse.cjs.map +1 -1
  42. package/dist/bedrock-converse.d.cts.map +1 -1
  43. package/dist/bedrock-converse.d.ts.map +1 -1
  44. package/dist/bedrock-converse.js +46 -14
  45. package/dist/bedrock-converse.js.map +1 -1
  46. package/dist/bedrock.cjs +10 -8
  47. package/dist/bedrock.cjs.map +1 -1
  48. package/dist/bedrock.d.cts.map +1 -1
  49. package/dist/bedrock.d.ts.map +1 -1
  50. package/dist/bedrock.js +12 -10
  51. package/dist/bedrock.js.map +1 -1
  52. package/dist/byteplus-video.cjs +3 -3
  53. package/dist/byteplus-video.cjs.map +1 -1
  54. package/dist/byteplus-video.js +4 -4
  55. package/dist/byteplus-video.js.map +1 -1
  56. package/dist/chaos.cjs +625 -72
  57. package/dist/chaos.cjs.map +1 -1
  58. package/dist/chaos.d.cts +164 -7
  59. package/dist/chaos.d.cts.map +1 -1
  60. package/dist/chaos.d.ts +164 -7
  61. package/dist/chaos.d.ts.map +1 -1
  62. package/dist/chaos.js +616 -74
  63. package/dist/chaos.js.map +1 -1
  64. package/dist/cli.cjs +131 -30
  65. package/dist/cli.cjs.map +1 -1
  66. package/dist/cli.js +132 -31
  67. package/dist/cli.js.map +1 -1
  68. package/dist/cohere.cjs +37 -9
  69. package/dist/cohere.cjs.map +1 -1
  70. package/dist/cohere.d.cts.map +1 -1
  71. package/dist/cohere.d.ts.map +1 -1
  72. package/dist/cohere.js +39 -11
  73. package/dist/cohere.js.map +1 -1
  74. package/dist/config-loader.cjs +1 -1
  75. package/dist/config-loader.js +1 -1
  76. package/dist/constants.cjs +8 -0
  77. package/dist/constants.cjs.map +1 -1
  78. package/dist/constants.d.cts +6 -2
  79. package/dist/constants.d.ts +6 -2
  80. package/dist/constants.js +8 -1
  81. package/dist/constants.js.map +1 -1
  82. package/dist/convert.cjs +1 -1
  83. package/dist/convert.cjs.map +1 -1
  84. package/dist/convert.js +1 -1
  85. package/dist/convert.js.map +1 -1
  86. package/dist/echo-text.cjs +65 -0
  87. package/dist/echo-text.cjs.map +1 -0
  88. package/dist/echo-text.d.cts +22 -0
  89. package/dist/echo-text.d.cts.map +1 -0
  90. package/dist/echo-text.d.ts +22 -0
  91. package/dist/echo-text.d.ts.map +1 -0
  92. package/dist/echo-text.js +65 -0
  93. package/dist/echo-text.js.map +1 -0
  94. package/dist/elevenlabs-audio.cjs +3 -3
  95. package/dist/elevenlabs-audio.cjs.map +1 -1
  96. package/dist/elevenlabs-audio.js +4 -4
  97. package/dist/elevenlabs-audio.js.map +1 -1
  98. package/dist/elevenlabs-voice.cjs +1053 -0
  99. package/dist/elevenlabs-voice.cjs.map +1 -0
  100. package/dist/elevenlabs-voice.d.cts +72 -0
  101. package/dist/elevenlabs-voice.d.cts.map +1 -0
  102. package/dist/elevenlabs-voice.d.ts +72 -0
  103. package/dist/elevenlabs-voice.d.ts.map +1 -0
  104. package/dist/elevenlabs-voice.js +1048 -0
  105. package/dist/elevenlabs-voice.js.map +1 -0
  106. package/dist/embeddings.cjs +26 -5
  107. package/dist/embeddings.cjs.map +1 -1
  108. package/dist/embeddings.d.cts +1 -1
  109. package/dist/embeddings.d.cts.map +1 -1
  110. package/dist/embeddings.d.ts +1 -1
  111. package/dist/embeddings.d.ts.map +1 -1
  112. package/dist/embeddings.js +27 -6
  113. package/dist/embeddings.js.map +1 -1
  114. package/dist/fal-audio.cjs +3 -3
  115. package/dist/fal-audio.cjs.map +1 -1
  116. package/dist/fal-audio.js +4 -4
  117. package/dist/fal-audio.js.map +1 -1
  118. package/dist/fal.cjs +28 -4
  119. package/dist/fal.cjs.map +1 -1
  120. package/dist/fal.d.cts +16 -1
  121. package/dist/fal.d.cts.map +1 -1
  122. package/dist/fal.d.ts +16 -1
  123. package/dist/fal.d.ts.map +1 -1
  124. package/dist/fal.js +28 -5
  125. package/dist/fal.js.map +1 -1
  126. package/dist/files.cjs +1069 -0
  127. package/dist/files.cjs.map +1 -0
  128. package/dist/files.d.cts +68 -0
  129. package/dist/files.d.cts.map +1 -0
  130. package/dist/files.d.ts +68 -0
  131. package/dist/files.d.ts.map +1 -0
  132. package/dist/files.js +1056 -0
  133. package/dist/files.js.map +1 -0
  134. package/dist/fine-tuning.cjs +1033 -0
  135. package/dist/fine-tuning.cjs.map +1 -0
  136. package/dist/fine-tuning.d.cts +96 -0
  137. package/dist/fine-tuning.d.cts.map +1 -0
  138. package/dist/fine-tuning.d.ts +96 -0
  139. package/dist/fine-tuning.d.ts.map +1 -0
  140. package/dist/fine-tuning.js +1026 -0
  141. package/dist/fine-tuning.js.map +1 -0
  142. package/dist/fixture-loader-services.cjs +116 -0
  143. package/dist/fixture-loader-services.cjs.map +1 -0
  144. package/dist/fixture-loader-services.d.cts +48 -0
  145. package/dist/fixture-loader-services.d.cts.map +1 -0
  146. package/dist/fixture-loader-services.d.ts +48 -0
  147. package/dist/fixture-loader-services.d.ts.map +1 -0
  148. package/dist/fixture-loader-services.js +114 -0
  149. package/dist/fixture-loader-services.js.map +1 -0
  150. package/dist/fixture-loader.cjs +0 -0
  151. package/dist/fixture-loader.cjs.map +1 -1
  152. package/dist/fixture-loader.d.cts +131 -5
  153. package/dist/fixture-loader.d.cts.map +1 -1
  154. package/dist/fixture-loader.d.ts +131 -5
  155. package/dist/fixture-loader.d.ts.map +1 -1
  156. package/dist/fixture-loader.js +0 -0
  157. package/dist/fixture-loader.js.map +1 -1
  158. package/dist/gemini-embeddings.cjs +44 -4
  159. package/dist/gemini-embeddings.cjs.map +1 -1
  160. package/dist/gemini-embeddings.js +46 -6
  161. package/dist/gemini-embeddings.js.map +1 -1
  162. package/dist/gemini-interactions.cjs +51 -7
  163. package/dist/gemini-interactions.cjs.map +1 -1
  164. package/dist/gemini-interactions.d.cts.map +1 -1
  165. package/dist/gemini-interactions.d.ts.map +1 -1
  166. package/dist/gemini-interactions.js +53 -9
  167. package/dist/gemini-interactions.js.map +1 -1
  168. package/dist/gemini.cjs +19 -8
  169. package/dist/gemini.cjs.map +1 -1
  170. package/dist/gemini.d.cts.map +1 -1
  171. package/dist/gemini.d.ts.map +1 -1
  172. package/dist/gemini.js +21 -10
  173. package/dist/gemini.js.map +1 -1
  174. package/dist/grok-video.cjs +14 -14
  175. package/dist/grok-video.cjs.map +1 -1
  176. package/dist/grok-video.js +15 -15
  177. package/dist/grok-video.js.map +1 -1
  178. package/dist/helpers.cjs +497 -10
  179. package/dist/helpers.cjs.map +1 -1
  180. package/dist/helpers.d.cts +38 -2
  181. package/dist/helpers.d.cts.map +1 -1
  182. package/dist/helpers.d.ts +38 -2
  183. package/dist/helpers.d.ts.map +1 -1
  184. package/dist/helpers.js +487 -12
  185. package/dist/helpers.js.map +1 -1
  186. package/dist/images.cjs +57 -125
  187. package/dist/images.cjs.map +1 -1
  188. package/dist/images.d.cts.map +1 -1
  189. package/dist/images.d.ts.map +1 -1
  190. package/dist/images.js +58 -126
  191. package/dist/images.js.map +1 -1
  192. package/dist/index.cjs +71 -9
  193. package/dist/index.d.cts +17 -7
  194. package/dist/index.d.ts +17 -7
  195. package/dist/index.js +21 -11
  196. package/dist/jest.cjs +24 -12
  197. package/dist/jest.cjs.map +1 -1
  198. package/dist/jest.d.cts +6 -3
  199. package/dist/jest.d.cts.map +1 -1
  200. package/dist/jest.d.ts +6 -3
  201. package/dist/jest.d.ts.map +1 -1
  202. package/dist/jest.js +25 -13
  203. package/dist/jest.js.map +1 -1
  204. package/dist/journal.cjs +61 -4
  205. package/dist/journal.cjs.map +1 -1
  206. package/dist/journal.d.cts +60 -3
  207. package/dist/journal.d.cts.map +1 -1
  208. package/dist/journal.d.ts +60 -3
  209. package/dist/journal.d.ts.map +1 -1
  210. package/dist/journal.js +61 -5
  211. package/dist/journal.js.map +1 -1
  212. package/dist/live-fixture.cjs +1192 -0
  213. package/dist/live-fixture.cjs.map +1 -0
  214. package/dist/live-fixture.js +1187 -0
  215. package/dist/live-fixture.js.map +1 -0
  216. package/dist/live-recorder.cjs +256 -0
  217. package/dist/live-recorder.cjs.map +1 -0
  218. package/dist/live-recorder.js +254 -0
  219. package/dist/live-recorder.js.map +1 -0
  220. package/dist/live-replay.cjs +286 -0
  221. package/dist/live-replay.cjs.map +1 -0
  222. package/dist/live-replay.js +285 -0
  223. package/dist/live-replay.js.map +1 -0
  224. package/dist/live-sanitize.cjs +196 -0
  225. package/dist/live-sanitize.cjs.map +1 -0
  226. package/dist/live-sanitize.js +196 -0
  227. package/dist/live-sanitize.js.map +1 -0
  228. package/dist/live-types.d.cts +71 -0
  229. package/dist/live-types.d.cts.map +1 -0
  230. package/dist/live-types.d.ts +71 -0
  231. package/dist/live-types.d.ts.map +1 -0
  232. package/dist/llmock.cjs +132 -49
  233. package/dist/llmock.cjs.map +1 -1
  234. package/dist/llmock.d.cts +51 -5
  235. package/dist/llmock.d.cts.map +1 -1
  236. package/dist/llmock.d.ts +51 -5
  237. package/dist/llmock.d.ts.map +1 -1
  238. package/dist/llmock.js +133 -50
  239. package/dist/llmock.js.map +1 -1
  240. package/dist/mcp-fakes-mount.cjs +216 -0
  241. package/dist/mcp-fakes-mount.cjs.map +1 -0
  242. package/dist/mcp-fakes-mount.js +210 -0
  243. package/dist/mcp-fakes-mount.js.map +1 -0
  244. package/dist/mcp-fakes.cjs +1619 -0
  245. package/dist/mcp-fakes.cjs.map +1 -0
  246. package/dist/mcp-fakes.d.cts +101 -0
  247. package/dist/mcp-fakes.d.cts.map +1 -0
  248. package/dist/mcp-fakes.d.ts +101 -0
  249. package/dist/mcp-fakes.d.ts.map +1 -0
  250. package/dist/mcp-fakes.js +1614 -0
  251. package/dist/mcp-fakes.js.map +1 -0
  252. package/dist/mcp-handler.cjs +331 -17
  253. package/dist/mcp-handler.cjs.map +1 -1
  254. package/dist/mcp-handler.js +330 -18
  255. package/dist/mcp-handler.js.map +1 -1
  256. package/dist/mcp-mock.cjs +217 -49
  257. package/dist/mcp-mock.cjs.map +1 -1
  258. package/dist/mcp-mock.d.cts +66 -1
  259. package/dist/mcp-mock.d.cts.map +1 -1
  260. package/dist/mcp-mock.d.ts +66 -1
  261. package/dist/mcp-mock.d.ts.map +1 -1
  262. package/dist/mcp-mock.js +218 -50
  263. package/dist/mcp-mock.js.map +1 -1
  264. package/dist/mcp-stub.cjs +5 -1
  265. package/dist/mcp-stub.d.cts +3 -1
  266. package/dist/mcp-stub.d.ts +3 -1
  267. package/dist/mcp-stub.js +3 -1
  268. package/dist/mcp-types.d.cts +6 -0
  269. package/dist/mcp-types.d.ts +6 -0
  270. package/dist/message-text.cjs +192 -0
  271. package/dist/message-text.cjs.map +1 -0
  272. package/dist/message-text.d.cts +31 -0
  273. package/dist/message-text.d.cts.map +1 -0
  274. package/dist/message-text.d.ts +31 -0
  275. package/dist/message-text.d.ts.map +1 -0
  276. package/dist/message-text.js +184 -0
  277. package/dist/message-text.js.map +1 -0
  278. package/dist/messages.cjs +49 -7
  279. package/dist/messages.cjs.map +1 -1
  280. package/dist/messages.d.cts.map +1 -1
  281. package/dist/messages.d.ts.map +1 -1
  282. package/dist/messages.js +51 -9
  283. package/dist/messages.js.map +1 -1
  284. package/dist/metrics.cjs +278 -45
  285. package/dist/metrics.cjs.map +1 -1
  286. package/dist/metrics.d.cts +17 -7
  287. package/dist/metrics.d.cts.map +1 -1
  288. package/dist/metrics.d.ts +17 -7
  289. package/dist/metrics.d.ts.map +1 -1
  290. package/dist/metrics.js +270 -29
  291. package/dist/metrics.js.map +1 -1
  292. package/dist/moderation.cjs +10 -1
  293. package/dist/moderation.cjs.map +1 -1
  294. package/dist/moderation.d.cts.map +1 -1
  295. package/dist/moderation.d.ts.map +1 -1
  296. package/dist/moderation.js +10 -1
  297. package/dist/moderation.js.map +1 -1
  298. package/dist/ollama.cjs +10 -6
  299. package/dist/ollama.cjs.map +1 -1
  300. package/dist/ollama.d.cts.map +1 -1
  301. package/dist/ollama.d.ts.map +1 -1
  302. package/dist/ollama.js +11 -7
  303. package/dist/ollama.js.map +1 -1
  304. package/dist/openapi.cjs +3405 -0
  305. package/dist/openapi.cjs.map +1 -0
  306. package/dist/openapi.d.cts +30 -0
  307. package/dist/openapi.d.cts.map +1 -0
  308. package/dist/openapi.d.ts +30 -0
  309. package/dist/openapi.d.ts.map +1 -0
  310. package/dist/openapi.js +3404 -0
  311. package/dist/openapi.js.map +1 -0
  312. package/dist/openrouter-video.cjs +9 -9
  313. package/dist/openrouter-video.cjs.map +1 -1
  314. package/dist/openrouter-video.d.cts.map +1 -1
  315. package/dist/openrouter-video.d.ts.map +1 -1
  316. package/dist/openrouter-video.js +10 -10
  317. package/dist/openrouter-video.js.map +1 -1
  318. package/dist/recorder.cjs +54 -10
  319. package/dist/recorder.cjs.map +1 -1
  320. package/dist/recorder.d.cts.map +1 -1
  321. package/dist/recorder.d.ts.map +1 -1
  322. package/dist/recorder.js +52 -12
  323. package/dist/recorder.js.map +1 -1
  324. package/dist/responses.cjs +47 -7
  325. package/dist/responses.cjs.map +1 -1
  326. package/dist/responses.d.cts.map +1 -1
  327. package/dist/responses.d.ts.map +1 -1
  328. package/dist/responses.js +48 -9
  329. package/dist/responses.js.map +1 -1
  330. package/dist/route-registry.cjs +951 -0
  331. package/dist/route-registry.cjs.map +1 -0
  332. package/dist/route-registry.d.cts +33 -0
  333. package/dist/route-registry.d.cts.map +1 -0
  334. package/dist/route-registry.d.ts +33 -0
  335. package/dist/route-registry.d.ts.map +1 -0
  336. package/dist/route-registry.js +866 -0
  337. package/dist/route-registry.js.map +1 -0
  338. package/dist/router.cjs +21 -34
  339. package/dist/router.cjs.map +1 -1
  340. package/dist/router.d.cts +4 -1
  341. package/dist/router.d.cts.map +1 -1
  342. package/dist/router.d.ts +4 -1
  343. package/dist/router.d.ts.map +1 -1
  344. package/dist/router.js +21 -35
  345. package/dist/router.js.map +1 -1
  346. package/dist/server.cjs +1252 -777
  347. package/dist/server.cjs.map +1 -1
  348. package/dist/server.d.cts +5 -1
  349. package/dist/server.d.cts.map +1 -1
  350. package/dist/server.d.ts +5 -1
  351. package/dist/server.d.ts.map +1 -1
  352. package/dist/server.js +1185 -710
  353. package/dist/server.js.map +1 -1
  354. package/dist/speech.cjs +6 -4
  355. package/dist/speech.cjs.map +1 -1
  356. package/dist/speech.js +7 -5
  357. package/dist/speech.js.map +1 -1
  358. package/dist/suite.cjs +1 -1
  359. package/dist/suite.js +1 -1
  360. package/dist/transcription.cjs +6 -6
  361. package/dist/transcription.cjs.map +1 -1
  362. package/dist/transcription.d.cts.map +1 -1
  363. package/dist/transcription.d.ts.map +1 -1
  364. package/dist/transcription.js +7 -7
  365. package/dist/transcription.js.map +1 -1
  366. package/dist/types.cjs +18 -0
  367. package/dist/types.cjs.map +1 -0
  368. package/dist/types.d.cts +320 -17
  369. package/dist/types.d.cts.map +1 -1
  370. package/dist/types.d.ts +320 -17
  371. package/dist/types.d.ts.map +1 -1
  372. package/dist/types.js +17 -0
  373. package/dist/types.js.map +1 -0
  374. package/dist/validate-cli.cjs +983 -0
  375. package/dist/validate-cli.cjs.map +1 -0
  376. package/dist/validate-cli.js +982 -0
  377. package/dist/validate-cli.js.map +1 -0
  378. package/dist/vector-stores.cjs +1180 -0
  379. package/dist/vector-stores.cjs.map +1 -0
  380. package/dist/vector-stores.d.cts +106 -0
  381. package/dist/vector-stores.d.cts.map +1 -0
  382. package/dist/vector-stores.d.ts +106 -0
  383. package/dist/vector-stores.d.ts.map +1 -0
  384. package/dist/vector-stores.js +1164 -0
  385. package/dist/vector-stores.js.map +1 -0
  386. package/dist/vector-types.d.cts.map +1 -1
  387. package/dist/vector-types.d.ts.map +1 -1
  388. package/dist/veo-video.cjs +13 -13
  389. package/dist/veo-video.cjs.map +1 -1
  390. package/dist/veo-video.js +14 -14
  391. package/dist/veo-video.js.map +1 -1
  392. package/dist/video.cjs +23 -4
  393. package/dist/video.cjs.map +1 -1
  394. package/dist/video.d.cts +1 -1
  395. package/dist/video.d.cts.map +1 -1
  396. package/dist/video.d.ts +1 -1
  397. package/dist/video.d.ts.map +1 -1
  398. package/dist/video.js +24 -5
  399. package/dist/video.js.map +1 -1
  400. package/dist/vitest.cjs +23 -11
  401. package/dist/vitest.cjs.map +1 -1
  402. package/dist/vitest.d.cts +5 -2
  403. package/dist/vitest.d.cts.map +1 -1
  404. package/dist/vitest.d.ts +5 -2
  405. package/dist/vitest.d.ts.map +1 -1
  406. package/dist/vitest.js +25 -13
  407. package/dist/vitest.js.map +1 -1
  408. package/dist/watcher.cjs +2 -1
  409. package/dist/watcher.cjs.map +1 -1
  410. package/dist/watcher.js +2 -1
  411. package/dist/watcher.js.map +1 -1
  412. package/dist/ws-framing.cjs +150 -35
  413. package/dist/ws-framing.cjs.map +1 -1
  414. package/dist/ws-framing.d.cts +18 -3
  415. package/dist/ws-framing.d.cts.map +1 -1
  416. package/dist/ws-framing.d.ts +18 -3
  417. package/dist/ws-framing.d.ts.map +1 -1
  418. package/dist/ws-framing.js +151 -36
  419. package/dist/ws-framing.js.map +1 -1
  420. package/dist/ws-gemini-live.cjs +2 -2
  421. package/dist/ws-gemini-live.js +2 -2
  422. package/dist/ws-live.cjs +401 -0
  423. package/dist/ws-live.cjs.map +1 -0
  424. package/dist/ws-live.js +400 -0
  425. package/dist/ws-live.js.map +1 -0
  426. package/dist/ws-realtime.cjs +46 -2
  427. package/dist/ws-realtime.cjs.map +1 -1
  428. package/dist/ws-realtime.d.cts.map +1 -1
  429. package/dist/ws-realtime.d.ts.map +1 -1
  430. package/dist/ws-realtime.js +46 -2
  431. package/dist/ws-realtime.js.map +1 -1
  432. package/dist/ws-responses.cjs +16 -4
  433. package/dist/ws-responses.cjs.map +1 -1
  434. package/dist/ws-responses.js +16 -4
  435. package/dist/ws-responses.js.map +1 -1
  436. package/dist/ws-upstream.cjs +118 -0
  437. package/dist/ws-upstream.cjs.map +1 -0
  438. package/dist/ws-upstream.js +115 -0
  439. package/dist/ws-upstream.js.map +1 -0
  440. package/fixtures/openai-live-client.json +390 -0
  441. package/fixtures/openai-live-managed.json +709 -0
  442. package/package.json +4 -1
package/dist/server.cjs CHANGED
@@ -1,16 +1,18 @@
1
1
  const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
2
  const require_constants = require('./constants.cjs');
3
+ const require_message_text = require('./message-text.cjs');
3
4
  const require_api_key_auth = require('./api-key-auth.cjs');
4
5
  const require_helpers = require('./helpers.cjs');
6
+ const require_live_fixture = require('./live-fixture.cjs');
7
+ const require_sse_writer = require('./sse-writer.cjs');
8
+ const require_chaos = require('./chaos.cjs');
9
+ const require_fixture_loader = require('./fixture-loader.cjs');
10
+ const require_router = require('./router.cjs');
11
+ const require_recorder = require('./recorder.cjs');
5
12
  const require_logger = require('./logger.cjs');
6
13
  const require_journal = require('./journal.cjs');
7
- const require_router = require('./router.cjs');
8
- const require_fixture_loader = require('./fixture-loader.cjs');
9
- const require_sse_writer = require('./sse-writer.cjs');
10
14
  const require_interruption = require('./interruption.cjs');
11
15
  const require_openrouter_chat = require('./openrouter-chat.cjs');
12
- const require_chaos = require('./chaos.cjs');
13
- const require_recorder = require('./recorder.cjs');
14
16
  const require_responses = require('./responses.cjs');
15
17
  const require_messages = require('./messages.cjs');
16
18
  const require_gemini = require('./gemini.cjs');
@@ -30,46 +32,33 @@ const require_veo_video = require('./veo-video.cjs');
30
32
  const require_grok_video = require('./grok-video.cjs');
31
33
  const require_byteplus_video = require('./byteplus-video.cjs');
32
34
  const require_elevenlabs_audio = require('./elevenlabs-audio.cjs');
35
+ const require_elevenlabs_voice = require('./elevenlabs-voice.cjs');
33
36
  const require_ollama = require('./ollama.cjs');
34
37
  const require_cohere = require('./cohere.cjs');
35
38
  const require_search = require('./search.cjs');
36
39
  const require_rerank = require('./rerank.cjs');
37
40
  const require_moderation = require('./moderation.cjs');
41
+ const require_fine_tuning = require('./fine-tuning.cjs');
42
+ const require_vector_stores = require('./vector-stores.cjs');
43
+ const require_files = require('./files.cjs');
44
+ const require_batches = require('./batches.cjs');
38
45
  const require_ws_framing = require('./ws-framing.cjs');
46
+ const require_ws_live = require('./ws-live.cjs');
39
47
  const require_ws_responses = require('./ws-responses.cjs');
40
48
  const require_ws_realtime = require('./ws-realtime.cjs');
41
49
  const require_ws_gemini_live = require('./ws-gemini-live.cjs');
50
+ const require_route_registry = require('./route-registry.cjs');
51
+ const require_openapi = require('./openapi.cjs');
42
52
  const require_metrics = require('./metrics.cjs');
53
+ const require_mcp_fakes = require('./mcp-fakes.cjs');
54
+ const require_mcp_mock = require('./mcp-mock.cjs');
55
+ const require_mcp_fakes_mount = require('./mcp-fakes-mount.cjs');
43
56
  let node_http = require("node:http");
44
57
  node_http = require_runtime.__toESM(node_http);
58
+ let node_async_hooks = require("node:async_hooks");
45
59
 
46
60
  //#region src/server.ts
47
- const COMPLETIONS_PATH = "/v1/chat/completions";
48
- const RESPONSES_PATH = "/v1/responses";
49
- const REALTIME_PATH = "/v1/realtime";
50
- const GEMINI_LIVE_PATH = "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent";
51
- const MESSAGES_PATH = "/v1/messages";
52
- const EMBEDDINGS_PATH = "/v1/embeddings";
53
- const COHERE_CHAT_PATH = "/v2/chat";
54
- const COHERE_EMBED_PATH = "/v2/embed";
55
- const SEARCH_PATH = "/search";
56
- const RERANK_PATH = "/v2/rerank";
57
- const MODERATIONS_PATH = "/v1/moderations";
58
- const IMAGES_PATH = "/v1/images/generations";
59
- const IMAGES_EDIT_PATH = "/v1/images/edits";
60
- const IMAGES_VARIATIONS_PATH = "/v1/images/variations";
61
- const SPEECH_PATH = "/v1/audio/speech";
62
- const TRANSCRIPTIONS_PATH = "/v1/audio/transcriptions";
63
- const TRANSLATIONS_PATH = "/v1/audio/translations";
64
- const VIDEOS_PATH = "/v1/videos";
65
- const GEMINI_PREDICT_RE = /^\/v1beta\/models\/([^:]+):predict$/;
66
- const ELEVENLABS_SOUND_GENERATION_PATH = "/v1/sound-generation";
67
- const ELEVENLABS_TTS_RE = /^\/v1\/text-to-speech\/([^/]+)$/;
68
- const ELEVENLABS_MUSIC_RE = /^\/v1\/music(?:\/(.+))?$/;
69
- const FAL_QUEUE_SUBMIT_RE = /^\/fal\/queue\/submit\/(.+)$/;
70
- const FAL_QUEUE_REQUESTS_RE = /^\/fal\/queue\/requests\/(.+)$/;
71
- const FAL_RUN_RE = /^\/fal\/run\/(.+)$/;
72
- const FAL_PREFIX_RE = /^\/fal(?:\/.*)?$/;
61
+ const liveClosers = /* @__PURE__ */ new WeakMap();
73
62
  const DEFAULT_CHUNK_SIZE = 20;
74
63
  const COMPAT_SUFFIXES = [
75
64
  "/chat/completions",
@@ -98,26 +87,6 @@ function normalizeCompatPath(pathname, logger) {
98
87
  }
99
88
  return pathname;
100
89
  }
101
- const GEMINI_INTERACTIONS_PATH = "/v1beta/interactions";
102
- const GEMINI_PATH_RE = /^\/v1beta\/models\/([^:]+):(generateContent|streamGenerateContent)$/;
103
- const GEMINI_EMBED_RE = /^\/v1beta\/models\/([^:]+):embedContent$/;
104
- const AZURE_DEPLOYMENT_RE = /^\/openai\/deployments\/([^/]+)\/(chat\/completions|embeddings)$/;
105
- const BEDROCK_INVOKE_RE = /^\/model\/([^/]+)\/invoke$/;
106
- const BEDROCK_STREAM_RE = /^\/model\/([^/]+)\/invoke-with-response-stream$/;
107
- const BEDROCK_CONVERSE_RE = /^\/model\/([^/]+)\/converse$/;
108
- const BEDROCK_CONVERSE_STREAM_RE = /^\/model\/([^/]+)\/converse-stream$/;
109
- const VERTEX_AI_RE = /^\/v1\/projects\/[^/]+\/locations\/[^/]+\/publishers\/google\/models\/([^/:]+):(generateContent|streamGenerateContent)$/;
110
- const OLLAMA_CHAT_PATH = "/api/chat";
111
- const OLLAMA_GENERATE_PATH = "/api/generate";
112
- const OLLAMA_EMBEDDINGS_PATH = "/api/embeddings";
113
- const OLLAMA_EMBED_PATH = "/api/embed";
114
- const OLLAMA_TAGS_PATH = "/api/tags";
115
- const OPENROUTER_VIDEOS_PATH = "/api/v1/videos";
116
- const OPENROUTER_VIDEO_MODELS_PATH = "/api/v1/videos/models";
117
- const HEALTH_PATH = "/health";
118
- const READY_PATH = "/ready";
119
- const MODELS_PATH = "/v1/models";
120
- const REQUESTS_PATH = "/v1/_requests";
121
90
  const DEFAULT_MODELS = [
122
91
  "gpt-4",
123
92
  "gpt-4o",
@@ -127,9 +96,9 @@ const DEFAULT_MODELS = [
127
96
  ];
128
97
  const CORS_HEADERS = {
129
98
  "Access-Control-Allow-Origin": "*",
130
- "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
99
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
131
100
  "Access-Control-Allow-Headers": "*",
132
- "Access-Control-Expose-Headers": "X-Total-Count"
101
+ "Access-Control-Expose-Headers": "X-Total-Count, X-Request-Id"
133
102
  };
134
103
  function setCorsHeaders(res) {
135
104
  for (const [key, value] of Object.entries(CORS_HEADERS)) res.setHeader(key, value);
@@ -146,7 +115,6 @@ function handleNotFound(res, message) {
146
115
  type: "not_found"
147
116
  } }));
148
117
  }
149
- const CONTROL_PREFIX = "/__aimock";
150
118
  /** The complete `GET /__aimock/fixtures` query-param vocabulary; anything else 400s. */
151
119
  const FIXTURES_PARAMS = new Set(["include"]);
152
120
  /** The complete `GET /__aimock/journal` query-param vocabulary; anything else 400s. */
@@ -157,14 +125,18 @@ const JOURNAL_PARAMS = new Set([
157
125
  "method",
158
126
  "status",
159
127
  "service",
160
- "testId"
128
+ "testId",
129
+ "requestId"
161
130
  ]);
162
131
  /**
163
132
  * Perform a full reset: clear the fixtures array, the journal (entries *and*
164
133
  * per-test fixture match-counts, i.e. sequence position), the video and fal.ai
165
- * job/queue state, the Gemini interaction/event-id counters, and any runtime
166
- * chaos override (reverting to the construction-time chaos config), then
167
- * re-zero the `aimock_fixtures_loaded` gauge.
134
+ * job/queue state, the fine-tuning store (jobs, their append-only event logs,
135
+ * their poll counts, the create-time suffixes their model names are built from,
136
+ * and the module's monotonic clock), the ElevenLabs Voice Design store, the
137
+ * Gemini interaction/event-id counters, and any runtime chaos override
138
+ * (reverting to the construction-time chaos config), then re-zero the
139
+ * `aimock_fixtures_loaded` gauge.
168
140
  *
169
141
  * `targets` is `null` when no server is running (an in-process `reset()` before
170
142
  * `start()`). The process-global generation state is reset either way, since it
@@ -174,19 +146,27 @@ const JOURNAL_PARAMS = new Set([
174
146
  * `POST /__aimock/reset/fixtures` alias, and `LLMock.reset()`.
175
147
  */
176
148
  function performFullReset(fixtures, targets) {
177
- fixtures.length = 0;
149
+ require_fixture_loader.clearFixtureQueue(fixtures);
178
150
  require_fal_audio.falJobs.clear();
179
151
  require_fal.falQueueStates.clear();
152
+ require_batches.clearBatchStore();
153
+ require_elevenlabs_voice.clearElevenLabsVoices();
154
+ require_files.clearFileStore();
155
+ require_fine_tuning.clearFineTuningStore();
156
+ require_vector_stores.clearVectorStoreStore();
180
157
  require_gemini_interactions.resetInteractionCounter();
181
158
  require_gemini_interactions.resetEventIdCounter();
182
159
  if (!targets) return;
160
+ liveClosers.get(targets.defaults)?.();
183
161
  targets.journal.clear();
162
+ require_chaos.resetChaosWarnings(targets.defaults.logger);
184
163
  targets.defaults.chaos = void 0;
185
164
  targets.videoStates.clear();
186
165
  targets.openRouterVideoJobs.clear();
187
166
  targets.veoVideoJobs.clear();
188
167
  targets.grokVideoJobs.clear();
189
168
  targets.bytePlusVideoJobs.clear();
169
+ for (const { handler } of targets.mounts ?? []) handler.clearMcpFakes?.();
190
170
  if (targets.defaults.registry) targets.defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
191
171
  }
192
172
  /**
@@ -232,22 +212,23 @@ function fixtureResponseKind(response) {
232
212
  return "unknown";
233
213
  }
234
214
  /**
235
- * Resolve a journal entry's testId exactly the way `getTestId` resolves an
236
- * incoming request's: the `X-Test-Id` header wins, then `?testId=` parsed out
237
- * of the query string, then `DEFAULT_TEST_ID`. The
238
- * filter and the server's own per-test isolation must never disagree — and a
239
- * raw `path.includes("testId=t1")` both prefix-collides with `t10` and matches
240
- * unrelated params like `notTestId`.
215
+ * The testId a journal entry was scoped under — `resolveTestId` applied to the
216
+ * journaled headers and path, i.e. the SAME function that scoped the request
217
+ * (fixture match-counts, chaos) when it arrived. Node folds a repeated
218
+ * `X-Test-Id` into one string ("a, a") before either side sees it, and the
219
+ * request is counted and chaos-evaluated under that folded string, so the
220
+ * `?testId=` filter matches it verbatim too: no token-splitting here that the
221
+ * scoping path does not also do. (Splitting on "," additionally broke every
222
+ * legitimately comma-bearing id.) A raw `path.includes("testId=t1")` both
223
+ * prefix-collides with `t10` and matches unrelated params like `notTestId`.
224
+ *
225
+ * An MCP mount records the test id it resolved on the entry (I9, B3): a
226
+ * decoded header or query value, or the one bound to the session at
227
+ * `initialize`. That value wins; LLM entries carry none and are attributed as
228
+ * before.
241
229
  */
242
230
  function journalEntryTestId(entry) {
243
- const header = entry.headers["x-test-id"];
244
- if (header) return header;
245
- const qIdx = entry.path.indexOf("?");
246
- if (qIdx !== -1) {
247
- const queryValue = new URLSearchParams(entry.path.slice(qIdx + 1)).get("testId");
248
- if (queryValue) return queryValue;
249
- }
250
- return require_constants.DEFAULT_TEST_ID;
231
+ return entry.testId ?? require_helpers.resolveTestId(entry.headers, entry.path);
251
232
  }
252
233
  /**
253
234
  * The testId a chaos override is stored under, and read back with — resolved by
@@ -274,27 +255,198 @@ function writeBlankTestId(res) {
274
255
  res.end(JSON.stringify({ error: "Invalid 'X-Test-Id': header is present but empty" }));
275
256
  return true;
276
257
  }
277
- /** The chaos config in effect for one testId: its override, else the baseline. */
258
+ /**
259
+ * The chaos config in effect for one testId: its override, else the baseline —
260
+ * one or the other, never the two merged. Mirrors `resolveScopedDefaults` in
261
+ * chaos.ts, so what `GET /__aimock/chaos` reports for a scope is exactly what
262
+ * that scope's traffic is evaluated against.
263
+ */
278
264
  function effectiveChaos(defaults, scopeId) {
279
- const scoped = defaults.chaosByTestId?.get(scopeId);
280
- if (scoped) return scoped;
265
+ if (defaults.chaosByTestId?.has(scopeId)) return defaults.chaosByTestId.get(scopeId) ?? {};
281
266
  const current = defaults.chaos;
282
267
  if (!current) return {};
283
268
  return require_chaos.isChaosScope(current) ? current.base ?? {} : current;
284
269
  }
270
+ /** The `GET /__aimock/mcp/fakes` query-param vocabulary; anything else 400s. */
271
+ const MCP_FAKES_PARAMS = new Set([
272
+ "testId",
273
+ "context",
274
+ "mount"
275
+ ]);
276
+ /** A block's `mount` path, read without running getters; the add validates it. */
277
+ function controlFakeMountOf(raw) {
278
+ if (typeof raw !== "object" || raw === null) return require_mcp_fakes.MCP_FAKES_DEFAULT_MOUNT;
279
+ const value = Object.getOwnPropertyDescriptor(raw, "mount")?.value;
280
+ return typeof value === "string" ? value : require_mcp_fakes.MCP_FAKES_DEFAULT_MOUNT;
281
+ }
282
+ /**
283
+ * Check one `POST /__aimock/fixtures` `mcpFakes` value against the live
284
+ * mounts (F10, W6) without changing anything. Returns every error in input
285
+ * order (an empty list when the value can be added), and `apply`, which adds
286
+ * the blocks (auto-mounting an MCPMock where no mount serves a block's path)
287
+ * and returns the number of blocks added.
288
+ *
289
+ * The whole value is checked before any mount is touched, and the check
290
+ * never adds to a real mount: each group is added to a throwaway store,
291
+ * whose errors are the ones reported (it numbers the add `control-api#1`, as
292
+ * a new mount would; for an existing MCPMock the rejected group is re-run on
293
+ * its atomic add, so the errors name that mount's next `<n>`, I7). A group
294
+ * whose path no request to it can reach (a mount that is not an MCPMock
295
+ * serves it, an MCPMock answers it as its root, or it is under the control
296
+ * prefix) is a mount conflict (one error per block).
297
+ */
298
+ function planControlApiFakes(raw, mounts) {
299
+ const items = Array.isArray(raw) ? raw.map((block, position) => ({
300
+ position,
301
+ source: {
302
+ source: "control-api",
303
+ blockIndex: position,
304
+ raw: block
305
+ }
306
+ })) : [{
307
+ position: 0,
308
+ source: {
309
+ source: "control-api",
310
+ blockIndex: null,
311
+ raw
312
+ }
313
+ }];
314
+ const groups = /* @__PURE__ */ new Map();
315
+ if (items.length === 0) groups.set(require_mcp_fakes.MCP_FAKES_DEFAULT_MOUNT, {
316
+ path: require_mcp_fakes.MCP_FAKES_DEFAULT_MOUNT,
317
+ positions: [],
318
+ sources: []
319
+ });
320
+ for (const { position, source } of items) {
321
+ const path = controlFakeMountOf(source.raw);
322
+ const group = groups.get(path) ?? {
323
+ path,
324
+ positions: [],
325
+ sources: []
326
+ };
327
+ group.positions.push(position);
328
+ group.sources.push(source);
329
+ groups.set(path, group);
330
+ }
331
+ const found = [];
332
+ /** The paths `apply` auto-mounts, in order. */
333
+ const pending = [];
334
+ for (const group of groups.values()) {
335
+ const target = require_mcp_fakes_mount.findFakeMount(mounts, group.path, pending);
336
+ if (target.kind === "conflict") {
337
+ group.positions.forEach((position) => {
338
+ const where = Array.isArray(raw) ? require_message_text.msg`mcpFakes[${position}]` : require_message_text.msg`mcpFakes`;
339
+ found.push({
340
+ position,
341
+ error: new require_fixture_loader.FixtureLoadError({
342
+ rule: "mcp-fakes/mount-conflict",
343
+ file: null,
344
+ blockId: null,
345
+ entryId: null,
346
+ detail: require_message_text.msg`${where}: ${require_mcp_fakes_mount.mountConflictDetail(group.path, target)}`
347
+ })
348
+ });
349
+ });
350
+ continue;
351
+ }
352
+ let failure = addFailure(() => new require_mcp_fakes.McpFakeStore().add(group.sources, { kind: "control-api" }));
353
+ if (target.kind === "none") pending.push(group.path);
354
+ if (!failure) continue;
355
+ if (target.kind === "mcp" && target.handler.addMcpFakes === require_mcp_mock.MCPMock.prototype.addMcpFakes) {
356
+ const handler = target.handler;
357
+ failure = addFailure(() => handler.addMcpFakes?.(group.sources, { kind: "control-api" })) ?? failure;
358
+ }
359
+ for (const error of failure.errors) found.push({
360
+ position: positionOf(error, group),
361
+ error
362
+ });
363
+ }
364
+ found.sort((a, b) => a.position - b.position);
365
+ return {
366
+ errors: found.map((f) => f.error),
367
+ apply(wiring) {
368
+ let added = 0;
369
+ for (const group of groups.values()) {
370
+ const target = require_mcp_fakes_mount.ensureFakeMount(mounts, group.path, {
371
+ journal: wiring.journal,
372
+ registry: wiring.defaults.registry,
373
+ logger: wiring.defaults.logger
374
+ }, wiring.defaults.logger);
375
+ if ("conflict" in target) continue;
376
+ const result = target.handler.addMcpFakes?.(group.sources, { kind: "control-api" });
377
+ for (const warning of result?.warnings ?? []) wiring.defaults.logger.warn(warning.message);
378
+ added += group.sources.length;
379
+ }
380
+ return added;
381
+ }
382
+ };
383
+ }
384
+ /** The `McpFakesAddError` that `add` throws, or `null` when it adds. */
385
+ function addFailure(add) {
386
+ try {
387
+ add();
388
+ return null;
389
+ } catch (err) {
390
+ if (err instanceof require_mcp_fakes.McpFakesAddError) return err;
391
+ throw err;
392
+ }
393
+ }
394
+ /**
395
+ * The request position of the block an add error names. The add numbers a
396
+ * run-time group's blocks by their place in the group (`[<j>]`), or gives no
397
+ * index to a single-object value; an error tied to no block sorts with the
398
+ * group's first block.
399
+ */
400
+ function positionOf(error, group) {
401
+ const first = group.positions[0] ?? 0;
402
+ if (error.blockId == null || error.file === null) return first;
403
+ const at = group.positions.findIndex((_, j) => error.blockId === require_mcp_fakes.blockIdOf(error.file ?? "", "", j));
404
+ return at === -1 ? first : group.positions[at];
405
+ }
406
+ function hasFakesSnapshot(handler) {
407
+ return "fakesSnapshot" in handler && typeof handler.fakesSnapshot === "function";
408
+ }
409
+ /**
410
+ * `GET /__aimock/mcp/fakes` (spec 6.5, I9): for each MCP mount (or the one
411
+ * named by `mount`), the blocks that apply to the requested test id and
412
+ * context, with every entry id and its consumed state for that test id.
413
+ * A mount is listed when it takes fakes (it implements `addMcpFakes`, the
414
+ * check every other fakes path uses); one with no `fakesSnapshot` lists no
415
+ * blocks.
416
+ */
417
+ function listControlApiFakes(mounts, testId, context, mount) {
418
+ const listed = mounts.flatMap(({ path, handler }) => require_mcp_fakes_mount.isFakeMount(handler) && (mount === null || path === mount) ? [{
419
+ mount: path,
420
+ blocks: hasFakesSnapshot(handler) ? handler.fakesSnapshot(testId, context) : []
421
+ }] : []);
422
+ return mount !== null && listed.length === 0 ? null : listed;
423
+ }
285
424
  /**
286
425
  * Handle requests under `/__aimock/`. Returns `true` if the request was
287
426
  * handled, `false` if the path doesn't match the control prefix.
288
427
  */
289
- async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults) {
290
- if (!pathname.startsWith(CONTROL_PREFIX)) return false;
291
- const subPath = pathname.slice(9);
428
+ async function handleControlAPI(req, res, pathname, searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mounts) {
429
+ if (!pathname.startsWith(require_route_registry.CONTROL_PREFIX)) return false;
430
+ const subPath = pathname.slice(require_route_registry.CONTROL_PREFIX.length);
292
431
  setCorsHeaders(res);
293
432
  if (subPath === "/health" && req.method === "GET") {
294
433
  res.writeHead(200, { "Content-Type": "application/json" });
295
434
  res.end(JSON.stringify({ status: "ok" }));
296
435
  return true;
297
436
  }
437
+ if (subPath === "/openapi.json" && req.method === "GET") {
438
+ res.writeHead(200, { "Content-Type": "application/json" });
439
+ res.end(JSON.stringify(require_openapi.buildOpenApiDocument()));
440
+ return true;
441
+ }
442
+ if (subPath === "/routes" && req.method === "GET") {
443
+ res.writeHead(200, { "Content-Type": "application/json" });
444
+ res.end(JSON.stringify({
445
+ count: require_openapi.CATALOG_ROUTES.length,
446
+ routes: require_openapi.CATALOG_ROUTES
447
+ }));
448
+ return true;
449
+ }
298
450
  if (subPath === "/journal" && req.method === "GET") {
299
451
  const bad = (message) => {
300
452
  res.writeHead(400, { "Content-Type": "application/json" });
@@ -322,6 +474,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
322
474
  const methodFilter = searchParams.get("method");
323
475
  const serviceFilter = searchParams.get("service");
324
476
  const testIdFilter = searchParams.get("testId");
477
+ const requestIdFilter = searchParams.get("requestId");
325
478
  let entries = journal.getAll();
326
479
  if (methodFilter !== null) {
327
480
  const want = methodFilter.toUpperCase();
@@ -331,6 +484,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
331
484
  if (statusFilter !== void 0) entries = entries.filter((e) => e.response.status === statusFilter);
332
485
  if (serviceFilter !== null) entries = entries.filter((e) => e.service === serviceFilter);
333
486
  if (testIdFilter !== null) entries = entries.filter((e) => journalEntryTestId(e) === testIdFilter);
487
+ if (requestIdFilter !== null) entries = entries.filter((e) => e.headers["x-request-id"] === requestIdFilter);
334
488
  const total = entries.length;
335
489
  if (offset > 0) entries = entries.slice(offset);
336
490
  if (limit >= 0) entries = entries.slice(0, limit);
@@ -364,7 +518,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
364
518
  fixtures: fixtures.map((fixture, index) => ({
365
519
  index,
366
520
  match: redactForInspection(fixture.match),
367
- responseKind: fixtureResponseKind(fixture.response),
521
+ responseKind: require_fixture_loader.isOneShotError(fixture) ? "error" : fixtureResponseKind(fixture.response),
368
522
  ...fixture.latency !== void 0 ? { latency: fixture.latency } : {},
369
523
  ...fixture.chaos !== void 0 ? { chaos: fixture.chaos } : {}
370
524
  }))
@@ -414,32 +568,53 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
414
568
  res.end(JSON.stringify({ error: "Invalid body: expected a JSON object" }));
415
569
  return true;
416
570
  }
417
- const allowed = [
418
- "dropRate",
419
- "malformedRate",
420
- "disconnectRate"
421
- ];
422
- for (const key of Object.keys(parsed)) if (!allowed.includes(key)) {
571
+ for (const key of Object.keys(parsed)) if (!require_chaos.CHAOS_FIELD_NAMES.includes(key)) {
423
572
  res.writeHead(400, { "Content-Type": "application/json" });
424
573
  res.end(JSON.stringify({ error: `Unknown chaos field: '${key}'` }));
425
574
  return true;
426
575
  }
427
576
  const next = {};
428
- for (const key of allowed) {
577
+ for (const key of require_chaos.CHAOS_FIELD_NAMES) {
429
578
  const value = parsed[key];
430
579
  if (value === void 0) continue;
431
- if (typeof value !== "number" || Number.isNaN(value) || value < 0 || value > 1) {
580
+ const accepted = typeof value === "number" ? require_chaos.parseChaosField(key, value) : void 0;
581
+ if (accepted === void 0) {
582
+ const shape = require_chaos.CHAOS_FIELDS[key].integer ? "a whole number of ms" : "a number";
432
583
  res.writeHead(400, { "Content-Type": "application/json" });
433
- res.end(JSON.stringify({ error: `Invalid '${key}': must be a number between 0 and 1` }));
584
+ res.end(JSON.stringify({ error: `Invalid '${key}': must be ${shape} between 0 and ${require_chaos.CHAOS_FIELDS[key].max}` }));
434
585
  return true;
435
586
  }
436
- next[key] = value;
587
+ next[key] = accepted;
588
+ }
589
+ const shadowed = effectiveChaos(defaults, scopeId);
590
+ const droppedFields = Object.keys(shadowed).filter((key) => shadowed[key] !== void 0 && next[key] === void 0);
591
+ if (droppedFields.length > 0) {
592
+ const lost = droppedFields.map((key) => `${key}=${shadowed[key]}`).join(", ");
593
+ defaults.logger.warn(`[chaos] POST /__aimock/chaos (testId '${scopeId}') replaces the chaos config for this scope wholesale, it is not merged: ${lost} no longer applies here. Restate the field in the body to keep it.`);
437
594
  }
438
595
  defaults.chaosByTestId?.set(scopeId, next);
439
596
  res.writeHead(200, { "Content-Type": "application/json" });
440
597
  res.end(JSON.stringify({ chaos: effectiveChaos(defaults, scopeId) }));
441
598
  return true;
442
599
  }
600
+ if (subPath === "/mcp/fakes" && req.method === "GET") {
601
+ const reply = (status, body) => {
602
+ res.writeHead(status, { "Content-Type": "application/json" });
603
+ res.end(JSON.stringify(body));
604
+ return true;
605
+ };
606
+ 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(", ")}` });
607
+ const testId = searchParams.get("testId") || null;
608
+ const context = searchParams.get("context") || null;
609
+ const mount = searchParams.get("mount") || null;
610
+ const listed = listControlApiFakes(mounts, testId, context, mount);
611
+ if (listed === null) return reply(404, { error: require_message_text.build(require_message_text.msg`No MCP mount at ${require_message_text.quote(mount ?? "")}`) });
612
+ return reply(200, {
613
+ testId,
614
+ context,
615
+ mounts: listed
616
+ });
617
+ }
443
618
  if (subPath === "/fixtures" && req.method === "POST") {
444
619
  let raw;
445
620
  try {
@@ -461,29 +636,60 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
461
636
  res.end(JSON.stringify({ error: `Invalid JSON: ${msg}` }));
462
637
  return true;
463
638
  }
464
- if (!Array.isArray(parsed.fixtures)) {
639
+ const withFakes = typeof parsed === "object" && parsed !== null && Object.hasOwn(parsed, "mcpFakes");
640
+ if (parsed === null || !Array.isArray(parsed.fixtures) && !(withFakes && parsed.fixtures === void 0)) {
465
641
  res.writeHead(400, { "Content-Type": "application/json" });
466
642
  res.end(JSON.stringify({ error: "Missing or invalid \"fixtures\" array" }));
467
643
  return true;
468
644
  }
469
- const converted = parsed.fixtures.map((e) => require_fixture_loader.entryToFixture(e));
645
+ const entries = Array.isArray(parsed.fixtures) ? parsed.fixtures : [];
646
+ const missingMatchIndex = entries.findIndex((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && entry.match == null);
647
+ if (missingMatchIndex !== -1) {
648
+ res.writeHead(400, { "Content-Type": "application/json" });
649
+ res.end(JSON.stringify({ error: `Fixture at index ${missingMatchIndex} is missing match` }));
650
+ return true;
651
+ }
652
+ const invalidMatchIndex = entries.findIndex((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && entry.match != null && (typeof entry.match !== "object" || Array.isArray(entry.match)));
653
+ if (invalidMatchIndex !== -1) {
654
+ res.writeHead(400, { "Content-Type": "application/json" });
655
+ res.end(JSON.stringify({ error: `Fixture at index ${invalidMatchIndex}: match must be an object` }));
656
+ return true;
657
+ }
658
+ const converted = entries.map((e) => require_fixture_loader.entryToFixture(e));
470
659
  const errors = require_fixture_loader.validateFixtures(converted).filter((i) => i.severity === "error");
471
- if (errors.length > 0) {
660
+ const fakes = withFakes ? planControlApiFakes(parsed.mcpFakes, mounts) : null;
661
+ const fakeErrors = fakes?.errors ?? [];
662
+ const validationFailed = (details) => {
472
663
  res.writeHead(400, { "Content-Type": "application/json" });
473
664
  res.end(JSON.stringify({
474
665
  error: "Validation failed",
475
- details: errors
666
+ details
476
667
  }));
477
668
  return true;
669
+ };
670
+ if (errors.length > 0 || fakeErrors.length > 0) return validationFailed([...errors, ...fakeErrors.map((e) => e.toJSON())]);
671
+ let mcpFakesAdded = 0;
672
+ if (fakes) try {
673
+ mcpFakesAdded = fakes.apply({
674
+ journal,
675
+ defaults
676
+ });
677
+ } catch (err) {
678
+ if (!(err instanceof require_mcp_fakes.McpFakesAddError)) throw err;
679
+ return validationFailed(err.errors.map((e) => e.toJSON()));
478
680
  }
479
681
  fixtures.push(...converted);
480
682
  if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
481
683
  res.writeHead(200, { "Content-Type": "application/json" });
482
- res.end(JSON.stringify({ added: converted.length }));
684
+ res.end(JSON.stringify(withFakes ? {
685
+ added: converted.length,
686
+ mcpFakesAdded
687
+ } : { added: converted.length }));
483
688
  return true;
484
689
  }
485
690
  if (subPath === "/fixtures" && req.method === "DELETE") {
486
- fixtures.length = 0;
691
+ require_fixture_loader.clearFixtureQueue(fixtures);
692
+ for (const { handler } of mounts) handler.clearMcpFakes?.();
487
693
  if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
488
694
  res.writeHead(200, { "Content-Type": "application/json" });
489
695
  res.end(JSON.stringify({ cleared: true }));
@@ -496,7 +702,8 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
496
702
  veoVideoJobs,
497
703
  grokVideoJobs,
498
704
  bytePlusVideoJobs,
499
- defaults
705
+ defaults,
706
+ mounts
500
707
  });
501
708
  if (subPath === "/reset" && req.method === "POST") {
502
709
  performFullReset(fixtures, resetTargets());
@@ -577,32 +784,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
577
784
  }
578
785
  }
579
786
  }
580
- const errorFixture = {
581
- match: { predicate: () => true },
582
- response: {
583
- error: {
584
- message: errorBody?.message ?? "Injected error",
585
- type: errorBody?.type ?? "server_error",
586
- code: errorBody?.code
587
- },
588
- status
589
- }
590
- };
591
- fixtures.unshift(errorFixture);
592
- let consumed = false;
593
- const original = errorFixture.match.predicate;
594
- errorFixture.match.predicate = (req) => {
595
- if (consumed) return false;
596
- const result = original(req);
597
- if (result) {
598
- consumed = true;
599
- queueMicrotask(() => {
600
- const idx = fixtures.indexOf(errorFixture);
601
- if (idx !== -1) fixtures.splice(idx, 1);
602
- });
603
- }
604
- return result;
605
- };
787
+ require_fixture_loader.queueOneShotError(fixtures, status, errorBody ?? void 0);
606
788
  res.writeHead(200, { "Content-Type": "application/json" });
607
789
  res.end(JSON.stringify({ queued: true }));
608
790
  return true;
@@ -610,6 +792,19 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
610
792
  handleNotFound(res, `Unknown control endpoint: ${pathname}`);
611
793
  return true;
612
794
  }
795
+ /**
796
+ * The client tore the connection down before its request body had fully
797
+ * arrived. Node rejects the pending body read with `aborted` (ECONNRESET) and
798
+ * destroys the response, so the rejection surfaces in the handler's read arm
799
+ * looking like any other thrown error. It is caller behaviour, not a server
800
+ * fault: no status can reach the client, so none must be journaled as
801
+ * delivered. A body that blew its size cap also leaves the request destroyed
802
+ * and incomplete, but that one is aimock's own decision, not a peer abort.
803
+ */
804
+ function clientAbortedMidBody(req, res, err) {
805
+ if (err instanceof require_helpers.RequestBodyTooLargeError) return false;
806
+ return res.destroyed && !req.complete;
807
+ }
613
808
  async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey, openRouter = false) {
614
809
  setCorsHeaders(res);
615
810
  let raw;
@@ -617,19 +812,43 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
617
812
  raw = await require_helpers.readBody(req);
618
813
  } catch (err) {
619
814
  const msg = err instanceof Error ? err.message : "Failed to read request body";
815
+ const clientFault = err instanceof require_helpers.RequestBodyTooLargeError;
816
+ const route = `${req.method ?? "POST"} ${req.url ?? require_route_registry.COMPLETIONS_PATH}`;
817
+ const clientAbort = clientAbortedMidBody(req, res, err);
818
+ const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
819
+ if (clientAbort || bodyLimitDisconnect) {
820
+ const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
821
+ defaults.logger.warn(bodyLimitDisconnect ? `${route}: ${msg}` : `${route}: client aborted mid-body (${msg})`);
822
+ journal.add({
823
+ method: req.method ?? "POST",
824
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
825
+ headers: require_helpers.flattenHeaders(req.headers),
826
+ body: null,
827
+ response: {
828
+ status: 0,
829
+ fixture: null,
830
+ interrupted: true,
831
+ interruptReason
832
+ }
833
+ });
834
+ return;
835
+ }
836
+ const status = clientFault ? 400 : 500;
837
+ if (clientFault) defaults.logger.warn(`${route}: ${msg}`);
838
+ else defaults.logger.error(`${route}: ${msg}`);
620
839
  journal.add({
621
840
  method: req.method ?? "POST",
622
- path: req.url ?? COMPLETIONS_PATH,
841
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
623
842
  headers: require_helpers.flattenHeaders(req.headers),
624
843
  body: null,
625
844
  response: {
626
- status: 500,
845
+ status,
627
846
  fixture: null
628
847
  }
629
848
  });
630
- require_sse_writer.writeErrorResponse(res, 500, openRouter ? require_openrouter_chat.serializeOpenRouterError(500, `Request body read failed: ${msg}`) : JSON.stringify({ error: {
849
+ require_sse_writer.writeErrorResponse(res, status, openRouter ? require_openrouter_chat.serializeOpenRouterError(status, `Request body read failed: ${msg}`) : JSON.stringify({ error: {
631
850
  message: `Request body read failed: ${msg}`,
632
- type: "server_error"
851
+ type: clientFault ? "invalid_request_error" : "server_error"
633
852
  } }));
634
853
  return;
635
854
  }
@@ -640,7 +859,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
640
859
  const detail = parseErr instanceof Error ? parseErr.message : "unknown parse error";
641
860
  journal.add({
642
861
  method: req.method ?? "POST",
643
- path: req.url ?? COMPLETIONS_PATH,
862
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
644
863
  headers: require_helpers.flattenHeaders(req.headers),
645
864
  body: null,
646
865
  response: {
@@ -659,7 +878,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
659
878
  if (!require_helpers.isJsonObject(body)) {
660
879
  journal.add({
661
880
  method: req.method ?? "POST",
662
- path: req.url ?? COMPLETIONS_PATH,
881
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
663
882
  headers: require_helpers.flattenHeaders(req.headers),
664
883
  body: null,
665
884
  response: {
@@ -677,7 +896,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
677
896
  if (!Array.isArray(body.messages)) {
678
897
  journal.add({
679
898
  method: req.method ?? "POST",
680
- path: req.url ?? COMPLETIONS_PATH,
899
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
681
900
  headers: require_helpers.flattenHeaders(req.headers),
682
901
  body: null,
683
902
  response: {
@@ -693,8 +912,53 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
693
912
  } }));
694
913
  return;
695
914
  }
915
+ const nullMessageIndex = body.messages.findIndex((message) => message === null);
916
+ if (nullMessageIndex !== -1) {
917
+ const message = `Invalid request: messages[${nullMessageIndex}] must be an object`;
918
+ journal.add({
919
+ method: req.method ?? "POST",
920
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
921
+ headers: require_helpers.flattenHeaders(req.headers),
922
+ body: null,
923
+ response: {
924
+ status: 400,
925
+ fixture: null
926
+ }
927
+ });
928
+ require_sse_writer.writeErrorResponse(res, 400, openRouter ? require_openrouter_chat.serializeOpenRouterError(400, message) : JSON.stringify({ error: {
929
+ message,
930
+ type: "invalid_request_error",
931
+ param: null,
932
+ code: null
933
+ } }));
934
+ return;
935
+ }
936
+ if (new URL(req.url ?? require_route_registry.COMPLETIONS_PATH, "http://localhost").pathname === require_route_registry.COMPLETIONS_PATH) {
937
+ let toolsError = require_helpers.validateToolsField(body.tools);
938
+ if (!toolsError && Array.isArray(body.tools)) {
939
+ const nullFunctionIndex = body.tools.findIndex((tool) => tool?.function === null);
940
+ if (nullFunctionIndex !== -1) toolsError = `tools[${nullFunctionIndex}].function must not be null`;
941
+ }
942
+ if (toolsError) {
943
+ journal.add({
944
+ method: req.method ?? "POST",
945
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
946
+ headers: require_helpers.flattenHeaders(req.headers),
947
+ body: null,
948
+ response: {
949
+ status: 400,
950
+ fixture: null
951
+ }
952
+ });
953
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
954
+ message: toolsError,
955
+ type: "invalid_request_error"
956
+ } }));
957
+ return;
958
+ }
959
+ }
696
960
  const method = req.method ?? "POST";
697
- const path = req.url ?? COMPLETIONS_PATH;
961
+ const path = req.url ?? require_route_registry.COMPLETIONS_PATH;
698
962
  const flatHeaders = require_helpers.flattenHeaders(req.headers);
699
963
  body._endpointType = "chat";
700
964
  body._context = require_helpers.getContext(req);
@@ -705,7 +969,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
705
969
  let preResolvedResponse = null;
706
970
  let responseModel = body.model;
707
971
  let fixtureCountIncremented = false;
708
- if (openRouter && Array.isArray(body.models) && body.models.length > 0) {
972
+ const openRouterFallback = openRouter && Array.isArray(body.models) && body.models.length > 0;
973
+ const selectFixture = (probe, counts) => {
974
+ for (;;) {
975
+ const attempt = require_router.matchFixtureDiagnostic(fixtures, probe, counts, defaults.requestTransform, matchOptions);
976
+ if (attempt.fixture && require_fixture_loader.isOneShotError(attempt.fixture) && !require_fixture_loader.claimOneShotError(fixtures, attempt.fixture)) continue;
977
+ return attempt;
978
+ }
979
+ };
980
+ if (openRouterFallback) {
709
981
  const candidates = require_openrouter_chat.buildOpenRouterCandidates(body);
710
982
  let matchCounts = journal.getFixtureMatchCountsForTest(testId);
711
983
  const allowFallbacks = body.provider?.allow_fallbacks !== false;
@@ -716,7 +988,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
716
988
  ...body,
717
989
  model: candidate
718
990
  };
719
- const attempt = require_router.matchFixtureDiagnostic(fixtures, probe, matchCounts, defaults.requestTransform, matchOptions);
991
+ const attempt = selectFixture(probe, matchCounts);
720
992
  skippedBySequenceOrTurn = Math.max(skippedBySequenceOrTurn, attempt.skippedBySequenceOrTurn);
721
993
  if (!attempt.fixture) return null;
722
994
  const response = await require_helpers.resolveResponse(attempt.fixture, probe);
@@ -753,41 +1025,54 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
753
1025
  preResolvedResponse = lastErrorResponse;
754
1026
  }
755
1027
  } else {
756
- const single = require_router.matchFixtureDiagnostic(fixtures, body, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, matchOptions);
1028
+ const single = selectFixture(body, journal.getFixtureMatchCountsForTest(testId));
757
1029
  fixture = single.fixture;
758
1030
  skippedBySequenceOrTurn = single.skippedBySequenceOrTurn;
759
1031
  }
760
1032
  if (fixture) {
761
1033
  if (!fixtureCountIncremented) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
762
- defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
1034
+ defaults.logger.debug(`Fixture matched: ${require_helpers.describeMatch(fixture.match, fixtures.indexOf(fixture))}`);
763
1035
  } else {
764
1036
  const lastUserMsg = body.messages.filter((m) => m.role === "user").pop();
765
1037
  const snippet = typeof lastUserMsg?.content === "string" ? lastUserMsg.content.slice(0, 80) : "";
766
1038
  defaults.logger.debug(`No fixture matched for request (model=${body.model ?? "?"}, msg="${snippet}")`);
767
1039
  }
768
- const chaosAction = require_chaos.evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger, req.url);
1040
+ const chaosConfig = require_chaos.resolveChaosConfig(fixture, defaults.chaos, req.headers, defaults.logger, req.url);
1041
+ await require_chaos.awaitChaosLatency(fixture, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
1042
+ const goneAfterLatency = require_chaos.responseGoneReason(res);
1043
+ if (goneAfterLatency !== null) {
1044
+ require_fixture_loader.releaseOneShotError(fixtures, fixture);
1045
+ defaults.logger.debug(`[chaos] ${method} ${path}: ${require_chaos.describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
1046
+ return;
1047
+ }
1048
+ const chaosAction = require_chaos.evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
769
1049
  const chaosContext = {
770
1050
  method,
771
1051
  path,
772
1052
  headers: flatHeaders,
773
1053
  body
774
1054
  };
775
- if (chaosAction === "drop" || chaosAction === "disconnect") {
776
- require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "proxy", defaults.registry);
1055
+ const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
1056
+ const missWouldProxy = require_helpers.wouldProxyMiss(effectiveStrict, defaults.record, providerKey);
1057
+ const noFixtureSource = missWouldProxy ? "proxy" : "internal";
1058
+ if (chaosAction === "drop" || chaosAction === "disconnect" || chaosAction === "rateLimit") {
1059
+ require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : noFixtureSource, defaults.registry, defaults.logger);
1060
+ require_fixture_loader.releaseOneShotError(fixtures, fixture);
777
1061
  return;
778
1062
  }
779
- if (fixture && chaosAction === "malformed") {
780
- require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, "fixture", defaults.registry);
1063
+ if (chaosAction === "malformed" && (fixture || !missWouldProxy)) {
1064
+ require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "internal", defaults.registry, defaults.logger);
1065
+ require_fixture_loader.releaseOneShotError(fixtures, fixture);
781
1066
  return;
782
1067
  }
783
1068
  if (!fixture) {
784
- if (require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
1069
+ if (effectiveStrict) {
785
1070
  const strictStatus = 503;
786
1071
  const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
787
- defaults.logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", req.url ?? COMPLETIONS_PATH, skippedBySequenceOrTurn));
1072
+ defaults.logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", req.url ?? require_route_registry.COMPLETIONS_PATH, skippedBySequenceOrTurn));
788
1073
  journal.add({
789
1074
  method: req.method ?? "POST",
790
- path: req.url ?? COMPLETIONS_PATH,
1075
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
791
1076
  headers: require_helpers.flattenHeaders(req.headers),
792
1077
  body,
793
1078
  response: {
@@ -807,7 +1092,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
807
1092
  if (defaults.record && providerKey) {
808
1093
  const hookOptions = chaosAction === "malformed" ? {
809
1094
  beforeWriteResponse: () => {
810
- require_chaos.applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry);
1095
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry, defaults.logger);
811
1096
  return true;
812
1097
  },
813
1098
  onHookBypassed: (reason) => {
@@ -819,26 +1104,47 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
819
1104
  });
820
1105
  }
821
1106
  } : void 0;
822
- const outcome = await require_recorder.proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions);
1107
+ let clientHungUp = false;
1108
+ const noteClientHangUp = () => {
1109
+ if (!res.writableEnded) clientHungUp = true;
1110
+ };
1111
+ req.socket?.on("end", noteClientHangUp);
1112
+ req.socket?.on("error", noteClientHangUp);
1113
+ let outcome;
1114
+ try {
1115
+ outcome = await require_recorder.proxyAndRecord(req, res, body, providerKey, req.url ?? require_route_registry.COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions);
1116
+ } finally {
1117
+ req.socket?.off("end", noteClientHangUp);
1118
+ req.socket?.off("error", noteClientHangUp);
1119
+ }
823
1120
  if (outcome === "handled_by_hook") return;
824
1121
  if (outcome !== "not_configured") {
1122
+ const destroyedMidStream = res.destroyed && !res.writableEnded;
825
1123
  journal.add({
826
1124
  method: req.method ?? "POST",
827
- path: req.url ?? COMPLETIONS_PATH,
1125
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
828
1126
  headers: require_helpers.flattenHeaders(req.headers),
829
1127
  body,
830
1128
  response: {
831
1129
  status: res.statusCode ?? 200,
832
1130
  fixture: null,
833
- source: "proxy"
1131
+ source: "proxy",
1132
+ ...destroyedMidStream ? {
1133
+ interrupted: true,
1134
+ interruptReason: clientHungUp ? "client aborted" : "proxy stream destroyed"
1135
+ } : {}
834
1136
  }
835
1137
  });
836
1138
  return;
837
1139
  }
1140
+ if (chaosAction === "malformed") {
1141
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, chaosContext, "internal", defaults.registry, defaults.logger);
1142
+ return;
1143
+ }
838
1144
  }
839
1145
  journal.add({
840
1146
  method: req.method ?? "POST",
841
- path: req.url ?? COMPLETIONS_PATH,
1147
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
842
1148
  headers: require_helpers.flattenHeaders(req.headers),
843
1149
  body,
844
1150
  response: {
@@ -876,7 +1182,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
876
1182
  const status = response.status ?? 500;
877
1183
  journal.add({
878
1184
  method: req.method ?? "POST",
879
- path: req.url ?? COMPLETIONS_PATH,
1185
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
880
1186
  headers: require_helpers.flattenHeaders(req.headers),
881
1187
  body,
882
1188
  response: {
@@ -890,7 +1196,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
890
1196
  if (require_helpers.isAudioResponse(response)) {
891
1197
  journal.add({
892
1198
  method: req.method ?? "POST",
893
- path: req.url ?? COMPLETIONS_PATH,
1199
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
894
1200
  headers: require_helpers.flattenHeaders(req.headers),
895
1201
  body,
896
1202
  response: {
@@ -910,10 +1216,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
910
1216
  const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
911
1217
  const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
912
1218
  const streaming = body.stream === true;
913
- const streamingBlocks = streaming && response.blocks && response.blocks.length > 0 ? require_helpers.resolveFixtureBlocks(response.blocks) : void 0;
1219
+ const blockOutcome = response.blocks && response.blocks.length > 0 ? require_helpers.resolveFixtureBlockOutcome(response.blocks) : void 0;
914
1220
  const journalEntry = journal.add({
915
1221
  method: req.method ?? "POST",
916
- path: req.url ?? COMPLETIONS_PATH,
1222
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
917
1223
  headers: require_helpers.flattenHeaders(req.headers),
918
1224
  body,
919
1225
  response: {
@@ -922,12 +1228,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
922
1228
  }
923
1229
  });
924
1230
  if (!streaming) {
925
- const completion = require_helpers.buildContentWithToolCallsCompletion(response.content ?? "", response.toolCalls ?? [], responseModel, effReasoning, overrides, body.messages);
1231
+ const completion = require_helpers.buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? response.toolCalls ?? [], responseModel, effReasoning, blockOutcome ? {
1232
+ ...overrides,
1233
+ finishReason: overrides?.finishReason ?? (blockOutcome.hasToolCalls ? "tool_calls" : "stop")
1234
+ } : overrides, body.messages);
926
1235
  res.writeHead(200, { "Content-Type": "application/json" });
927
1236
  res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
928
1237
  } else {
929
- const chunks = require_helpers.buildContentWithToolCallsChunks(response.content ?? "", response.toolCalls ?? [], responseModel, chunkSize, effReasoning, overrides, streamingBlocks);
930
- const completionText = streamingBlocks ? streamingBlocks.map((b) => b.type === "text" ? b.text : b.name + b.arguments).join("") : (response.content ?? "") + (response.toolCalls ?? []).map((tc) => tc.name + tc.arguments).join("");
1238
+ const chunks = require_helpers.buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? response.toolCalls ?? [], responseModel, chunkSize, effReasoning, overrides, blockOutcome?.ordered);
1239
+ 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("");
931
1240
  const usageChunk = emitStreamingUsage ? require_helpers.buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
932
1241
  shapeORChunks(chunks, usageChunk, overrides);
933
1242
  const interruption = require_interruption.createInterruptionSignal(fixture);
@@ -956,7 +1265,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
956
1265
  const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
957
1266
  const journalEntry = journal.add({
958
1267
  method: req.method ?? "POST",
959
- path: req.url ?? COMPLETIONS_PATH,
1268
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
960
1269
  headers: require_helpers.flattenHeaders(req.headers),
961
1270
  body,
962
1271
  response: {
@@ -998,7 +1307,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
998
1307
  const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
999
1308
  const journalEntry = journal.add({
1000
1309
  method: req.method ?? "POST",
1001
- path: req.url ?? COMPLETIONS_PATH,
1310
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
1002
1311
  headers: require_helpers.flattenHeaders(req.headers),
1003
1312
  body,
1004
1313
  response: {
@@ -1036,7 +1345,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
1036
1345
  }
1037
1346
  journal.add({
1038
1347
  method: req.method ?? "POST",
1039
- path: req.url ?? COMPLETIONS_PATH,
1348
+ path: req.url ?? require_route_registry.COMPLETIONS_PATH,
1040
1349
  headers: require_helpers.flattenHeaders(req.headers),
1041
1350
  body,
1042
1351
  response: {
@@ -1057,9 +1366,24 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1057
1366
  }
1058
1367
  /** @internal Config startup uses this to avoid parsing a selected auth source twice. */
1059
1368
  async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mounts, serviceFixtures) {
1369
+ const logger = new require_logger.Logger(options?.logLevel ?? "silent");
1370
+ const mountList = mounts ?? [];
1371
+ const handOff = serviceFixtures?.mcpFakes && serviceFixtures.mcpFakes.length > 0 ? require_mcp_fakes_mount.handOffMcpFakes(mountList, serviceFixtures.mcpFakes, logger) : null;
1372
+ try {
1373
+ return await startServer(fixtures, options, resolvedAuth, mountList, logger, serviceFixtures, () => handOff?.commit());
1374
+ } catch (err) {
1375
+ handOff?.undo();
1376
+ throw err;
1377
+ }
1378
+ }
1379
+ /**
1380
+ * The server of `createServerWithResolvedAuth`, after the MCP fakes hand-off.
1381
+ * `commitHandOff` runs once the server listens; if it throws, the server is
1382
+ * closed and start rejects.
1383
+ */
1384
+ async function startServer(fixtures, options, resolvedAuth, mountList, logger, serviceFixtures, commitHandOff) {
1060
1385
  const host = options?.host ?? "127.0.0.1";
1061
1386
  const port = options?.port ?? 0;
1062
- const logger = new require_logger.Logger(options?.logLevel ?? "silent");
1063
1387
  const registry = options?.metrics ? require_metrics.createMetricsRegistry() : void 0;
1064
1388
  const serverOptions = options ?? {};
1065
1389
  const chaosByTestId = /* @__PURE__ */ new Map();
@@ -1111,21 +1435,14 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1111
1435
  }
1112
1436
  };
1113
1437
  if (options?.chaos) {
1114
- const chaosRates = [
1115
- {
1116
- name: "dropRate",
1117
- value: options.chaos.dropRate
1118
- },
1119
- {
1120
- name: "malformedRate",
1121
- value: options.chaos.malformedRate
1122
- },
1123
- {
1124
- name: "disconnectRate",
1125
- value: options.chaos.disconnectRate
1126
- }
1127
- ];
1128
- for (const { name, value } of chaosRates) if (value !== void 0 && (value < 0 || value > 1)) logger.warn(`Chaos ${name} (${value}) is outside 0-1 range — will be clamped at runtime`);
1438
+ const chaosDefaults = options.chaos;
1439
+ for (const field of require_chaos.CHAOS_FIELD_NAMES) {
1440
+ const value = chaosDefaults[field];
1441
+ if (value === void 0) continue;
1442
+ if (typeof value === "number" && require_chaos.parseChaosField(field, value) !== void 0) continue;
1443
+ const shape = require_chaos.CHAOS_FIELDS[field].integer ? "a whole number of ms" : "a number";
1444
+ logger.warn(`Chaos default ${field} value ${JSON.stringify(value)} — must be ${shape} in [0,${require_chaos.CHAOS_FIELDS[field].max}]; rejected at runtime, never clamped: this default is ignored`);
1445
+ }
1129
1446
  }
1130
1447
  for (const { name, config } of [
1131
1448
  {
@@ -1171,123 +1488,315 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1171
1488
  if (basePath !== void 0 && /(^|\/)api\/v3\/?$/.test(basePath)) throw new Error(`record.providers.byteplus (${bytePlusBase}) ends in /api/v3, but aimock appends the /api/v3 prefix itself — every BytePlus request would go to /api/v3/api/v3/... and fail upstream. Configure the ORIGIN only (e.g. https://ark.ap-southeast.bytepluses.com).`);
1172
1489
  }
1173
1490
  }
1491
+ /**
1492
+ * The in-flight request, scoped to the async execution that serves it.
1493
+ * Entered once in the `http.createServer` callback, so every handler —
1494
+ * including the ones in other modules that only ever see the shared
1495
+ * `journal` — runs inside it.
1496
+ */
1497
+ const requestScope = new node_async_hooks.AsyncLocalStorage();
1498
+ /** The journal entry each request produced, keyed by the request object. */
1499
+ const ownJournalEntry = /* @__PURE__ */ new WeakMap();
1174
1500
  const journal = new require_journal.Journal({
1175
1501
  maxEntries: options?.journalMaxEntries ?? 1e3,
1176
- fixtureCountsMaxTestIds: options?.fixtureCountsMaxTestIds ?? 500
1502
+ fixtureCountsMaxTestIds: options?.fixtureCountsMaxTestIds ?? 500,
1503
+ onAdd: (entry) => {
1504
+ const req = requestScope.getStore();
1505
+ if (req) ownJournalEntry.set(req, entry);
1506
+ }
1177
1507
  });
1178
1508
  const videoStates = new require_video.VideoStateMap();
1179
1509
  const openRouterVideoJobs = new require_openrouter_video.OpenRouterVideoJobMap();
1180
1510
  const veoVideoJobs = new require_veo_video.VeoVideoJobMap();
1181
1511
  const grokVideoJobs = new require_grok_video.GrokVideoJobMap();
1182
1512
  const bytePlusVideoJobs = new require_byteplus_video.BytePlusVideoJobMap();
1183
- if (mounts) for (const { handler } of mounts) {
1513
+ /**
1514
+ * The OpenAI error `type` that goes with a status: 4xx is the caller's
1515
+ * fault, everything else is ours. Shared by `routeError` and the envelopes
1516
+ * it calls so the body can never disagree with the status line.
1517
+ */
1518
+ function errorTypeForStatus(status) {
1519
+ return status >= 400 && status < 500 ? "invalid_request_error" : "server_error";
1520
+ }
1521
+ /**
1522
+ * The newest journal entry this request already produced, or null.
1523
+ *
1524
+ * Attribution is by the identity of the `IncomingMessage` the entry was
1525
+ * written under — never by `x-request-id`. That header is CALLER-supplied
1526
+ * (`resolveRequestId` echoes a well-formed one) and nothing makes it
1527
+ * unique: a retry, a harness that pins one id, or a load generator sends
1528
+ * the same id on many requests, and keying off it made a crash rewrite an
1529
+ * EARLIER request's entry while leaving the crashing request untraced. The
1530
+ * request object cannot be collided by a caller, so one entry belongs to
1531
+ * exactly one request. A handler writes at most one entry; if it writes
1532
+ * more, the newest wins, which is the entry the old scan would have found.
1533
+ */
1534
+ function lastJournalEntryFor(req) {
1535
+ return ownJournalEntry.get(req) ?? null;
1536
+ }
1537
+ /**
1538
+ * Uniform terminal arm for a route handler that threw. Every route error
1539
+ * path logs the route, journals the failure, sets CORS and answers the error
1540
+ * envelope — so a handler crash is never invisible in the logs or in
1541
+ * `/__aimock/journal`.
1542
+ *
1543
+ * ONE request produces ONE entry. `res.headersSent` is not the test for that:
1544
+ * a handler that journaled its 200 and then threw before `writeHead` has
1545
+ * sent no headers, and journaling again would leave two contradicting
1546
+ * entries for one request. So the existing entry is looked up by request
1547
+ * identity ({@link lastJournalEntryFor}) and AMENDED — status to what the
1548
+ * client actually got when the response is still changeable, or interrupted
1549
+ * when it isn't — and a fresh entry is
1550
+ * added only when the handler journaled nothing. That holds after the
1551
+ * response too: a handler that answered and then crashed before its own
1552
+ * journal write gets an entry carrying the status already sent, so a
1553
+ * post-response crash is never invisible.
1554
+ *
1555
+ * Once headers are on the wire the status can't change, so the socket is
1556
+ * destroyed: a clean `end()` reads as EOF and the client treats a truncated
1557
+ * stream as a complete response. The chat-style SSE routes whose clients
1558
+ * surface an error frame pass `streamEvent`; that frame is written first,
1559
+ * but the tear-down still follows it.
1560
+ *
1561
+ * A body that blew its size cap is the CALLER's fault, not a server fault:
1562
+ * it logs at warn, but if the reader destroyed the socket it journals an
1563
+ * interruption with status 0 because no 4xx reached the client. A client
1564
+ * that hung up before its body arrived
1565
+ * ({@link clientAbortedMidBody}) is not a fault at all: it logs at warn and
1566
+ * journals a torn socket (`interrupted`, status 0), never a 500 the client
1567
+ * could not have received.
1568
+ */
1569
+ function routeError(req, res, err, pathname, opts) {
1570
+ const route = `${req.method ?? "?"} ${pathname}`;
1571
+ const msg = err instanceof Error ? err.message : "Internal error";
1572
+ const clientFault = err instanceof require_helpers.RequestBodyTooLargeError;
1573
+ const clientAbort = clientAbortedMidBody(req, res, err);
1574
+ const status = clientFault ? 400 : 500;
1575
+ if (clientAbort) logger.warn(`${route}: client aborted mid-body (${msg})`);
1576
+ else if (clientFault) logger.warn(`${route}: ${msg}`);
1577
+ else logger.error(`${route}: ${msg}`);
1578
+ if (err instanceof Error && err.stack) logger.debug(err.stack);
1579
+ const existing = lastJournalEntryFor(req);
1580
+ const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
1581
+ if ((clientAbort || bodyLimitDisconnect) && !res.headersSent) {
1582
+ const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
1583
+ if (existing) {
1584
+ if (!existing.response.interrupted) {
1585
+ existing.response.status = 0;
1586
+ existing.response.interrupted = true;
1587
+ existing.response.interruptReason = interruptReason;
1588
+ }
1589
+ return;
1590
+ }
1591
+ try {
1592
+ journal.add({
1593
+ method: req.method ?? "?",
1594
+ path: req.url ?? "?",
1595
+ headers: require_helpers.flattenHeaders(req.headers),
1596
+ body: null,
1597
+ ...opts?.service ? { service: opts.service } : {},
1598
+ response: {
1599
+ status: 0,
1600
+ fixture: null,
1601
+ source: "internal",
1602
+ interrupted: true,
1603
+ interruptReason
1604
+ }
1605
+ });
1606
+ } catch (jErr) {
1607
+ logger.warn(`${route}: journal write failed after body-read interruption: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1608
+ }
1609
+ return;
1610
+ }
1611
+ if (!res.headersSent) {
1612
+ if (existing) existing.response.status = status;
1613
+ else try {
1614
+ journal.add({
1615
+ method: req.method ?? "?",
1616
+ path: req.url ?? "?",
1617
+ headers: require_helpers.flattenHeaders(req.headers),
1618
+ body: null,
1619
+ ...opts?.service ? { service: opts.service } : {},
1620
+ response: {
1621
+ status,
1622
+ fixture: null,
1623
+ source: "internal"
1624
+ }
1625
+ });
1626
+ } catch (jErr) {
1627
+ logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1628
+ }
1629
+ setCorsHeaders(res);
1630
+ require_sse_writer.writeErrorResponse(res, status, opts?.envelope ? opts.envelope(msg, status) : JSON.stringify({ error: {
1631
+ message: msg,
1632
+ type: errorTypeForStatus(status)
1633
+ } }));
1634
+ } else {
1635
+ const midStreamReason = clientAbort ? "client aborted" : "handler crashed mid-stream";
1636
+ if (existing) {
1637
+ if (res.writableEnded) {
1638
+ if (existing.response.error === void 0) existing.response.error = msg;
1639
+ } else if (!existing.response.interrupted) {
1640
+ existing.response.interrupted = true;
1641
+ existing.response.interruptReason = midStreamReason;
1642
+ }
1643
+ } else try {
1644
+ journal.add({
1645
+ method: req.method ?? "?",
1646
+ path: req.url ?? "?",
1647
+ headers: require_helpers.flattenHeaders(req.headers),
1648
+ body: null,
1649
+ ...opts?.service ? { service: opts.service } : {},
1650
+ response: {
1651
+ status: res.statusCode,
1652
+ fixture: null,
1653
+ source: "internal",
1654
+ ...res.writableEnded ? { error: msg } : {
1655
+ interrupted: true,
1656
+ interruptReason: midStreamReason
1657
+ }
1658
+ }
1659
+ });
1660
+ } catch (jErr) {
1661
+ logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1662
+ }
1663
+ if (res.writableEnded) return;
1664
+ if (opts?.streamEvent) try {
1665
+ res.write(opts.streamEvent(msg, status), () => {
1666
+ if (!res.destroyed) res.destroy();
1667
+ });
1668
+ } catch (writeErr) {
1669
+ logger.debug("Failed to write error recovery response:", writeErr);
1670
+ res.destroy();
1671
+ }
1672
+ else res.destroy();
1673
+ }
1674
+ }
1675
+ for (const { handler } of mountList) {
1184
1676
  if (handler.setJournal) handler.setJournal(journal);
1185
1677
  if (registry && handler.setRegistry) handler.setRegistry(registry);
1678
+ if (handler.setLogger) handler.setLogger(logger);
1186
1679
  }
1187
1680
  if (registry) registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
1188
1681
  const server = node_http.createServer((req, res) => {
1189
- handleHttpRequest(req, res).catch((err) => {
1190
- const msg = err instanceof Error ? err.message : "Internal error";
1191
- const stack = err instanceof Error ? err.stack ?? msg : msg;
1192
- const method = req.method ?? "?";
1193
- const url = req.url ?? "?";
1194
- defaults.logger.warn(`Unhandled request error on ${method} ${url}: ${msg}\n${stack}`);
1195
- if (!res.headersSent) {
1196
- res.writeHead(500, { "Content-Type": "application/json" });
1197
- res.end(JSON.stringify({ error: {
1198
- message: msg,
1199
- type: "server_error"
1200
- } }));
1201
- } else if (!res.writableEnded) res.end();
1682
+ requestScope.run(req, () => {
1683
+ handleHttpRequest(req, res).catch((err) => {
1684
+ routeError(req, res, err, req.url ?? "?");
1685
+ });
1202
1686
  });
1203
1687
  });
1204
1688
  async function handleHttpRequest(req, res) {
1205
1689
  const startTime = registry ? process.hrtime.bigint() : 0n;
1206
- const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1207
- let pathname = parsedUrl.pathname;
1690
+ const { id: requestId, generated: requestIdMinted } = require_helpers.resolveRequestId(req.headers);
1691
+ req.headers["x-request-id"] = requestId;
1692
+ require_helpers.markMintedRequestId(req, requestIdMinted);
1693
+ res.setHeader("X-Request-Id", requestId);
1694
+ let pathname = "";
1695
+ if (registry) {
1696
+ let recorded = false;
1697
+ const recordOutcome = (status) => {
1698
+ if (recorded) return;
1699
+ recorded = true;
1700
+ try {
1701
+ const normalizedPath = require_metrics.normalizePathLabel(pathname, mountList.map((m) => m.path));
1702
+ const method = req.method ?? "UNKNOWN";
1703
+ registry.incrementCounter("aimock_requests_total", {
1704
+ method,
1705
+ path: normalizedPath,
1706
+ status
1707
+ });
1708
+ const elapsed = Number(process.hrtime.bigint() - startTime) / 1e9;
1709
+ registry.observeHistogram("aimock_request_duration_seconds", {
1710
+ method,
1711
+ path: normalizedPath
1712
+ }, elapsed);
1713
+ } catch (err) {
1714
+ defaults.logger.warn("metrics instrumentation error", err);
1715
+ }
1716
+ };
1717
+ res.on("finish", () => recordOutcome(String(res.statusCode)));
1718
+ res.on("close", () => {
1719
+ if (!res.writableFinished) recordOutcome(require_metrics.DESTROYED_STATUS_LABEL);
1720
+ });
1721
+ }
1722
+ let parsedUrl;
1723
+ try {
1724
+ parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1725
+ } catch (err) {
1726
+ const route = `${req.method ?? "?"} ${req.url ?? "?"}`;
1727
+ const msg = err instanceof Error ? err.message : "Internal error";
1728
+ logger.error(`${route}: ${msg}`);
1729
+ if (err instanceof Error && err.stack) logger.debug(err.stack);
1730
+ try {
1731
+ journal.add({
1732
+ method: req.method ?? "?",
1733
+ path: req.url ?? "?",
1734
+ headers: require_helpers.flattenHeaders(req.headers),
1735
+ body: null,
1736
+ response: {
1737
+ status: 400,
1738
+ fixture: null,
1739
+ source: "internal"
1740
+ }
1741
+ });
1742
+ } catch (jErr) {
1743
+ logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1744
+ }
1745
+ setCorsHeaders(res);
1746
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
1747
+ message: "Invalid request target",
1748
+ type: "invalid_request_error"
1749
+ } }));
1750
+ return;
1751
+ }
1752
+ pathname = parsedUrl.pathname;
1208
1753
  const originalPathname = pathname;
1209
1754
  const isOpenRouter = require_openrouter_chat.isOpenRouterPath(originalPathname);
1210
1755
  const isBytePlusArk = defaults.record?.providers.byteplus !== void 0 && originalPathname.startsWith("/api/v3/");
1211
- if (registry) res.on("finish", () => {
1212
- try {
1213
- const normalizedPath = require_metrics.normalizePathLabel(pathname);
1214
- const method = req.method ?? "UNKNOWN";
1215
- const status = String(res.statusCode);
1216
- registry.incrementCounter("aimock_requests_total", {
1217
- method,
1218
- path: normalizedPath,
1219
- status
1220
- });
1221
- const elapsed = Number(process.hrtime.bigint() - startTime) / 1e9;
1222
- registry.observeHistogram("aimock_request_duration_seconds", {
1223
- method,
1224
- path: normalizedPath
1225
- }, elapsed);
1226
- } catch (err) {
1227
- defaults.logger.warn("metrics instrumentation error", err);
1228
- }
1229
- });
1230
1756
  if (req.method === "OPTIONS" && (!resolvedAuth.policy.enabled || req.headers.origin !== void 0 && req.headers["access-control-request-method"] !== void 0)) {
1231
1757
  handleOptions(res);
1232
1758
  return;
1233
1759
  }
1234
- if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname === "/metrics"))) {
1760
+ if (!(req.method === "GET" && (pathname === require_route_registry.HEALTH_PATH || pathname === require_route_registry.READY_PATH || pathname === require_route_registry.METRICS_PATH))) {
1235
1761
  if (!require_api_key_auth.validateRequestApiKey(req, resolvedAuth.policy).ok) {
1236
1762
  require_api_key_auth.writeApiKeyHttpRejection(res, setCorsHeaders);
1237
1763
  return;
1238
1764
  }
1239
1765
  require_api_key_auth.markAuthenticatedRequest(req, resolvedAuth.policy);
1240
1766
  }
1241
- if (pathname.startsWith(CONTROL_PREFIX)) {
1242
- await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults);
1767
+ if (pathname.startsWith(require_route_registry.CONTROL_PREFIX)) {
1768
+ await handleControlAPI(req, res, pathname, parsedUrl.searchParams, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, bytePlusVideoJobs, defaults, mountList);
1243
1769
  return;
1244
1770
  }
1245
- if (mounts) {
1246
- for (const { path: mountPath, handler } of mounts) if (pathname === mountPath || pathname.startsWith(mountPath + "/")) {
1247
- const subPath = pathname.slice(mountPath.length) || "/";
1248
- if (await handler.handleRequest(req, res, subPath)) return;
1249
- }
1771
+ for (const { path: mountPath, handler } of mountList) if (pathname === mountPath || pathname.startsWith(mountPath + "/")) {
1772
+ const subPath = pathname.slice(mountPath.length) || "/";
1773
+ if (await handler.handleRequest(req, res, subPath)) return;
1250
1774
  }
1251
- if (pathname === OLLAMA_CHAT_PATH && req.method === "POST") {
1775
+ if (pathname === require_route_registry.OLLAMA_CHAT_PATH && req.method === "POST") {
1252
1776
  try {
1253
1777
  await require_ollama.handleOllama(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1254
1778
  } catch (err) {
1255
- const msg = err instanceof Error ? err.message : "Internal error";
1256
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1257
- message: msg,
1258
- type: "server_error"
1259
- } }));
1260
- else if (!res.writableEnded) res.destroy();
1779
+ routeError(req, res, err, pathname);
1261
1780
  }
1262
1781
  return;
1263
1782
  }
1264
- if (pathname === OLLAMA_GENERATE_PATH && req.method === "POST") {
1783
+ if (pathname === require_route_registry.OLLAMA_GENERATE_PATH && req.method === "POST") {
1265
1784
  try {
1266
1785
  await require_ollama.handleOllamaGenerate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1267
1786
  } catch (err) {
1268
- const msg = err instanceof Error ? err.message : "Internal error";
1269
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1270
- message: msg,
1271
- type: "server_error"
1272
- } }));
1273
- else if (!res.writableEnded) res.destroy();
1787
+ routeError(req, res, err, pathname);
1274
1788
  }
1275
1789
  return;
1276
1790
  }
1277
- if ((pathname === OLLAMA_EMBEDDINGS_PATH || pathname === OLLAMA_EMBED_PATH) && req.method === "POST") {
1791
+ if ((pathname === require_route_registry.OLLAMA_EMBEDDINGS_PATH || pathname === require_route_registry.OLLAMA_EMBED_PATH) && req.method === "POST") {
1278
1792
  try {
1279
1793
  await require_ollama.handleOllamaEmbeddings(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1280
1794
  } catch (err) {
1281
- const msg = err instanceof Error ? err.message : "Internal error";
1282
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1283
- message: msg,
1284
- type: "server_error"
1285
- } }));
1286
- else if (!res.writableEnded) res.destroy();
1795
+ routeError(req, res, err, pathname);
1287
1796
  }
1288
1797
  return;
1289
1798
  }
1290
- if (pathname === OLLAMA_TAGS_PATH && req.method === "GET") {
1799
+ if (pathname === require_route_registry.OLLAMA_TAGS_PATH && req.method === "GET") {
1291
1800
  setCorsHeaders(res);
1292
1801
  const modelIds = /* @__PURE__ */ new Set();
1293
1802
  for (const f of fixtures) if (f.match.model && typeof f.match.model === "string") modelIds.add(f.match.model);
@@ -1303,212 +1812,86 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1303
1812
  res.end(JSON.stringify({ models }));
1304
1813
  return;
1305
1814
  }
1306
- const openRouterVideoContentMatch = pathname.match(require_metrics.OPENROUTER_VIDEO_CONTENT_RE);
1815
+ const openRouterVideoContentMatch = pathname.match(require_route_registry.OPENROUTER_VIDEO_CONTENT_RE);
1307
1816
  if (openRouterVideoContentMatch && req.method === "GET") {
1308
1817
  try {
1309
1818
  await require_openrouter_video.handleOpenRouterVideoContent(req, res, openRouterVideoContentMatch[1], journal, defaults, setCorsHeaders, openRouterVideoJobs);
1310
1819
  } catch (err) {
1311
- const msg = err instanceof Error ? err.message : "Internal error";
1312
- defaults.logger.error(`openrouter-video content: ${msg}`);
1313
- if (!res.headersSent) {
1314
- try {
1315
- journal.add({
1316
- method: req.method ?? "GET",
1317
- path: req.url ?? pathname,
1318
- headers: require_helpers.flattenHeaders(req.headers),
1319
- body: null,
1320
- response: {
1321
- status: 500,
1322
- fixture: null
1323
- }
1324
- });
1325
- } catch (jErr) {
1326
- defaults.logger.warn(`openrouter-video content: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1327
- }
1328
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1329
- message: msg,
1330
- type: "server_error"
1331
- } }));
1332
- } else if (!res.writableEnded) res.destroy();
1820
+ routeError(req, res, err, pathname);
1333
1821
  }
1334
1822
  return;
1335
1823
  }
1336
- if (pathname === OPENROUTER_VIDEO_MODELS_PATH && req.method === "GET") {
1824
+ if (pathname === require_route_registry.OPENROUTER_VIDEO_MODELS_PATH && req.method === "GET") {
1337
1825
  try {
1338
1826
  await require_openrouter_video.handleOpenRouterVideoModels(req, res, fixtures, journal, defaults, setCorsHeaders);
1339
1827
  } catch (err) {
1340
- const msg = err instanceof Error ? err.message : "Internal error";
1341
- defaults.logger.error(`openrouter-video models: ${msg}`);
1342
- if (!res.headersSent) {
1343
- try {
1344
- journal.add({
1345
- method: req.method ?? "GET",
1346
- path: req.url ?? pathname,
1347
- headers: require_helpers.flattenHeaders(req.headers),
1348
- body: null,
1349
- response: {
1350
- status: 500,
1351
- fixture: null
1352
- }
1353
- });
1354
- } catch (jErr) {
1355
- defaults.logger.warn(`openrouter-video models: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1356
- }
1357
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1358
- message: msg,
1359
- type: "server_error"
1360
- } }));
1361
- } else if (!res.writableEnded) res.destroy();
1828
+ routeError(req, res, err, pathname);
1362
1829
  }
1363
1830
  return;
1364
1831
  }
1365
- const openRouterVideoStatusMatch = pathname.match(require_metrics.OPENROUTER_VIDEO_STATUS_RE);
1832
+ const openRouterVideoStatusMatch = pathname.match(require_route_registry.OPENROUTER_VIDEO_STATUS_RE);
1366
1833
  if (openRouterVideoStatusMatch && req.method === "GET") {
1367
1834
  try {
1368
1835
  await require_openrouter_video.handleOpenRouterVideoStatus(req, res, openRouterVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
1369
1836
  } catch (err) {
1370
- const msg = err instanceof Error ? err.message : "Internal error";
1371
- defaults.logger.error(`openrouter-video status: ${msg}`);
1372
- if (!res.headersSent) {
1373
- try {
1374
- journal.add({
1375
- method: req.method ?? "GET",
1376
- path: req.url ?? pathname,
1377
- headers: require_helpers.flattenHeaders(req.headers),
1378
- body: null,
1379
- response: {
1380
- status: 500,
1381
- fixture: null
1382
- }
1383
- });
1384
- } catch (jErr) {
1385
- defaults.logger.warn(`openrouter-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1386
- }
1387
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1388
- message: msg,
1389
- type: "server_error"
1390
- } }));
1391
- } else if (!res.writableEnded) res.destroy();
1837
+ routeError(req, res, err, pathname);
1392
1838
  }
1393
1839
  return;
1394
1840
  }
1395
- if (pathname === OPENROUTER_VIDEOS_PATH && req.method === "POST") {
1841
+ if (pathname === require_route_registry.OPENROUTER_VIDEOS_PATH && req.method === "POST") {
1396
1842
  setCorsHeaders(res);
1397
1843
  try {
1398
1844
  await require_openrouter_video.handleOpenRouterVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
1399
1845
  } catch (err) {
1400
- const msg = err instanceof Error ? err.message : "Internal error";
1401
- defaults.logger.error(`openrouter-video submit: ${msg}`);
1402
- if (!res.headersSent) {
1403
- try {
1404
- journal.add({
1405
- method: req.method ?? "POST",
1406
- path: req.url ?? pathname,
1407
- headers: require_helpers.flattenHeaders(req.headers),
1408
- body: null,
1409
- response: {
1410
- status: 500,
1411
- fixture: null
1412
- }
1413
- });
1414
- } catch (jErr) {
1415
- defaults.logger.warn(`openrouter-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1416
- }
1417
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1418
- message: msg,
1419
- type: "server_error"
1420
- } }));
1421
- } else if (!res.writableEnded) res.destroy();
1846
+ routeError(req, res, err, pathname);
1422
1847
  }
1423
1848
  return;
1424
1849
  }
1425
- const bytePlusVideoStatusMatch = pathname.match(require_metrics.BYTEPLUS_VIDEO_STATUS_RE);
1850
+ const bytePlusVideoStatusMatch = pathname.match(require_route_registry.BYTEPLUS_VIDEO_STATUS_RE);
1426
1851
  if (bytePlusVideoStatusMatch && req.method === "GET") {
1427
1852
  try {
1428
1853
  await require_byteplus_video.handleBytePlusVideoStatus(req, res, bytePlusVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
1429
1854
  } catch (err) {
1430
- const msg = err instanceof Error ? err.message : "Internal error";
1431
- defaults.logger.error(`byteplus-video status: ${msg}`);
1432
- if (!res.headersSent) {
1433
- try {
1434
- journal.add({
1435
- method: req.method ?? "GET",
1436
- path: req.url ?? pathname,
1437
- headers: require_helpers.flattenHeaders(req.headers),
1438
- body: null,
1439
- response: {
1440
- status: 500,
1441
- fixture: null
1442
- }
1443
- });
1444
- } catch (jErr) {
1445
- defaults.logger.warn(`byteplus-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1446
- }
1447
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1448
- message: msg,
1449
- type: "server_error"
1450
- } }));
1451
- } else if (!res.writableEnded) res.destroy();
1855
+ routeError(req, res, err, pathname);
1452
1856
  }
1453
1857
  return;
1454
1858
  }
1455
- if (require_metrics.BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname) && req.method === "POST") {
1859
+ if (require_route_registry.BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname) && req.method === "POST") {
1456
1860
  setCorsHeaders(res);
1457
1861
  try {
1458
1862
  await require_byteplus_video.handleBytePlusVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
1459
1863
  } catch (err) {
1460
- const msg = err instanceof Error ? err.message : "Internal error";
1461
- defaults.logger.error(`byteplus-video submit: ${msg}`);
1462
- if (!res.headersSent) {
1463
- try {
1464
- journal.add({
1465
- method: req.method ?? "POST",
1466
- path: req.url ?? pathname,
1467
- headers: require_helpers.flattenHeaders(req.headers),
1468
- body: null,
1469
- response: {
1470
- status: 500,
1471
- fixture: null
1472
- }
1473
- });
1474
- } catch (jErr) {
1475
- defaults.logger.warn(`byteplus-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1476
- }
1477
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1478
- message: msg,
1479
- type: "server_error"
1480
- } }));
1481
- } else if (!res.writableEnded) res.destroy();
1864
+ routeError(req, res, err, pathname);
1482
1865
  }
1483
1866
  return;
1484
1867
  }
1485
- if (pathname === "/api/v1/models" && req.method === "GET") {
1868
+ if (pathname === require_route_registry.OPENROUTER_MODELS_PATH && req.method === "GET") {
1486
1869
  require_openrouter_chat.handleOpenRouterModels(req, res, fixtures, journal, defaults, setCorsHeaders);
1487
1870
  return;
1488
1871
  }
1489
- if (pathname === "/api/v1/key" && req.method === "GET") {
1872
+ if (pathname === require_route_registry.OPENROUTER_KEY_PATH && req.method === "GET") {
1490
1873
  require_openrouter_chat.handleOpenRouterKey(req, res, journal, setCorsHeaders);
1491
1874
  return;
1492
1875
  }
1493
- if (pathname === "/api/v1/credits" && req.method === "GET") {
1876
+ if (pathname === require_route_registry.OPENROUTER_CREDITS_PATH && req.method === "GET") {
1494
1877
  require_openrouter_chat.handleOpenRouterCredits(req, res, journal, setCorsHeaders);
1495
1878
  return;
1496
1879
  }
1497
1880
  let azureDeploymentId;
1498
- const azureMatch = pathname.match(AZURE_DEPLOYMENT_RE);
1881
+ const azureMatch = pathname.match(require_route_registry.AZURE_DEPLOYMENT_RE);
1499
1882
  if (azureMatch && req.method === "POST") {
1500
1883
  azureDeploymentId = azureMatch[1];
1501
1884
  pathname = `/v1/${azureMatch[2]}`;
1502
1885
  }
1503
1886
  if (!azureDeploymentId) pathname = normalizeCompatPath(pathname, logger);
1504
- if (pathname === HEALTH_PATH && req.method === "GET") {
1887
+ if (pathname === require_route_registry.HEALTH_PATH && req.method === "GET") {
1505
1888
  setCorsHeaders(res);
1506
- if (mounts && mounts.length > 0) {
1889
+ if (mountList.length > 0) {
1507
1890
  const services = { llm: {
1508
1891
  status: "ok",
1509
1892
  fixtures: fixtures.length
1510
1893
  } };
1511
- for (const { path: mountPath, handler } of mounts) if (handler.health) {
1894
+ for (const { path: mountPath, handler } of mountList) if (handler.health) {
1512
1895
  const name = mountPath.replace(/^\//, "");
1513
1896
  services[name] = handler.health();
1514
1897
  }
@@ -1523,13 +1906,13 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1523
1906
  }
1524
1907
  return;
1525
1908
  }
1526
- if (pathname === READY_PATH && req.method === "GET") {
1909
+ if (pathname === require_route_registry.READY_PATH && req.method === "GET") {
1527
1910
  setCorsHeaders(res);
1528
1911
  res.writeHead(200, { "Content-Type": "application/json" });
1529
1912
  res.end(JSON.stringify({ status: "ready" }));
1530
1913
  return;
1531
1914
  }
1532
- if (pathname === "/metrics" && req.method === "GET") {
1915
+ if (pathname === require_route_registry.METRICS_PATH && req.method === "GET") {
1533
1916
  if (!registry) {
1534
1917
  handleNotFound(res, "Not found");
1535
1918
  return;
@@ -1539,7 +1922,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1539
1922
  res.end(registry.serialize());
1540
1923
  return;
1541
1924
  }
1542
- if (pathname === MODELS_PATH && req.method === "GET") {
1925
+ if (pathname === require_route_registry.MODELS_PATH && req.method === "GET") {
1543
1926
  setCorsHeaders(res);
1544
1927
  const modelIds = /* @__PURE__ */ new Set();
1545
1928
  for (const f of fixtures) if (f.match.model && typeof f.match.model === "string") modelIds.add(f.match.model);
@@ -1556,14 +1939,261 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1556
1939
  }));
1557
1940
  return;
1558
1941
  }
1559
- if (pathname === REQUESTS_PATH) {
1942
+ const batchesCancelMatch = pathname.match(require_route_registry.BATCHES_CANCEL_RE);
1943
+ if (batchesCancelMatch && req.method === "POST") {
1944
+ try {
1945
+ await require_helpers.readBody(req);
1946
+ await require_batches.handleBatchesCancel(req, res, batchesCancelMatch[1], journal, defaults, setCorsHeaders);
1947
+ } catch (err) {
1948
+ routeError(req, res, err, pathname, { service: "batches" });
1949
+ }
1950
+ return;
1951
+ }
1952
+ const batchesIdMatch = pathname.match(require_route_registry.BATCHES_ID_RE);
1953
+ if (batchesIdMatch && req.method === "GET") {
1954
+ try {
1955
+ await require_batches.handleBatchesRetrieve(req, res, batchesIdMatch[1], journal, defaults, setCorsHeaders);
1956
+ } catch (err) {
1957
+ routeError(req, res, err, pathname, { service: "batches" });
1958
+ }
1959
+ return;
1960
+ }
1961
+ if (pathname === require_route_registry.BATCHES_PATH && req.method === "GET") {
1962
+ try {
1963
+ await require_batches.handleBatchesList(req, res, journal, defaults, setCorsHeaders);
1964
+ } catch (err) {
1965
+ routeError(req, res, err, pathname, { service: "batches" });
1966
+ }
1967
+ return;
1968
+ }
1969
+ if (pathname === require_route_registry.BATCHES_PATH && req.method === "POST") {
1970
+ try {
1971
+ await require_batches.handleBatchesCreate(req, res, await require_helpers.readBody(req), journal, defaults, setCorsHeaders);
1972
+ } catch (err) {
1973
+ routeError(req, res, err, pathname, { service: "batches" });
1974
+ }
1975
+ return;
1976
+ }
1977
+ const filesContentMatch = pathname.match(require_route_registry.FILES_CONTENT_RE);
1978
+ if (filesContentMatch && req.method === "GET") {
1979
+ try {
1980
+ await require_files.handleFilesContent(req, res, filesContentMatch[1], journal, defaults, setCorsHeaders);
1981
+ } catch (err) {
1982
+ routeError(req, res, err, pathname, { service: "files" });
1983
+ }
1984
+ return;
1985
+ }
1986
+ const filesIdMatch = pathname.match(require_route_registry.FILES_ID_RE);
1987
+ if (filesIdMatch && req.method === "GET") {
1988
+ try {
1989
+ await require_files.handleFilesRetrieve(req, res, filesIdMatch[1], journal, defaults, setCorsHeaders);
1990
+ } catch (err) {
1991
+ routeError(req, res, err, pathname, { service: "files" });
1992
+ }
1993
+ return;
1994
+ }
1995
+ if (filesIdMatch && req.method === "DELETE") {
1996
+ try {
1997
+ await require_files.handleFilesDelete(req, res, filesIdMatch[1], journal, defaults, setCorsHeaders);
1998
+ } catch (err) {
1999
+ routeError(req, res, err, pathname, { service: "files" });
2000
+ }
2001
+ return;
2002
+ }
2003
+ if (pathname === require_route_registry.FILES_PATH && req.method === "GET") {
2004
+ try {
2005
+ await require_files.handleFilesList(req, res, journal, defaults, setCorsHeaders);
2006
+ } catch (err) {
2007
+ routeError(req, res, err, pathname, { service: "files" });
2008
+ }
2009
+ return;
2010
+ }
2011
+ if (pathname === require_route_registry.FILES_PATH && req.method === "POST") {
2012
+ try {
2013
+ await require_files.handleFilesCreate(req, res, (await require_helpers.readBodyBufferBounded(req, require_files.FILES_BODY_MAX_BYTES, require_files.FILES_BODY_DRAIN_MAX_BYTES)).buffer ?? require_files.FILES_BODY_OVERSIZED, journal, defaults, setCorsHeaders);
2014
+ } catch (err) {
2015
+ routeError(req, res, err, pathname, { service: "files" });
2016
+ }
2017
+ return;
2018
+ }
2019
+ const ftCancelMatch = pathname.match(require_route_registry.FINE_TUNING_CANCEL_RE);
2020
+ if (ftCancelMatch && req.method === "POST") {
2021
+ try {
2022
+ await require_helpers.readBody(req);
2023
+ await require_fine_tuning.handleFineTuningCancel(req, res, ftCancelMatch[1], journal, defaults, setCorsHeaders);
2024
+ } catch (err) {
2025
+ routeError(req, res, err, pathname, { service: "fine-tuning" });
2026
+ }
2027
+ return;
2028
+ }
2029
+ const ftEventsMatch = pathname.match(require_route_registry.FINE_TUNING_EVENTS_RE);
2030
+ if (ftEventsMatch && req.method === "GET") {
2031
+ try {
2032
+ await require_fine_tuning.handleFineTuningEvents(req, res, ftEventsMatch[1], journal, defaults, setCorsHeaders);
2033
+ } catch (err) {
2034
+ routeError(req, res, err, pathname, { service: "fine-tuning" });
2035
+ }
2036
+ return;
2037
+ }
2038
+ const ftIdMatch = pathname.match(require_route_registry.FINE_TUNING_ID_RE);
2039
+ if (ftIdMatch && req.method === "GET") {
2040
+ try {
2041
+ await require_fine_tuning.handleFineTuningRetrieve(req, res, ftIdMatch[1], journal, defaults, setCorsHeaders);
2042
+ } catch (err) {
2043
+ routeError(req, res, err, pathname, { service: "fine-tuning" });
2044
+ }
2045
+ return;
2046
+ }
2047
+ if (pathname === require_route_registry.FINE_TUNING_JOBS_PATH && req.method === "GET") {
2048
+ try {
2049
+ await require_fine_tuning.handleFineTuningList(req, res, journal, defaults, setCorsHeaders);
2050
+ } catch (err) {
2051
+ routeError(req, res, err, pathname, { service: "fine-tuning" });
2052
+ }
2053
+ return;
2054
+ }
2055
+ if (pathname === require_route_registry.FINE_TUNING_JOBS_PATH && req.method === "POST") {
2056
+ try {
2057
+ await require_fine_tuning.handleFineTuningCreate(req, res, await require_helpers.readBody(req), journal, defaults, setCorsHeaders);
2058
+ } catch (err) {
2059
+ routeError(req, res, err, pathname, { service: "fine-tuning" });
2060
+ }
2061
+ return;
2062
+ }
2063
+ const vsSearchMatch = pathname.match(require_route_registry.VECTOR_STORES_SEARCH_RE);
2064
+ if (vsSearchMatch && req.method === "POST") {
2065
+ try {
2066
+ const raw = await require_helpers.readBody(req);
2067
+ await require_vector_stores.handleVectorStoresSearch(req, res, vsSearchMatch[1], raw, journal, defaults, setCorsHeaders);
2068
+ } catch (err) {
2069
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2070
+ }
2071
+ return;
2072
+ }
2073
+ const vsBatchFilesMatch = pathname.match(require_route_registry.VECTOR_STORES_BATCH_FILES_RE);
2074
+ if (vsBatchFilesMatch && req.method === "GET") {
2075
+ try {
2076
+ await require_vector_stores.handleVectorFileBatchesFiles(req, res, vsBatchFilesMatch[1], vsBatchFilesMatch[2], journal, defaults, setCorsHeaders);
2077
+ } catch (err) {
2078
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2079
+ }
2080
+ return;
2081
+ }
2082
+ const vsBatchCancelMatch = pathname.match(require_route_registry.VECTOR_STORES_BATCH_CANCEL_RE);
2083
+ if (vsBatchCancelMatch && req.method === "POST") {
2084
+ try {
2085
+ await require_helpers.readBody(req);
2086
+ await require_vector_stores.handleVectorFileBatchesCancel(req, res, vsBatchCancelMatch[1], vsBatchCancelMatch[2], journal, defaults, setCorsHeaders);
2087
+ } catch (err) {
2088
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2089
+ }
2090
+ return;
2091
+ }
2092
+ const vsBatchMatch = pathname.match(require_route_registry.VECTOR_STORES_BATCH_RE);
2093
+ if (vsBatchMatch && req.method === "GET") {
2094
+ try {
2095
+ await require_vector_stores.handleVectorFileBatchesRetrieve(req, res, vsBatchMatch[1], vsBatchMatch[2], journal, defaults, setCorsHeaders);
2096
+ } catch (err) {
2097
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2098
+ }
2099
+ return;
2100
+ }
2101
+ const vsFileMatch = pathname.match(require_route_registry.VECTOR_STORES_FILE_RE);
2102
+ if (vsFileMatch && req.method === "GET") {
2103
+ try {
2104
+ await require_vector_stores.handleVectorStoreFilesRetrieve(req, res, vsFileMatch[1], vsFileMatch[2], journal, defaults, setCorsHeaders);
2105
+ } catch (err) {
2106
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2107
+ }
2108
+ return;
2109
+ }
2110
+ if (vsFileMatch && req.method === "DELETE") {
2111
+ try {
2112
+ await require_vector_stores.handleVectorStoreFilesDelete(req, res, vsFileMatch[1], vsFileMatch[2], journal, defaults, setCorsHeaders);
2113
+ } catch (err) {
2114
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2115
+ }
2116
+ return;
2117
+ }
2118
+ const vsIdMatch = pathname.match(require_route_registry.VECTOR_STORES_ID_RE);
2119
+ if (vsIdMatch && req.method === "GET") {
2120
+ try {
2121
+ await require_vector_stores.handleVectorStoresRetrieve(req, res, vsIdMatch[1], journal, defaults, setCorsHeaders);
2122
+ } catch (err) {
2123
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2124
+ }
2125
+ return;
2126
+ }
2127
+ if (vsIdMatch && req.method === "POST") {
2128
+ try {
2129
+ const raw = await require_helpers.readBody(req);
2130
+ await require_vector_stores.handleVectorStoresModify(req, res, vsIdMatch[1], raw, journal, defaults, setCorsHeaders);
2131
+ } catch (err) {
2132
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2133
+ }
2134
+ return;
2135
+ }
2136
+ if (vsIdMatch && req.method === "DELETE") {
2137
+ try {
2138
+ await require_vector_stores.handleVectorStoresDelete(req, res, vsIdMatch[1], journal, defaults, setCorsHeaders);
2139
+ } catch (err) {
2140
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2141
+ }
2142
+ return;
2143
+ }
2144
+ if (require_route_registry.VECTOR_STORES_FILES_RE.test(pathname) && req.method === "POST") {
2145
+ try {
2146
+ const raw = await require_helpers.readBody(req);
2147
+ const storeId = pathname.split("/")[3];
2148
+ await require_vector_stores.handleVectorStoreFilesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders);
2149
+ } catch (err) {
2150
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2151
+ }
2152
+ return;
2153
+ }
2154
+ if (require_route_registry.VECTOR_STORES_FILES_RE.test(pathname) && req.method === "GET") {
2155
+ try {
2156
+ const storeId = pathname.split("/")[3];
2157
+ await require_vector_stores.handleVectorStoreFilesList(req, res, storeId, journal, defaults, setCorsHeaders);
2158
+ } catch (err) {
2159
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2160
+ }
2161
+ return;
2162
+ }
2163
+ if (require_route_registry.VECTOR_STORES_FILE_BATCHES_RE.test(pathname) && req.method === "POST") {
2164
+ try {
2165
+ const raw = await require_helpers.readBody(req);
2166
+ const storeId = pathname.split("/")[3];
2167
+ await require_vector_stores.handleVectorFileBatchesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders);
2168
+ } catch (err) {
2169
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2170
+ }
2171
+ return;
2172
+ }
2173
+ if (pathname === require_route_registry.VECTOR_STORES_PATH && req.method === "GET") {
2174
+ try {
2175
+ await require_vector_stores.handleVectorStoresList(req, res, journal, defaults, setCorsHeaders);
2176
+ } catch (err) {
2177
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2178
+ }
2179
+ return;
2180
+ }
2181
+ if (pathname === require_route_registry.VECTOR_STORES_PATH && req.method === "POST") {
2182
+ try {
2183
+ await require_vector_stores.handleVectorStoresCreate(req, res, await require_helpers.readBody(req), journal, defaults, setCorsHeaders);
2184
+ } catch (err) {
2185
+ routeError(req, res, err, pathname, { service: "vector-stores" });
2186
+ }
2187
+ return;
2188
+ }
2189
+ if (pathname === require_route_registry.REQUESTS_PATH) {
1560
2190
  setCorsHeaders(res);
1561
2191
  if (req.method === "GET") {
1562
2192
  const limitParam = parsedUrl.searchParams.get("limit");
1563
2193
  let opts;
1564
2194
  if (limitParam) {
1565
- const limit = parseInt(limitParam, 10);
1566
- if (Number.isNaN(limit) || limit <= 0) {
2195
+ const limit = Number(limitParam);
2196
+ if (!/^\+?\d+$/.test(limitParam.trim()) || !Number.isSafeInteger(limit) || limit <= 0) {
1567
2197
  require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
1568
2198
  message: `Invalid limit parameter: "${limitParam}"`,
1569
2199
  type: "invalid_request_error"
@@ -1586,74 +2216,39 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1586
2216
  handleNotFound(res, "Not found");
1587
2217
  return;
1588
2218
  }
1589
- if (pathname === RESPONSES_PATH && req.method === "POST") {
2219
+ if (pathname === require_route_registry.RESPONSES_PATH && req.method === "POST") {
1590
2220
  try {
1591
2221
  await require_responses.handleResponses(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1592
2222
  } catch (err) {
1593
- const msg = err instanceof Error ? err.message : "Internal error";
1594
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1595
- message: msg,
1596
- type: "server_error"
1597
- } }));
1598
- else if (!res.writableEnded) try {
1599
- res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
1600
- res.end();
1601
- } catch (writeErr) {
1602
- logger.debug("Failed to write error recovery response:", writeErr);
1603
- }
2223
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
1604
2224
  }
1605
2225
  return;
1606
2226
  }
1607
- if (pathname === MESSAGES_PATH && req.method === "POST") {
2227
+ if (pathname === require_route_registry.MESSAGES_PATH && req.method === "POST") {
1608
2228
  try {
1609
2229
  await require_messages.handleMessages(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1610
2230
  } catch (err) {
1611
- const msg = err instanceof Error ? err.message : "Internal error";
1612
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1613
- message: msg,
1614
- type: "server_error"
1615
- } }));
1616
- else if (!res.writableEnded) try {
1617
- res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
1618
- res.end();
1619
- } catch (writeErr) {
1620
- logger.debug("Failed to write error recovery response:", writeErr);
1621
- }
2231
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
1622
2232
  }
1623
2233
  return;
1624
2234
  }
1625
- if (pathname === COHERE_CHAT_PATH && req.method === "POST") {
2235
+ if (pathname === require_route_registry.COHERE_CHAT_PATH && req.method === "POST") {
1626
2236
  try {
1627
2237
  await require_cohere.handleCohere(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1628
2238
  } catch (err) {
1629
- const msg = err instanceof Error ? err.message : "Internal error";
1630
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1631
- message: msg,
1632
- type: "server_error"
1633
- } }));
1634
- else if (!res.writableEnded) try {
1635
- res.write(`event: error\ndata: ${JSON.stringify({ error: { message: msg } })}\n\n`);
1636
- res.end();
1637
- } catch (writeErr) {
1638
- logger.debug("Failed to write error recovery response:", writeErr);
1639
- }
2239
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
1640
2240
  }
1641
2241
  return;
1642
2242
  }
1643
- if (pathname === COHERE_EMBED_PATH && req.method === "POST") {
2243
+ if (pathname === require_route_registry.COHERE_EMBED_PATH && req.method === "POST") {
1644
2244
  try {
1645
2245
  await require_cohere.handleCohereEmbed(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1646
2246
  } catch (err) {
1647
- const msg = err instanceof Error ? err.message : "Internal error";
1648
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1649
- message: msg,
1650
- type: "server_error"
1651
- } }));
1652
- else if (!res.writableEnded) res.destroy();
2247
+ routeError(req, res, err, pathname);
1653
2248
  }
1654
2249
  return;
1655
2250
  }
1656
- if (pathname === EMBEDDINGS_PATH && req.method === "POST") {
2251
+ if (pathname === require_route_registry.EMBEDDINGS_PATH && req.method === "POST") {
1657
2252
  try {
1658
2253
  const deploymentId = azureDeploymentId;
1659
2254
  const embeddingsProvider = azureDeploymentId ? "azure" : "openai";
@@ -1667,551 +2262,379 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1667
2262
  } catch (err) {
1668
2263
  if (!(err instanceof SyntaxError)) defaults.logger.error(`Unexpected error in Azure model injection: ${err instanceof Error ? err.message : String(err)}`);
1669
2264
  }
1670
- await require_embeddings.handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider);
2265
+ await require_embeddings.handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider, originalPathname === require_route_registry.EMBEDDINGS_PATH);
1671
2266
  } catch (err) {
1672
- const msg = err instanceof Error ? err.message : "Internal error";
1673
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1674
- message: msg,
1675
- type: "server_error"
1676
- } }));
1677
- else if (!res.writableEnded) res.destroy();
2267
+ routeError(req, res, err, pathname);
1678
2268
  }
1679
2269
  return;
1680
2270
  }
1681
- if (pathname === IMAGES_PATH && req.method === "POST") {
2271
+ if (pathname === require_route_registry.IMAGES_PATH && req.method === "POST") {
1682
2272
  try {
1683
2273
  await require_images.handleImages(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, "openai", void 0, isBytePlusArk ? "byteplus" : void 0);
1684
2274
  } catch (err) {
1685
- const msg = err instanceof Error ? err.message : "Internal error";
1686
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1687
- message: msg,
1688
- type: "server_error"
1689
- } }));
1690
- else if (!res.writableEnded) res.destroy();
2275
+ routeError(req, res, err, pathname);
1691
2276
  }
1692
2277
  return;
1693
2278
  }
1694
- if (pathname === IMAGES_EDIT_PATH && req.method === "POST") {
2279
+ if (pathname === require_route_registry.IMAGES_EDIT_PATH && req.method === "POST") {
1695
2280
  try {
1696
2281
  await require_images.handleImageEdit(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1697
2282
  } catch (err) {
1698
- const msg = err instanceof Error ? err.message : "Internal error";
1699
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1700
- message: msg,
1701
- type: "server_error"
1702
- } }));
1703
- else if (!res.writableEnded) res.destroy();
2283
+ routeError(req, res, err, pathname);
1704
2284
  }
1705
2285
  return;
1706
2286
  }
1707
- if (pathname === IMAGES_VARIATIONS_PATH && req.method === "POST") {
2287
+ if (pathname === require_route_registry.IMAGES_VARIATIONS_PATH && req.method === "POST") {
1708
2288
  try {
1709
2289
  await require_images.handleImageVariations(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1710
2290
  } catch (err) {
1711
- const msg = err instanceof Error ? err.message : "Internal error";
1712
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1713
- message: msg,
1714
- type: "server_error"
1715
- } }));
1716
- else if (!res.writableEnded) res.destroy();
2291
+ routeError(req, res, err, pathname);
1717
2292
  }
1718
2293
  return;
1719
2294
  }
1720
- if (pathname === SPEECH_PATH && req.method === "POST") {
2295
+ if (pathname === require_route_registry.SPEECH_PATH && req.method === "POST") {
1721
2296
  try {
1722
2297
  await require_speech.handleSpeech(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1723
2298
  } catch (err) {
1724
- const msg = err instanceof Error ? err.message : "Internal error";
1725
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1726
- message: msg,
1727
- type: "server_error"
1728
- } }));
1729
- else if (!res.writableEnded) res.destroy();
2299
+ routeError(req, res, err, pathname);
1730
2300
  }
1731
2301
  return;
1732
2302
  }
1733
- if (pathname === TRANSCRIPTIONS_PATH && req.method === "POST") {
2303
+ if (pathname === require_route_registry.TRANSCRIPTIONS_PATH && req.method === "POST") {
1734
2304
  try {
1735
2305
  await require_transcription.handleTranscription(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1736
2306
  } catch (err) {
1737
- const msg = err instanceof Error ? err.message : "Internal error";
1738
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1739
- message: msg,
1740
- type: "server_error"
1741
- } }));
1742
- else if (!res.writableEnded) res.destroy();
2307
+ routeError(req, res, err, pathname);
1743
2308
  }
1744
2309
  return;
1745
2310
  }
1746
- if (pathname === TRANSLATIONS_PATH && req.method === "POST") {
2311
+ if (pathname === require_route_registry.TRANSLATIONS_PATH && req.method === "POST") {
1747
2312
  try {
1748
2313
  await require_transcription.handleTranscription(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, "translation");
1749
2314
  } catch (err) {
1750
- const msg = err instanceof Error ? err.message : "Internal error";
1751
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1752
- message: msg,
1753
- type: "server_error"
1754
- } }));
1755
- else if (!res.writableEnded) res.destroy();
2315
+ routeError(req, res, err, pathname);
1756
2316
  }
1757
2317
  return;
1758
2318
  }
1759
- if (pathname === require_metrics.GROK_VIDEO_SUBMIT_PATH && req.method === "POST") {
2319
+ if (pathname === require_route_registry.GROK_VIDEO_SUBMIT_PATH && req.method === "POST") {
1760
2320
  setCorsHeaders(res);
1761
2321
  try {
1762
2322
  await require_grok_video.handleGrokVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, grokVideoJobs);
1763
2323
  } catch (err) {
1764
- const msg = err instanceof Error ? err.message : "Internal error";
1765
- defaults.logger.error(`grok-video submit: ${msg}`);
1766
- if (!res.headersSent) {
1767
- try {
1768
- journal.add({
1769
- method: req.method ?? "POST",
1770
- path: req.url ?? pathname,
1771
- headers: require_helpers.flattenHeaders(req.headers),
1772
- body: null,
1773
- response: {
1774
- status: 500,
1775
- fixture: null
1776
- }
1777
- });
1778
- } catch (jErr) {
1779
- defaults.logger.warn(`grok-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1780
- }
1781
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1782
- message: msg,
1783
- type: "server_error"
1784
- } }));
1785
- } else if (!res.writableEnded) res.destroy();
2324
+ routeError(req, res, err, pathname);
1786
2325
  }
1787
2326
  return;
1788
2327
  }
1789
- if (pathname === VIDEOS_PATH && req.method === "POST") {
2328
+ if (pathname === require_route_registry.VIDEOS_PATH && req.method === "POST") {
1790
2329
  try {
1791
2330
  await require_video.handleVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, videoStates);
1792
2331
  } catch (err) {
1793
- const msg = err instanceof Error ? err.message : "Internal error";
1794
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1795
- message: msg,
1796
- type: "server_error"
1797
- } }));
1798
- else if (!res.writableEnded) res.destroy();
2332
+ routeError(req, res, err, pathname);
1799
2333
  }
1800
2334
  return;
1801
2335
  }
1802
- const grokVideoStatusMatch = pathname.match(require_metrics.GROK_VIDEO_STATUS_RE);
2336
+ const grokVideoStatusMatch = pathname.match(require_route_registry.GROK_VIDEO_STATUS_RE);
1803
2337
  if (grokVideoStatusMatch && grokVideoStatusMatch[1] !== "generations" && req.method === "GET") {
1804
2338
  try {
1805
2339
  await require_grok_video.handleGrokVideoStatus(req, res, grokVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, grokVideoJobs, videoStates);
1806
2340
  } catch (err) {
1807
- const msg = err instanceof Error ? err.message : "Internal error";
1808
- defaults.logger.error(`grok-video status: ${msg}`);
1809
- if (!res.headersSent) {
1810
- try {
1811
- journal.add({
1812
- method: req.method ?? "GET",
1813
- path: req.url ?? pathname,
1814
- headers: require_helpers.flattenHeaders(req.headers),
1815
- body: null,
1816
- response: {
1817
- status: 500,
1818
- fixture: null
1819
- }
1820
- });
1821
- } catch (jErr) {
1822
- defaults.logger.warn(`grok-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1823
- }
1824
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1825
- message: msg,
1826
- type: "server_error"
1827
- } }));
1828
- } else if (!res.writableEnded) res.destroy();
2341
+ routeError(req, res, err, pathname);
1829
2342
  }
1830
2343
  return;
1831
2344
  }
1832
- const veoPredictMatch = pathname.match(require_metrics.VEO_PREDICT_LRO_RE);
2345
+ const veoPredictMatch = pathname.match(require_route_registry.VEO_PREDICT_LRO_RE);
1833
2346
  if (veoPredictMatch && req.method === "POST") {
1834
2347
  setCorsHeaders(res);
1835
2348
  try {
1836
2349
  await require_veo_video.handleVeoVideoCreate(req, res, await require_helpers.readBody(req), veoPredictMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
1837
2350
  } catch (err) {
1838
- const msg = err instanceof Error ? err.message : "Internal error";
1839
- defaults.logger.error(`veo-video submit: ${msg}`);
1840
- if (!res.headersSent) {
1841
- try {
1842
- journal.add({
1843
- method: req.method ?? "POST",
1844
- path: req.url ?? pathname,
1845
- headers: require_helpers.flattenHeaders(req.headers),
1846
- body: null,
1847
- response: {
1848
- status: 500,
1849
- fixture: null
1850
- }
1851
- });
1852
- } catch (jErr) {
1853
- defaults.logger.warn(`veo-video submit: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1854
- }
1855
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1856
- message: msg,
1857
- type: "server_error"
1858
- } }));
1859
- } else if (!res.writableEnded) res.destroy();
2351
+ routeError(req, res, err, pathname);
1860
2352
  }
1861
2353
  return;
1862
2354
  }
1863
- const veoOperationMatch = pathname.match(require_metrics.VEO_OPERATION_RE);
2355
+ const veoOperationMatch = pathname.match(require_route_registry.VEO_OPERATION_RE);
1864
2356
  if (veoOperationMatch && req.method === "GET") {
1865
2357
  try {
1866
2358
  await require_veo_video.handleVeoVideoStatus(req, res, veoOperationMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
1867
2359
  } catch (err) {
1868
- const msg = err instanceof Error ? err.message : "Internal error";
1869
- defaults.logger.error(`veo-video status: ${msg}`);
1870
- if (!res.headersSent) {
1871
- try {
1872
- journal.add({
1873
- method: req.method ?? "GET",
1874
- path: req.url ?? pathname,
1875
- headers: require_helpers.flattenHeaders(req.headers),
1876
- body: null,
1877
- response: {
1878
- status: 500,
1879
- fixture: null
1880
- }
1881
- });
1882
- } catch (jErr) {
1883
- defaults.logger.warn(`veo-video status: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1884
- }
1885
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1886
- message: msg,
1887
- type: "server_error"
1888
- } }));
1889
- } else if (!res.writableEnded) res.destroy();
2360
+ routeError(req, res, err, pathname);
1890
2361
  }
1891
2362
  return;
1892
2363
  }
1893
- const geminiPredictMatch = pathname.match(GEMINI_PREDICT_RE);
2364
+ const geminiPredictMatch = pathname.match(require_route_registry.GEMINI_PREDICT_RE);
1894
2365
  if (geminiPredictMatch && req.method === "POST") {
1895
2366
  const predictModel = geminiPredictMatch[1];
1896
2367
  try {
1897
2368
  await require_images.handleImages(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, "gemini", predictModel);
1898
2369
  } catch (err) {
1899
- const msg = err instanceof Error ? err.message : "Internal error";
1900
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1901
- message: msg,
1902
- type: "server_error"
1903
- } }));
1904
- else if (!res.writableEnded) res.destroy();
2370
+ routeError(req, res, err, pathname);
1905
2371
  }
1906
2372
  return;
1907
2373
  }
1908
- if (pathname === GEMINI_INTERACTIONS_PATH && req.method === "POST") {
2374
+ if (pathname === require_route_registry.GEMINI_INTERACTIONS_PATH && req.method === "POST") {
1909
2375
  try {
1910
2376
  await require_gemini_interactions.handleGeminiInteractions(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1911
2377
  } catch (err) {
1912
- const msg = err instanceof Error ? err.message : "Internal error";
1913
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1914
- message: msg,
1915
- type: "server_error"
1916
- } }));
1917
- else if (!res.writableEnded) try {
1918
- res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
1919
- res.end();
1920
- } catch (writeErr) {
1921
- logger.debug("Failed to write error recovery response:", writeErr);
1922
- }
2378
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
1923
2379
  }
1924
2380
  return;
1925
2381
  }
1926
- const geminiEmbedMatch = pathname.match(GEMINI_EMBED_RE);
2382
+ const geminiEmbedMatch = pathname.match(require_route_registry.GEMINI_EMBED_RE);
1927
2383
  if (geminiEmbedMatch && req.method === "POST") {
1928
2384
  const embedModel = geminiEmbedMatch[1];
1929
2385
  try {
1930
2386
  await require_gemini_embeddings.handleGeminiEmbedContent(req, res, await require_helpers.readBody(req), embedModel, fixtures, journal, defaults, setCorsHeaders);
1931
2387
  } catch (err) {
1932
- const msg = err instanceof Error ? err.message : "Internal error";
1933
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1934
- message: msg,
1935
- type: "server_error"
1936
- } }));
1937
- else if (!res.writableEnded) res.destroy();
2388
+ routeError(req, res, err, pathname);
1938
2389
  }
1939
2390
  return;
1940
2391
  }
1941
- const geminiMatch = pathname.match(GEMINI_PATH_RE);
2392
+ const geminiMatch = pathname.match(require_route_registry.GEMINI_PATH_RE);
1942
2393
  if (geminiMatch && req.method === "POST") {
1943
2394
  const geminiModel = geminiMatch[1];
1944
2395
  const streaming = geminiMatch[2] === "streamGenerateContent";
1945
2396
  try {
1946
2397
  await require_gemini.handleGemini(req, res, await require_helpers.readBody(req), geminiModel, streaming, fixtures, journal, defaults, setCorsHeaders);
1947
2398
  } catch (err) {
1948
- const msg = err instanceof Error ? err.message : "Internal error";
1949
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1950
- message: msg,
1951
- type: "server_error"
1952
- } }));
1953
- else if (!res.writableEnded) try {
1954
- res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
1955
- res.end();
1956
- } catch (writeErr) {
1957
- logger.debug("Failed to write error recovery response:", writeErr);
1958
- }
2399
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
1959
2400
  }
1960
2401
  return;
1961
2402
  }
1962
- const vertexMatch = pathname.match(VERTEX_AI_RE);
2403
+ const vertexMatch = pathname.match(require_route_registry.VERTEX_AI_RE);
1963
2404
  if (vertexMatch && req.method === "POST") {
1964
2405
  const vertexModel = vertexMatch[1];
1965
2406
  const streaming = vertexMatch[2] === "streamGenerateContent";
1966
2407
  try {
1967
2408
  await require_gemini.handleGemini(req, res, await require_helpers.readBody(req), vertexModel, streaming, fixtures, journal, defaults, setCorsHeaders, "vertexai");
1968
2409
  } catch (err) {
1969
- const msg = err instanceof Error ? err.message : "Internal error";
1970
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1971
- message: msg,
1972
- type: "server_error"
1973
- } }));
1974
- else if (!res.writableEnded) try {
1975
- res.write(`data: ${JSON.stringify({ error: { message: msg } })}\n\n`);
1976
- res.end();
1977
- } catch (writeErr) {
1978
- logger.debug("Failed to write error recovery response:", writeErr);
1979
- }
2410
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
1980
2411
  }
1981
2412
  return;
1982
2413
  }
1983
- const bedrockMatch = pathname.match(BEDROCK_INVOKE_RE);
2414
+ const bedrockMatch = pathname.match(require_route_registry.BEDROCK_INVOKE_RE);
1984
2415
  if (bedrockMatch && req.method === "POST") {
1985
2416
  const bedrockModelId = bedrockMatch[1];
1986
2417
  try {
1987
2418
  await require_bedrock.handleBedrock(req, res, await require_helpers.readBody(req), bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
1988
2419
  } catch (err) {
1989
- const msg = err instanceof Error ? err.message : "Internal error";
1990
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1991
- message: msg,
1992
- type: "server_error"
1993
- } }));
1994
- else if (!res.writableEnded) res.destroy();
2420
+ routeError(req, res, err, pathname);
1995
2421
  }
1996
2422
  return;
1997
2423
  }
1998
- const bedrockStreamMatch = pathname.match(BEDROCK_STREAM_RE);
2424
+ const bedrockStreamMatch = pathname.match(require_route_registry.BEDROCK_STREAM_RE);
1999
2425
  if (bedrockStreamMatch && req.method === "POST") {
2000
2426
  const bedrockModelId = bedrockStreamMatch[1];
2001
2427
  try {
2002
2428
  await require_bedrock.handleBedrockStream(req, res, await require_helpers.readBody(req), bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
2003
2429
  } catch (err) {
2004
- const msg = err instanceof Error ? err.message : "Internal error";
2005
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2006
- message: msg,
2007
- type: "server_error"
2008
- } }));
2009
- else if (!res.writableEnded) res.destroy();
2430
+ routeError(req, res, err, pathname);
2010
2431
  }
2011
2432
  return;
2012
2433
  }
2013
- const converseMatch = pathname.match(BEDROCK_CONVERSE_RE);
2434
+ const converseMatch = pathname.match(require_route_registry.BEDROCK_CONVERSE_RE);
2014
2435
  if (converseMatch && req.method === "POST") {
2015
2436
  const converseModelId = converseMatch[1];
2016
2437
  try {
2017
2438
  await require_bedrock_converse.handleConverse(req, res, await require_helpers.readBody(req), converseModelId, fixtures, journal, defaults, setCorsHeaders);
2018
2439
  } catch (err) {
2019
- const msg = err instanceof Error ? err.message : "Internal error";
2020
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2021
- message: msg,
2022
- type: "server_error"
2023
- } }));
2024
- else if (!res.writableEnded) res.destroy();
2440
+ routeError(req, res, err, pathname);
2025
2441
  }
2026
2442
  return;
2027
2443
  }
2028
- const converseStreamMatch = pathname.match(BEDROCK_CONVERSE_STREAM_RE);
2444
+ const converseStreamMatch = pathname.match(require_route_registry.BEDROCK_CONVERSE_STREAM_RE);
2029
2445
  if (converseStreamMatch && req.method === "POST") {
2030
2446
  const converseStreamModelId = converseStreamMatch[1];
2031
2447
  try {
2032
2448
  await require_bedrock_converse.handleConverseStream(req, res, await require_helpers.readBody(req), converseStreamModelId, fixtures, journal, defaults, setCorsHeaders);
2033
2449
  } catch (err) {
2034
- const msg = err instanceof Error ? err.message : "Internal error";
2035
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2036
- message: msg,
2037
- type: "server_error"
2038
- } }));
2039
- else if (!res.writableEnded) res.destroy();
2450
+ routeError(req, res, err, pathname);
2040
2451
  }
2041
2452
  return;
2042
2453
  }
2043
- if (pathname === SEARCH_PATH && req.method === "POST") {
2454
+ if (pathname === require_route_registry.SEARCH_PATH && req.method === "POST") {
2044
2455
  try {
2045
2456
  await require_search.handleSearch(req, res, await require_helpers.readBody(req), serviceFixtures?.search ?? [], journal, defaults, setCorsHeaders);
2046
2457
  } catch (err) {
2047
- const msg = err instanceof Error ? err.message : "Internal error";
2048
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2049
- message: msg,
2050
- type: "server_error"
2051
- } }));
2052
- else if (!res.writableEnded) res.destroy();
2458
+ routeError(req, res, err, pathname, { service: "search" });
2053
2459
  }
2054
2460
  return;
2055
2461
  }
2056
- if (pathname === RERANK_PATH && req.method === "POST") {
2462
+ if (pathname === require_route_registry.RERANK_PATH && req.method === "POST") {
2057
2463
  try {
2058
2464
  await require_rerank.handleRerank(req, res, await require_helpers.readBody(req), serviceFixtures?.rerank ?? [], journal, defaults, setCorsHeaders);
2059
2465
  } catch (err) {
2060
- const msg = err instanceof Error ? err.message : "Internal error";
2061
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2062
- message: msg,
2063
- type: "server_error"
2064
- } }));
2065
- else if (!res.writableEnded) res.destroy();
2466
+ routeError(req, res, err, pathname, { service: "rerank" });
2066
2467
  }
2067
2468
  return;
2068
2469
  }
2069
- if (pathname === MODERATIONS_PATH && req.method === "POST") {
2470
+ if (pathname === require_route_registry.MODERATIONS_PATH && req.method === "POST") {
2070
2471
  try {
2071
2472
  await require_moderation.handleModeration(req, res, await require_helpers.readBody(req), serviceFixtures?.moderation ?? [], journal, defaults, setCorsHeaders);
2072
2473
  } catch (err) {
2073
- const msg = err instanceof Error ? err.message : "Internal error";
2074
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2075
- message: msg,
2076
- type: "server_error"
2077
- } }));
2078
- else if (!res.writableEnded) res.destroy();
2474
+ routeError(req, res, err, pathname, { service: "moderation" });
2079
2475
  }
2080
2476
  return;
2081
2477
  }
2082
- if (pathname === ELEVENLABS_SOUND_GENERATION_PATH && req.method === "POST") {
2478
+ if (pathname === require_route_registry.ELEVENLABS_SOUND_GENERATION_PATH && req.method === "POST") {
2083
2479
  setCorsHeaders(res);
2084
2480
  try {
2085
2481
  await require_elevenlabs_audio.handleElevenLabsAudio(req, res, await require_helpers.readBody(req), fixtures, defaults, journal, "sound-generation");
2086
2482
  } catch (err) {
2087
- const msg = err instanceof Error ? err.message : "Internal error";
2088
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2089
- message: msg,
2090
- type: "server_error"
2483
+ routeError(req, res, err, pathname);
2484
+ }
2485
+ return;
2486
+ }
2487
+ if (pathname === require_route_registry.ELEVENLABS_VOICE_DESIGN_PATH && req.method === "POST") {
2488
+ setCorsHeaders(res);
2489
+ try {
2490
+ await require_elevenlabs_voice.handleElevenLabsVoiceDesign(req, res, await require_helpers.readBody(req), fixtures, defaults, journal);
2491
+ } catch (err) {
2492
+ routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2493
+ }
2494
+ return;
2495
+ }
2496
+ if (pathname === require_route_registry.ELEVENLABS_VOICE_CREATE_PATH && req.method === "POST") {
2497
+ setCorsHeaders(res);
2498
+ try {
2499
+ await require_elevenlabs_voice.handleElevenLabsVoiceCreate(req, res, await require_helpers.readBody(req), fixtures, defaults, journal);
2500
+ } catch (err) {
2501
+ routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2502
+ }
2503
+ return;
2504
+ }
2505
+ const elevenLabsVoiceMatch = pathname.match(require_route_registry.ELEVENLABS_VOICE_RE);
2506
+ if (elevenLabsVoiceMatch && (req.method === "GET" || req.method === "DELETE")) {
2507
+ setCorsHeaders(res);
2508
+ let voiceId;
2509
+ try {
2510
+ voiceId = decodeURIComponent(elevenLabsVoiceMatch[1]);
2511
+ } catch {
2512
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
2513
+ message: `Invalid voice id '${elevenLabsVoiceMatch[1]}': malformed percent-encoding`,
2514
+ type: "invalid_request_error"
2091
2515
  } }));
2092
- else if (!res.writableEnded) res.destroy();
2516
+ journal.add({
2517
+ method: req.method ?? "GET",
2518
+ path: req.url ?? pathname,
2519
+ headers: require_helpers.flattenHeaders(req.headers),
2520
+ service: "elevenlabs-voice",
2521
+ body: null,
2522
+ response: {
2523
+ status: 400,
2524
+ fixture: null
2525
+ }
2526
+ });
2527
+ return;
2528
+ }
2529
+ try {
2530
+ if (req.method === "GET") await require_elevenlabs_voice.handleElevenLabsVoiceGet(req, res, voiceId, fixtures, defaults, journal);
2531
+ else await require_elevenlabs_voice.handleElevenLabsVoiceDelete(req, res, voiceId, fixtures, defaults, journal);
2532
+ } catch (err) {
2533
+ routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2093
2534
  }
2094
2535
  return;
2095
2536
  }
2096
- const elevenLabsTTSMatch = pathname.match(ELEVENLABS_TTS_RE);
2537
+ const elevenLabsTTSMatch = pathname.match(require_route_registry.ELEVENLABS_TTS_RE);
2097
2538
  if (elevenLabsTTSMatch && req.method === "POST") {
2098
2539
  setCorsHeaders(res);
2099
2540
  const voiceId = elevenLabsTTSMatch[1];
2100
2541
  try {
2101
2542
  await require_elevenlabs_audio.handleElevenLabsTTS(req, res, await require_helpers.readBody(req), fixtures, defaults, journal, voiceId);
2102
2543
  } catch (err) {
2103
- const msg = err instanceof Error ? err.message : "Internal error";
2104
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2105
- message: msg,
2106
- type: "server_error"
2107
- } }));
2108
- else if (!res.writableEnded) res.destroy();
2544
+ routeError(req, res, err, pathname);
2109
2545
  }
2110
2546
  return;
2111
2547
  }
2112
- const musicMatch = pathname.match(ELEVENLABS_MUSIC_RE);
2548
+ const musicMatch = pathname.match(require_route_registry.ELEVENLABS_MUSIC_RE);
2113
2549
  if (musicMatch && req.method === "POST") {
2114
2550
  setCorsHeaders(res);
2115
2551
  const musicSubType = musicMatch[1] ?? "music";
2116
2552
  try {
2117
2553
  await require_elevenlabs_audio.handleElevenLabsAudio(req, res, await require_helpers.readBody(req), fixtures, defaults, journal, musicSubType);
2118
2554
  } catch (err) {
2119
- const msg = err instanceof Error ? err.message : "Internal error";
2120
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2121
- message: msg,
2122
- type: "server_error"
2123
- } }));
2124
- else if (!res.writableEnded) res.destroy();
2555
+ routeError(req, res, err, pathname);
2125
2556
  }
2126
2557
  return;
2127
2558
  }
2128
2559
  let falBody;
2129
- if (FAL_PREFIX_RE.test(pathname) && req.headers["x-fal-target-host"]) {
2560
+ if (require_route_registry.FAL_ROUTE_RE.test(pathname) && req.headers["x-fal-target-host"]) {
2130
2561
  setCorsHeaders(res);
2131
2562
  try {
2132
2563
  falBody = req.method === "POST" || req.method === "PUT" ? await require_helpers.readBody(req) : "";
2133
2564
  const raw = falBody;
2134
- const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url);
2135
- if (chaosAction) {
2136
- require_chaos.applyChaosAction(chaosAction, res, null, journal, {
2137
- method: req.method ?? "GET",
2138
- path: pathname,
2139
- headers: require_helpers.flattenHeaders(req.headers),
2140
- body: {
2141
- model: "",
2142
- messages: []
2143
- }
2144
- }, "fixture", defaults.registry);
2145
- return;
2565
+ if (require_fal.falWillHandle(req, pathname)) {
2566
+ const chaosConfig = require_chaos.resolveChaosConfig(null, defaults.chaos, req.headers, defaults.logger, req.url);
2567
+ await require_chaos.awaitChaosLatency(null, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
2568
+ const goneAfterLatency = require_chaos.responseGoneReason(res);
2569
+ if (goneAfterLatency !== null) {
2570
+ defaults.logger.debug(`[chaos] ${req.method ?? "GET"} ${pathname}: ${require_chaos.describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
2571
+ return;
2572
+ }
2573
+ const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
2574
+ if (chaosAction) {
2575
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, {
2576
+ method: req.method ?? "GET",
2577
+ path: pathname,
2578
+ headers: require_helpers.flattenHeaders(req.headers),
2579
+ body: null
2580
+ }, "internal", defaults.registry, defaults.logger);
2581
+ return;
2582
+ }
2146
2583
  }
2147
2584
  if (await require_fal.handleFal(req, res, raw, pathname, fixtures, defaults, journal) === "handled") return;
2148
2585
  } catch (err) {
2149
- const msg = err instanceof Error ? err.message : "Internal error";
2150
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2151
- message: msg,
2152
- type: "server_error"
2153
- } }));
2154
- else if (!res.writableEnded) res.destroy();
2586
+ routeError(req, res, err, pathname);
2155
2587
  return;
2156
2588
  }
2157
2589
  }
2158
- if (pathname.match(FAL_QUEUE_SUBMIT_RE) && req.method === "POST") {
2590
+ if (pathname.match(require_route_registry.FAL_QUEUE_SUBMIT_RE) && req.method === "POST") {
2159
2591
  setCorsHeaders(res);
2160
2592
  try {
2161
2593
  await require_fal_audio.handleFalQueue(req, res, falBody ?? await require_helpers.readBody(req), pathname, fixtures, defaults, journal);
2162
2594
  } catch (err) {
2163
- const msg = err instanceof Error ? err.message : "Internal error";
2164
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2165
- message: msg,
2166
- type: "server_error"
2167
- } }));
2168
- else if (!res.writableEnded) res.destroy();
2595
+ routeError(req, res, err, pathname);
2169
2596
  }
2170
2597
  return;
2171
2598
  }
2172
- if (pathname.match(FAL_QUEUE_REQUESTS_RE) && (req.method === "GET" || req.method === "POST" || req.method === "PUT")) {
2599
+ if (pathname.match(require_route_registry.FAL_QUEUE_REQUESTS_RE) && (req.method === "GET" || req.method === "POST" || req.method === "PUT")) {
2173
2600
  setCorsHeaders(res);
2174
2601
  try {
2175
2602
  const raw = req.method === "POST" || req.method === "PUT" ? falBody ?? await require_helpers.readBody(req) : "{}";
2176
- const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url);
2603
+ const chaosConfig = require_chaos.resolveChaosConfig(null, defaults.chaos, req.headers, defaults.logger, req.url);
2604
+ await require_chaos.awaitChaosLatency(null, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
2605
+ const goneAfterLatency = require_chaos.responseGoneReason(res);
2606
+ if (goneAfterLatency !== null) {
2607
+ defaults.logger.debug(`[chaos] ${req.method ?? "GET"} ${pathname}: ${require_chaos.describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
2608
+ return;
2609
+ }
2610
+ const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
2177
2611
  if (chaosAction) {
2178
2612
  require_chaos.applyChaosAction(chaosAction, res, null, journal, {
2179
2613
  method: req.method ?? "GET",
2180
2614
  path: pathname,
2181
2615
  headers: require_helpers.flattenHeaders(req.headers),
2182
- body: {
2183
- model: "",
2184
- messages: []
2185
- }
2186
- }, "fixture", defaults.registry);
2616
+ body: null
2617
+ }, "internal", defaults.registry, defaults.logger);
2187
2618
  return;
2188
2619
  }
2189
2620
  await require_fal_audio.handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
2190
2621
  } catch (err) {
2191
- const msg = err instanceof Error ? err.message : "Internal error";
2192
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2193
- message: msg,
2194
- type: "server_error"
2195
- } }));
2196
- else if (!res.writableEnded) res.destroy();
2622
+ routeError(req, res, err, pathname);
2197
2623
  }
2198
2624
  return;
2199
2625
  }
2200
- if (pathname.match(FAL_RUN_RE) && req.method === "POST") {
2626
+ if (pathname.match(require_route_registry.FAL_RUN_RE) && req.method === "POST") {
2201
2627
  setCorsHeaders(res);
2202
2628
  try {
2203
2629
  await require_fal_audio.handleFalQueue(req, res, falBody ?? await require_helpers.readBody(req), pathname, fixtures, defaults, journal);
2204
2630
  } catch (err) {
2205
- const msg = err instanceof Error ? err.message : "Internal error";
2206
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
2207
- message: msg,
2208
- type: "server_error"
2209
- } }));
2210
- else if (!res.writableEnded) res.destroy();
2631
+ routeError(req, res, err, pathname);
2211
2632
  }
2212
2633
  return;
2213
2634
  }
2214
- if (pathname !== COMPLETIONS_PATH) {
2635
+ if (pathname !== require_route_registry.COMPLETIONS_PATH) {
2636
+ const catalogMatch = require_route_registry.matchRouteDefinition(req.method ?? "", pathname);
2637
+ if (catalogMatch && !(pathname === require_route_registry.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.`);
2215
2638
  handleNotFound(res, "Not found");
2216
2639
  return;
2217
2640
  }
@@ -2223,23 +2646,25 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2223
2646
  try {
2224
2647
  await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider, isOpenRouter);
2225
2648
  } catch (err) {
2226
- const msg = err instanceof Error ? err.message : "Internal error";
2227
- if (!res.headersSent) require_sse_writer.writeErrorResponse(res, 500, isOpenRouter ? require_openrouter_chat.serializeOpenRouterError(500, msg) : JSON.stringify({ error: {
2228
- message: msg,
2229
- type: "server_error"
2230
- } }));
2231
- else if (!res.writableEnded) try {
2232
- res.write(`data: ${isOpenRouter ? require_openrouter_chat.serializeOpenRouterError(500, msg) : JSON.stringify({ error: {
2233
- message: msg,
2234
- type: "server_error"
2235
- } })}\n\n`);
2236
- res.end();
2237
- } catch (writeErr) {
2238
- logger.debug("Failed to write error recovery response:", writeErr);
2239
- }
2649
+ routeError(req, res, err, pathname, {
2650
+ envelope: (m, status) => isOpenRouter ? require_openrouter_chat.serializeOpenRouterError(status, m) : JSON.stringify({ error: {
2651
+ message: m,
2652
+ type: errorTypeForStatus(status)
2653
+ } }),
2654
+ streamEvent: (m, status) => `data: ${isOpenRouter ? require_openrouter_chat.serializeOpenRouterError(status, m) : JSON.stringify({ error: {
2655
+ message: m,
2656
+ type: errorTypeForStatus(status)
2657
+ } })}\n\n`
2658
+ });
2240
2659
  }
2241
2660
  }
2242
2661
  const activeConnections = /* @__PURE__ */ new Set();
2662
+ const liveLimits = require_live_fixture.normalizeLiveOptions(options?.live);
2663
+ const liveSessions = /* @__PURE__ */ new Map();
2664
+ const closeLiveSessions = (testId) => {
2665
+ for (const session of [...liveSessions.values()]) if (testId === void 0 || session.testId === testId) session.dispose();
2666
+ };
2667
+ liveClosers.set(defaults, closeLiveSessions);
2243
2668
  server.on("upgrade", (req, socket, head) => {
2244
2669
  handleUpgradeRequest(req, socket, head).catch((err) => {
2245
2670
  const msg = err instanceof Error ? err.message : "Internal error";
@@ -2289,30 +2714,57 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2289
2714
  server.emit("connection", socket);
2290
2715
  return;
2291
2716
  }
2292
- const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
2717
+ let parsedUrl;
2718
+ try {
2719
+ parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
2720
+ } catch (err) {
2721
+ const msg = err instanceof Error ? err.message : "Internal error";
2722
+ defaults.logger.warn(`Unhandled upgrade error: ${msg}`);
2723
+ socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
2724
+ return;
2725
+ }
2293
2726
  let pathname = parsedUrl.pathname;
2294
2727
  if (!require_api_key_auth.validateRequestApiKey(req, resolvedAuth.policy).ok) {
2295
2728
  require_api_key_auth.writeApiKeyUpgradeRejection(socket);
2296
2729
  return;
2297
2730
  }
2298
- if (mounts) {
2299
- for (const { path: mountPath, handler } of mounts) if ((pathname === mountPath || pathname.startsWith(mountPath + "/")) && handler.handleUpgrade) {
2300
- const subPath = pathname.slice(mountPath.length) || "/";
2301
- if (await handler.handleUpgrade(socket, head, subPath)) return;
2302
- }
2731
+ for (const { path: mountPath, handler } of mountList) if ((pathname === mountPath || pathname.startsWith(mountPath + "/")) && handler.handleUpgrade) {
2732
+ const subPath = pathname.slice(mountPath.length) || "/";
2733
+ if (await handler.handleUpgrade(socket, head, subPath)) return;
2303
2734
  }
2304
- if (!pathname.match(AZURE_DEPLOYMENT_RE)) pathname = normalizeCompatPath(pathname, logger);
2305
- if (pathname !== RESPONSES_PATH && pathname !== REALTIME_PATH && pathname !== GEMINI_LIVE_PATH) {
2735
+ if (!pathname.match(require_route_registry.AZURE_DEPLOYMENT_RE)) pathname = normalizeCompatPath(pathname, logger);
2736
+ if (pathname !== require_route_registry.RESPONSES_PATH && pathname !== require_route_registry.REALTIME_PATH && pathname !== require_route_registry.GEMINI_LIVE_PATH && pathname !== require_route_registry.LIVE_PATH) {
2306
2737
  socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
2307
2738
  socket.destroy();
2308
2739
  return;
2309
2740
  }
2741
+ let liveId;
2742
+ if (pathname === require_route_registry.LIVE_PATH) {
2743
+ if (req.method !== "GET" || parsedUrl.searchParams.has("model")) {
2744
+ socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
2745
+ return;
2746
+ }
2747
+ if (liveSessions.size >= liveLimits.maxSessions) {
2748
+ socket.end("HTTP/1.1 429 Too Many Requests\r\nConnection: close\r\n\r\n");
2749
+ return;
2750
+ }
2751
+ liveId = Symbol("live-session");
2752
+ const reservedId = liveId;
2753
+ liveSessions.set(reservedId, {
2754
+ testId: require_helpers.getTestId(req),
2755
+ dispose: () => {
2756
+ socket.destroy();
2757
+ liveSessions.delete(reservedId);
2758
+ }
2759
+ });
2760
+ }
2310
2761
  if (head.length > 0) socket.unshift(head);
2311
2762
  let ws;
2312
2763
  try {
2313
- ws = require_ws_framing.upgradeToWebSocket(req, socket);
2764
+ ws = require_ws_framing.upgradeToWebSocket(req, socket, pathname === require_route_registry.LIVE_PATH ? liveLimits : void 0);
2314
2765
  } catch (err) {
2315
2766
  const msg = err instanceof Error ? err.message : "WebSocket upgrade failed";
2767
+ if (liveId !== void 0) liveSessions.delete(liveId);
2316
2768
  logger.error(`WebSocket upgrade error: ${msg}`);
2317
2769
  if (!socket.destroyed) socket.destroy();
2318
2770
  return;
@@ -2326,13 +2778,30 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2326
2778
  activeConnections.delete(ws);
2327
2779
  });
2328
2780
  const wsTestId = require_helpers.getTestId(req);
2329
- if (pathname === RESPONSES_PATH) require_ws_responses.handleWebSocketResponses(ws, fixtures, journal, {
2781
+ if (pathname === require_route_registry.LIVE_PATH && liveId !== void 0) {
2782
+ const sessionId = liveId;
2783
+ const dispose = require_ws_live.handleLiveSession(ws, fixtures, journal, {
2784
+ defaults,
2785
+ limits: liveLimits,
2786
+ localApiKeys: resolvedAuth.publicConfig?.apiKeys,
2787
+ testId: wsTestId,
2788
+ headers: req.headers,
2789
+ onDispose: () => {
2790
+ liveSessions.delete(sessionId);
2791
+ activeConnections.delete(ws);
2792
+ }
2793
+ });
2794
+ liveSessions.set(sessionId, {
2795
+ testId: wsTestId,
2796
+ dispose
2797
+ });
2798
+ } else if (pathname === require_route_registry.RESPONSES_PATH) require_ws_responses.handleWebSocketResponses(ws, fixtures, journal, {
2330
2799
  ...defaults,
2331
2800
  model: "gpt-4",
2332
2801
  testId: wsTestId,
2333
2802
  upgradeHeaders: req.headers
2334
2803
  });
2335
- else if (pathname === REALTIME_PATH) {
2804
+ else if (pathname === require_route_registry.REALTIME_PATH) {
2336
2805
  const transcriptionIntent = parsedUrl.searchParams.get("intent") === "transcription";
2337
2806
  const model = transcriptionIntent ? "gpt-transcribe" : parsedUrl.searchParams.get("model") ?? "gpt-realtime-2";
2338
2807
  require_ws_realtime.handleWebSocketRealtime(ws, fixtures, journal, {
@@ -2342,7 +2811,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2342
2811
  testId: wsTestId,
2343
2812
  upgradeHeaders: req.headers
2344
2813
  });
2345
- } else if (pathname === GEMINI_LIVE_PATH) require_ws_gemini_live.handleWebSocketGeminiLive(ws, fixtures, journal, {
2814
+ } else if (pathname === require_route_registry.GEMINI_LIVE_PATH) require_ws_gemini_live.handleWebSocketGeminiLive(ws, fixtures, journal, {
2346
2815
  ...defaults,
2347
2816
  model: "gemini-2.0-flash",
2348
2817
  testId: wsTestId,
@@ -2351,6 +2820,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2351
2820
  }
2352
2821
  const originalClose = server.close.bind(server);
2353
2822
  server.close = function(callback) {
2823
+ closeLiveSessions();
2354
2824
  for (const ws of activeConnections) ws.close(1001, "Server shutting down");
2355
2825
  activeConnections.clear();
2356
2826
  videoStates.clear();
@@ -2370,10 +2840,15 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2370
2840
  return;
2371
2841
  }
2372
2842
  const url = `http://${addr.address}:${addr.port}`;
2373
- if (mounts) {
2374
- for (const { path: mountPath, handler } of mounts) if (handler.setBaseUrl) handler.setBaseUrl(url + mountPath);
2843
+ try {
2844
+ commitHandOff();
2845
+ } catch (err) {
2846
+ server.close(() => reject(err));
2847
+ return;
2375
2848
  }
2849
+ for (const { path: mountPath, handler } of mountList) if (handler.setBaseUrl) handler.setBaseUrl(url + mountPath);
2376
2850
  resolve({
2851
+ closeLiveSessions,
2377
2852
  server,
2378
2853
  journal,
2379
2854
  url,