@copilotkit/aimock 1.42.1 → 1.43.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 (338) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +50 -0
  4. package/README.md +102 -16
  5. package/dist/agui-mock.cjs +1 -1
  6. package/dist/agui-mock.cjs.map +1 -1
  7. package/dist/agui-mock.d.cts.map +1 -1
  8. package/dist/agui-mock.d.ts.map +1 -1
  9. package/dist/agui-mock.js +1 -1
  10. package/dist/agui-mock.js.map +1 -1
  11. package/dist/agui-recorder.cjs +33 -11
  12. package/dist/agui-recorder.cjs.map +1 -1
  13. package/dist/agui-recorder.d.cts +10 -1
  14. package/dist/agui-recorder.d.cts.map +1 -1
  15. package/dist/agui-recorder.d.ts +10 -1
  16. package/dist/agui-recorder.d.ts.map +1 -1
  17. package/dist/agui-recorder.js +33 -11
  18. package/dist/agui-recorder.js.map +1 -1
  19. package/dist/agui-types.d.cts +1 -0
  20. package/dist/agui-types.d.cts.map +1 -1
  21. package/dist/agui-types.d.ts +1 -0
  22. package/dist/agui-types.d.ts.map +1 -1
  23. package/dist/aimock-cli.cjs +38 -10
  24. package/dist/aimock-cli.cjs.map +1 -1
  25. package/dist/aimock-cli.d.cts +5 -0
  26. package/dist/aimock-cli.d.cts.map +1 -1
  27. package/dist/aimock-cli.d.ts +5 -0
  28. package/dist/aimock-cli.d.ts.map +1 -1
  29. package/dist/aimock-cli.js +38 -10
  30. package/dist/aimock-cli.js.map +1 -1
  31. package/dist/batches.cjs +458 -0
  32. package/dist/batches.cjs.map +1 -0
  33. package/dist/batches.d.cts +57 -0
  34. package/dist/batches.d.cts.map +1 -0
  35. package/dist/batches.d.ts +57 -0
  36. package/dist/batches.d.ts.map +1 -0
  37. package/dist/batches.js +453 -0
  38. package/dist/batches.js.map +1 -0
  39. package/dist/bedrock-converse.cjs +12 -8
  40. package/dist/bedrock-converse.cjs.map +1 -1
  41. package/dist/bedrock-converse.d.cts.map +1 -1
  42. package/dist/bedrock-converse.d.ts.map +1 -1
  43. package/dist/bedrock-converse.js +14 -10
  44. package/dist/bedrock-converse.js.map +1 -1
  45. package/dist/bedrock.cjs +10 -8
  46. package/dist/bedrock.cjs.map +1 -1
  47. package/dist/bedrock.d.cts.map +1 -1
  48. package/dist/bedrock.d.ts.map +1 -1
  49. package/dist/bedrock.js +12 -10
  50. package/dist/bedrock.js.map +1 -1
  51. package/dist/byteplus-video.cjs +3 -3
  52. package/dist/byteplus-video.cjs.map +1 -1
  53. package/dist/byteplus-video.js +4 -4
  54. package/dist/byteplus-video.js.map +1 -1
  55. package/dist/chaos.cjs +622 -72
  56. package/dist/chaos.cjs.map +1 -1
  57. package/dist/chaos.d.cts +164 -7
  58. package/dist/chaos.d.cts.map +1 -1
  59. package/dist/chaos.d.ts +164 -7
  60. package/dist/chaos.d.ts.map +1 -1
  61. package/dist/chaos.js +613 -74
  62. package/dist/chaos.js.map +1 -1
  63. package/dist/cli.cjs +42 -23
  64. package/dist/cli.cjs.map +1 -1
  65. package/dist/cli.js +42 -23
  66. package/dist/cli.js.map +1 -1
  67. package/dist/cohere.cjs +10 -8
  68. package/dist/cohere.cjs.map +1 -1
  69. package/dist/cohere.d.cts.map +1 -1
  70. package/dist/cohere.d.ts.map +1 -1
  71. package/dist/cohere.js +12 -10
  72. package/dist/cohere.js.map +1 -1
  73. package/dist/config-loader.d.ts.map +1 -1
  74. package/dist/convert.cjs +1 -1
  75. package/dist/convert.cjs.map +1 -1
  76. package/dist/convert.js +1 -1
  77. package/dist/convert.js.map +1 -1
  78. package/dist/elevenlabs-audio.cjs +3 -3
  79. package/dist/elevenlabs-audio.cjs.map +1 -1
  80. package/dist/elevenlabs-audio.js +4 -4
  81. package/dist/elevenlabs-audio.js.map +1 -1
  82. package/dist/elevenlabs-voice.cjs +1053 -0
  83. package/dist/elevenlabs-voice.cjs.map +1 -0
  84. package/dist/elevenlabs-voice.d.cts +72 -0
  85. package/dist/elevenlabs-voice.d.cts.map +1 -0
  86. package/dist/elevenlabs-voice.d.ts +72 -0
  87. package/dist/elevenlabs-voice.d.ts.map +1 -0
  88. package/dist/elevenlabs-voice.js +1048 -0
  89. package/dist/elevenlabs-voice.js.map +1 -0
  90. package/dist/embeddings.cjs +8 -4
  91. package/dist/embeddings.cjs.map +1 -1
  92. package/dist/embeddings.d.cts.map +1 -1
  93. package/dist/embeddings.d.ts.map +1 -1
  94. package/dist/embeddings.js +9 -5
  95. package/dist/embeddings.js.map +1 -1
  96. package/dist/fal-audio.cjs +3 -3
  97. package/dist/fal-audio.cjs.map +1 -1
  98. package/dist/fal-audio.js +4 -4
  99. package/dist/fal-audio.js.map +1 -1
  100. package/dist/fal.cjs +28 -4
  101. package/dist/fal.cjs.map +1 -1
  102. package/dist/fal.d.cts +16 -1
  103. package/dist/fal.d.cts.map +1 -1
  104. package/dist/fal.d.ts +16 -1
  105. package/dist/fal.d.ts.map +1 -1
  106. package/dist/fal.js +28 -5
  107. package/dist/fal.js.map +1 -1
  108. package/dist/files.cjs +1071 -0
  109. package/dist/files.cjs.map +1 -0
  110. package/dist/files.d.cts +68 -0
  111. package/dist/files.d.cts.map +1 -0
  112. package/dist/files.d.ts +68 -0
  113. package/dist/files.d.ts.map +1 -0
  114. package/dist/files.js +1059 -0
  115. package/dist/files.js.map +1 -0
  116. package/dist/fine-tuning.cjs +1051 -0
  117. package/dist/fine-tuning.cjs.map +1 -0
  118. package/dist/fine-tuning.d.cts +96 -0
  119. package/dist/fine-tuning.d.cts.map +1 -0
  120. package/dist/fine-tuning.d.ts +96 -0
  121. package/dist/fine-tuning.d.ts.map +1 -0
  122. package/dist/fine-tuning.js +1044 -0
  123. package/dist/fine-tuning.js.map +1 -0
  124. package/dist/fixture-loader.cjs +0 -0
  125. package/dist/fixture-loader.cjs.map +1 -1
  126. package/dist/fixture-loader.d.cts +37 -5
  127. package/dist/fixture-loader.d.cts.map +1 -1
  128. package/dist/fixture-loader.d.ts +37 -5
  129. package/dist/fixture-loader.d.ts.map +1 -1
  130. package/dist/fixture-loader.js +0 -0
  131. package/dist/fixture-loader.js.map +1 -1
  132. package/dist/gemini-embeddings.cjs +2 -2
  133. package/dist/gemini-embeddings.cjs.map +1 -1
  134. package/dist/gemini-embeddings.js +3 -3
  135. package/dist/gemini-embeddings.js.map +1 -1
  136. package/dist/gemini-interactions.cjs +13 -7
  137. package/dist/gemini-interactions.cjs.map +1 -1
  138. package/dist/gemini-interactions.d.cts.map +1 -1
  139. package/dist/gemini-interactions.d.ts.map +1 -1
  140. package/dist/gemini-interactions.js +15 -9
  141. package/dist/gemini-interactions.js.map +1 -1
  142. package/dist/gemini.cjs +11 -7
  143. package/dist/gemini.cjs.map +1 -1
  144. package/dist/gemini.d.cts.map +1 -1
  145. package/dist/gemini.d.ts.map +1 -1
  146. package/dist/gemini.js +13 -9
  147. package/dist/gemini.js.map +1 -1
  148. package/dist/grok-video.cjs +14 -14
  149. package/dist/grok-video.cjs.map +1 -1
  150. package/dist/grok-video.js +15 -15
  151. package/dist/grok-video.js.map +1 -1
  152. package/dist/helpers.cjs +221 -5
  153. package/dist/helpers.cjs.map +1 -1
  154. package/dist/helpers.d.cts +38 -2
  155. package/dist/helpers.d.cts.map +1 -1
  156. package/dist/helpers.d.ts +38 -2
  157. package/dist/helpers.d.ts.map +1 -1
  158. package/dist/helpers.js +213 -6
  159. package/dist/helpers.js.map +1 -1
  160. package/dist/images.cjs +54 -123
  161. package/dist/images.cjs.map +1 -1
  162. package/dist/images.d.cts.map +1 -1
  163. package/dist/images.d.ts.map +1 -1
  164. package/dist/images.js +55 -124
  165. package/dist/images.js.map +1 -1
  166. package/dist/index.cjs +41 -8
  167. package/dist/index.d.cts +12 -7
  168. package/dist/index.d.ts +12 -7
  169. package/dist/index.js +14 -10
  170. package/dist/jest.cjs +14 -7
  171. package/dist/jest.cjs.map +1 -1
  172. package/dist/jest.d.cts +3 -2
  173. package/dist/jest.d.cts.map +1 -1
  174. package/dist/jest.d.ts +3 -2
  175. package/dist/jest.d.ts.map +1 -1
  176. package/dist/jest.js +14 -7
  177. package/dist/jest.js.map +1 -1
  178. package/dist/journal.cjs +41 -4
  179. package/dist/journal.cjs.map +1 -1
  180. package/dist/journal.d.cts +44 -3
  181. package/dist/journal.d.cts.map +1 -1
  182. package/dist/journal.d.ts +44 -3
  183. package/dist/journal.d.ts.map +1 -1
  184. package/dist/journal.js +41 -5
  185. package/dist/journal.js.map +1 -1
  186. package/dist/live-fixture.cjs +1192 -0
  187. package/dist/live-fixture.cjs.map +1 -0
  188. package/dist/live-fixture.js +1187 -0
  189. package/dist/live-fixture.js.map +1 -0
  190. package/dist/live-recorder.cjs +256 -0
  191. package/dist/live-recorder.cjs.map +1 -0
  192. package/dist/live-recorder.js +254 -0
  193. package/dist/live-recorder.js.map +1 -0
  194. package/dist/live-replay.cjs +286 -0
  195. package/dist/live-replay.cjs.map +1 -0
  196. package/dist/live-replay.js +285 -0
  197. package/dist/live-replay.js.map +1 -0
  198. package/dist/live-sanitize.cjs +196 -0
  199. package/dist/live-sanitize.cjs.map +1 -0
  200. package/dist/live-sanitize.js +196 -0
  201. package/dist/live-sanitize.js.map +1 -0
  202. package/dist/live-types.d.cts +71 -0
  203. package/dist/live-types.d.cts.map +1 -0
  204. package/dist/live-types.d.ts +71 -0
  205. package/dist/live-types.d.ts.map +1 -0
  206. package/dist/llmock.cjs +52 -41
  207. package/dist/llmock.cjs.map +1 -1
  208. package/dist/llmock.d.cts +16 -5
  209. package/dist/llmock.d.cts.map +1 -1
  210. package/dist/llmock.d.ts +16 -5
  211. package/dist/llmock.d.ts.map +1 -1
  212. package/dist/llmock.js +53 -42
  213. package/dist/llmock.js.map +1 -1
  214. package/dist/messages.cjs +11 -7
  215. package/dist/messages.cjs.map +1 -1
  216. package/dist/messages.d.cts.map +1 -1
  217. package/dist/messages.d.ts.map +1 -1
  218. package/dist/messages.js +13 -9
  219. package/dist/messages.js.map +1 -1
  220. package/dist/metrics.cjs +258 -5
  221. package/dist/metrics.cjs.map +1 -1
  222. package/dist/metrics.d.cts +3 -1
  223. package/dist/metrics.d.cts.map +1 -1
  224. package/dist/metrics.d.ts +3 -1
  225. package/dist/metrics.d.ts.map +1 -1
  226. package/dist/metrics.js +255 -6
  227. package/dist/metrics.js.map +1 -1
  228. package/dist/moderation.cjs +10 -1
  229. package/dist/moderation.cjs.map +1 -1
  230. package/dist/moderation.d.cts.map +1 -1
  231. package/dist/moderation.d.ts.map +1 -1
  232. package/dist/moderation.js +10 -1
  233. package/dist/moderation.js.map +1 -1
  234. package/dist/ollama.cjs +5 -5
  235. package/dist/ollama.cjs.map +1 -1
  236. package/dist/ollama.js +6 -6
  237. package/dist/ollama.js.map +1 -1
  238. package/dist/openrouter-video.cjs +5 -5
  239. package/dist/openrouter-video.cjs.map +1 -1
  240. package/dist/openrouter-video.js +6 -6
  241. package/dist/openrouter-video.js.map +1 -1
  242. package/dist/recorder.cjs +45 -9
  243. package/dist/recorder.cjs.map +1 -1
  244. package/dist/recorder.d.cts.map +1 -1
  245. package/dist/recorder.d.ts.map +1 -1
  246. package/dist/recorder.js +43 -11
  247. package/dist/recorder.js.map +1 -1
  248. package/dist/responses.cjs +4 -3
  249. package/dist/responses.cjs.map +1 -1
  250. package/dist/responses.js +5 -5
  251. package/dist/responses.js.map +1 -1
  252. package/dist/router.cjs +20 -33
  253. package/dist/router.cjs.map +1 -1
  254. package/dist/router.d.cts +4 -1
  255. package/dist/router.d.cts.map +1 -1
  256. package/dist/router.d.ts +4 -1
  257. package/dist/router.d.ts.map +1 -1
  258. package/dist/router.js +20 -34
  259. package/dist/router.js.map +1 -1
  260. package/dist/server.cjs +701 -625
  261. package/dist/server.cjs.map +1 -1
  262. package/dist/server.d.cts +1 -0
  263. package/dist/server.d.cts.map +1 -1
  264. package/dist/server.d.ts +1 -0
  265. package/dist/server.d.ts.map +1 -1
  266. package/dist/server.js +704 -628
  267. package/dist/server.js.map +1 -1
  268. package/dist/speech.cjs +2 -2
  269. package/dist/speech.cjs.map +1 -1
  270. package/dist/speech.js +3 -3
  271. package/dist/speech.js.map +1 -1
  272. package/dist/transcription.cjs +4 -4
  273. package/dist/transcription.cjs.map +1 -1
  274. package/dist/transcription.js +5 -5
  275. package/dist/transcription.js.map +1 -1
  276. package/dist/types.d.cts +119 -16
  277. package/dist/types.d.cts.map +1 -1
  278. package/dist/types.d.ts +119 -16
  279. package/dist/types.d.ts.map +1 -1
  280. package/dist/validate-cli.cjs +925 -0
  281. package/dist/validate-cli.cjs.map +1 -0
  282. package/dist/validate-cli.js +924 -0
  283. package/dist/validate-cli.js.map +1 -0
  284. package/dist/veo-video.cjs +13 -13
  285. package/dist/veo-video.cjs.map +1 -1
  286. package/dist/veo-video.js +14 -14
  287. package/dist/veo-video.js.map +1 -1
  288. package/dist/video.cjs +4 -4
  289. package/dist/video.cjs.map +1 -1
  290. package/dist/video.d.cts +1 -1
  291. package/dist/video.d.cts.map +1 -1
  292. package/dist/video.d.ts +1 -1
  293. package/dist/video.d.ts.map +1 -1
  294. package/dist/video.js +5 -5
  295. package/dist/video.js.map +1 -1
  296. package/dist/vitest.cjs +13 -6
  297. package/dist/vitest.cjs.map +1 -1
  298. package/dist/vitest.d.cts +2 -1
  299. package/dist/vitest.d.cts.map +1 -1
  300. package/dist/vitest.d.ts +2 -1
  301. package/dist/vitest.d.ts.map +1 -1
  302. package/dist/vitest.js +14 -7
  303. package/dist/vitest.js.map +1 -1
  304. package/dist/watcher.cjs +2 -1
  305. package/dist/watcher.cjs.map +1 -1
  306. package/dist/watcher.js +2 -1
  307. package/dist/watcher.js.map +1 -1
  308. package/dist/ws-framing.cjs +150 -35
  309. package/dist/ws-framing.cjs.map +1 -1
  310. package/dist/ws-framing.d.cts +18 -3
  311. package/dist/ws-framing.d.cts.map +1 -1
  312. package/dist/ws-framing.d.ts +18 -3
  313. package/dist/ws-framing.d.ts.map +1 -1
  314. package/dist/ws-framing.js +151 -36
  315. package/dist/ws-framing.js.map +1 -1
  316. package/dist/ws-gemini-live.cjs +2 -2
  317. package/dist/ws-gemini-live.d.ts +2 -2
  318. package/dist/ws-gemini-live.js +2 -2
  319. package/dist/ws-live.cjs +401 -0
  320. package/dist/ws-live.cjs.map +1 -0
  321. package/dist/ws-live.js +400 -0
  322. package/dist/ws-live.js.map +1 -0
  323. package/dist/ws-realtime.cjs +46 -2
  324. package/dist/ws-realtime.cjs.map +1 -1
  325. package/dist/ws-realtime.d.cts.map +1 -1
  326. package/dist/ws-realtime.d.ts +2 -2
  327. package/dist/ws-realtime.d.ts.map +1 -1
  328. package/dist/ws-realtime.js +46 -2
  329. package/dist/ws-realtime.js.map +1 -1
  330. package/dist/ws-responses.cjs +2 -2
  331. package/dist/ws-responses.js +2 -2
  332. package/dist/ws-upstream.cjs +118 -0
  333. package/dist/ws-upstream.cjs.map +1 -0
  334. package/dist/ws-upstream.js +115 -0
  335. package/dist/ws-upstream.js.map +1 -0
  336. package/fixtures/openai-live-client.json +390 -0
  337. package/fixtures/openai-live-managed.json +709 -0
  338. package/package.json +1 -1
package/dist/server.cjs CHANGED
@@ -2,15 +2,16 @@ const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
2
  const require_constants = require('./constants.cjs');
3
3
  const require_api_key_auth = require('./api-key-auth.cjs');
4
4
  const require_helpers = require('./helpers.cjs');
5
+ const require_live_fixture = require('./live-fixture.cjs');
6
+ const require_sse_writer = require('./sse-writer.cjs');
7
+ const require_chaos = require('./chaos.cjs');
8
+ const require_fixture_loader = require('./fixture-loader.cjs');
9
+ const require_router = require('./router.cjs');
10
+ const require_recorder = require('./recorder.cjs');
5
11
  const require_logger = require('./logger.cjs');
6
12
  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
13
  const require_interruption = require('./interruption.cjs');
11
14
  const require_openrouter_chat = require('./openrouter-chat.cjs');
12
- const require_chaos = require('./chaos.cjs');
13
- const require_recorder = require('./recorder.cjs');
14
15
  const require_responses = require('./responses.cjs');
15
16
  const require_messages = require('./messages.cjs');
16
17
  const require_gemini = require('./gemini.cjs');
@@ -30,22 +31,30 @@ const require_veo_video = require('./veo-video.cjs');
30
31
  const require_grok_video = require('./grok-video.cjs');
31
32
  const require_byteplus_video = require('./byteplus-video.cjs');
32
33
  const require_elevenlabs_audio = require('./elevenlabs-audio.cjs');
34
+ const require_elevenlabs_voice = require('./elevenlabs-voice.cjs');
33
35
  const require_ollama = require('./ollama.cjs');
34
36
  const require_cohere = require('./cohere.cjs');
35
37
  const require_search = require('./search.cjs');
36
38
  const require_rerank = require('./rerank.cjs');
37
39
  const require_moderation = require('./moderation.cjs');
40
+ const require_fine_tuning = require('./fine-tuning.cjs');
41
+ const require_files = require('./files.cjs');
42
+ const require_batches = require('./batches.cjs');
38
43
  const require_ws_framing = require('./ws-framing.cjs');
44
+ const require_ws_live = require('./ws-live.cjs');
39
45
  const require_ws_responses = require('./ws-responses.cjs');
40
46
  const require_ws_realtime = require('./ws-realtime.cjs');
41
47
  const require_ws_gemini_live = require('./ws-gemini-live.cjs');
42
48
  const require_metrics = require('./metrics.cjs');
43
49
  let node_http = require("node:http");
44
50
  node_http = require_runtime.__toESM(node_http);
51
+ let node_async_hooks = require("node:async_hooks");
45
52
 
46
53
  //#region src/server.ts
54
+ const liveClosers = /* @__PURE__ */ new WeakMap();
47
55
  const COMPLETIONS_PATH = "/v1/chat/completions";
48
56
  const RESPONSES_PATH = "/v1/responses";
57
+ const LIVE_PATH = "/v1/live/sessions";
49
58
  const REALTIME_PATH = "/v1/realtime";
50
59
  const GEMINI_LIVE_PATH = "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent";
51
60
  const MESSAGES_PATH = "/v1/messages";
@@ -66,10 +75,12 @@ const GEMINI_PREDICT_RE = /^\/v1beta\/models\/([^:]+):predict$/;
66
75
  const ELEVENLABS_SOUND_GENERATION_PATH = "/v1/sound-generation";
67
76
  const ELEVENLABS_TTS_RE = /^\/v1\/text-to-speech\/([^/]+)$/;
68
77
  const ELEVENLABS_MUSIC_RE = /^\/v1\/music(?:\/(.+))?$/;
78
+ const ELEVENLABS_VOICE_DESIGN_PATH = "/v1/text-to-voice/design";
79
+ const ELEVENLABS_VOICE_CREATE_PATH = "/v1/text-to-voice";
80
+ const ELEVENLABS_VOICE_RE = /^\/v1\/voices\/([^/]+)$/;
69
81
  const FAL_QUEUE_SUBMIT_RE = /^\/fal\/queue\/submit\/(.+)$/;
70
82
  const FAL_QUEUE_REQUESTS_RE = /^\/fal\/queue\/requests\/(.+)$/;
71
83
  const FAL_RUN_RE = /^\/fal\/run\/(.+)$/;
72
- const FAL_PREFIX_RE = /^\/fal(?:\/.*)?$/;
73
84
  const DEFAULT_CHUNK_SIZE = 20;
74
85
  const COMPAT_SUFFIXES = [
75
86
  "/chat/completions",
@@ -118,6 +129,14 @@ const HEALTH_PATH = "/health";
118
129
  const READY_PATH = "/ready";
119
130
  const MODELS_PATH = "/v1/models";
120
131
  const REQUESTS_PATH = "/v1/_requests";
132
+ const BATCHES_PATH = "/v1/batches";
133
+ const BATCHES_ID_RE = /^\/v1\/batches\/([^/]+)$/;
134
+ const BATCHES_CANCEL_RE = /^\/v1\/batches\/([^/]+)\/cancel$/;
135
+ const FILES_PATH = "/v1/files";
136
+ const FINE_TUNING_JOBS_PATH = "/v1/fine_tuning/jobs";
137
+ const FINE_TUNING_ID_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)$/;
138
+ const FINE_TUNING_CANCEL_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)\/cancel$/;
139
+ const FINE_TUNING_EVENTS_RE = /^\/v1\/fine_tuning\/jobs\/([^/]+)\/events$/;
121
140
  const DEFAULT_MODELS = [
122
141
  "gpt-4",
123
142
  "gpt-4o",
@@ -129,7 +148,7 @@ const CORS_HEADERS = {
129
148
  "Access-Control-Allow-Origin": "*",
130
149
  "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
131
150
  "Access-Control-Allow-Headers": "*",
132
- "Access-Control-Expose-Headers": "X-Total-Count"
151
+ "Access-Control-Expose-Headers": "X-Total-Count, X-Request-Id"
133
152
  };
134
153
  function setCorsHeaders(res) {
135
154
  for (const [key, value] of Object.entries(CORS_HEADERS)) res.setHeader(key, value);
@@ -157,14 +176,18 @@ const JOURNAL_PARAMS = new Set([
157
176
  "method",
158
177
  "status",
159
178
  "service",
160
- "testId"
179
+ "testId",
180
+ "requestId"
161
181
  ]);
162
182
  /**
163
183
  * Perform a full reset: clear the fixtures array, the journal (entries *and*
164
184
  * 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.
185
+ * job/queue state, the fine-tuning store (jobs, their append-only event logs,
186
+ * their poll counts, the create-time suffixes their model names are built from,
187
+ * and the module's monotonic clock), the ElevenLabs Voice Design store, the
188
+ * Gemini interaction/event-id counters, and any runtime chaos override
189
+ * (reverting to the construction-time chaos config), then re-zero the
190
+ * `aimock_fixtures_loaded` gauge.
168
191
  *
169
192
  * `targets` is `null` when no server is running (an in-process `reset()` before
170
193
  * `start()`). The process-global generation state is reset either way, since it
@@ -174,13 +197,19 @@ const JOURNAL_PARAMS = new Set([
174
197
  * `POST /__aimock/reset/fixtures` alias, and `LLMock.reset()`.
175
198
  */
176
199
  function performFullReset(fixtures, targets) {
177
- fixtures.length = 0;
200
+ require_fixture_loader.clearFixtureQueue(fixtures);
178
201
  require_fal_audio.falJobs.clear();
179
202
  require_fal.falQueueStates.clear();
203
+ require_batches.clearBatchStore();
204
+ require_elevenlabs_voice.clearElevenLabsVoices();
205
+ require_files.clearFileStore();
206
+ require_fine_tuning.clearFineTuningStore();
180
207
  require_gemini_interactions.resetInteractionCounter();
181
208
  require_gemini_interactions.resetEventIdCounter();
182
209
  if (!targets) return;
210
+ liveClosers.get(targets.defaults)?.();
183
211
  targets.journal.clear();
212
+ require_chaos.resetChaosWarnings(targets.defaults.logger);
184
213
  targets.defaults.chaos = void 0;
185
214
  targets.videoStates.clear();
186
215
  targets.openRouterVideoJobs.clear();
@@ -232,22 +261,18 @@ function fixtureResponseKind(response) {
232
261
  return "unknown";
233
262
  }
234
263
  /**
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`.
264
+ * The testId a journal entry was scoped under — `resolveTestId` applied to the
265
+ * journaled headers and path, i.e. the SAME function that scoped the request
266
+ * (fixture match-counts, chaos) when it arrived. Node folds a repeated
267
+ * `X-Test-Id` into one string ("a, a") before either side sees it, and the
268
+ * request is counted and chaos-evaluated under that folded string, so the
269
+ * `?testId=` filter matches it verbatim too: no token-splitting here that the
270
+ * scoping path does not also do. (Splitting on "," additionally broke every
271
+ * legitimately comma-bearing id.) A raw `path.includes("testId=t1")` both
272
+ * prefix-collides with `t10` and matches unrelated params like `notTestId`.
241
273
  */
242
274
  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;
275
+ return require_helpers.resolveTestId(entry.headers, entry.path);
251
276
  }
252
277
  /**
253
278
  * The testId a chaos override is stored under, and read back with — resolved by
@@ -274,10 +299,14 @@ function writeBlankTestId(res) {
274
299
  res.end(JSON.stringify({ error: "Invalid 'X-Test-Id': header is present but empty" }));
275
300
  return true;
276
301
  }
277
- /** The chaos config in effect for one testId: its override, else the baseline. */
302
+ /**
303
+ * The chaos config in effect for one testId: its override, else the baseline —
304
+ * one or the other, never the two merged. Mirrors `resolveScopedDefaults` in
305
+ * chaos.ts, so what `GET /__aimock/chaos` reports for a scope is exactly what
306
+ * that scope's traffic is evaluated against.
307
+ */
278
308
  function effectiveChaos(defaults, scopeId) {
279
- const scoped = defaults.chaosByTestId?.get(scopeId);
280
- if (scoped) return scoped;
309
+ if (defaults.chaosByTestId?.has(scopeId)) return defaults.chaosByTestId.get(scopeId) ?? {};
281
310
  const current = defaults.chaos;
282
311
  if (!current) return {};
283
312
  return require_chaos.isChaosScope(current) ? current.base ?? {} : current;
@@ -322,6 +351,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
322
351
  const methodFilter = searchParams.get("method");
323
352
  const serviceFilter = searchParams.get("service");
324
353
  const testIdFilter = searchParams.get("testId");
354
+ const requestIdFilter = searchParams.get("requestId");
325
355
  let entries = journal.getAll();
326
356
  if (methodFilter !== null) {
327
357
  const want = methodFilter.toUpperCase();
@@ -331,6 +361,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
331
361
  if (statusFilter !== void 0) entries = entries.filter((e) => e.response.status === statusFilter);
332
362
  if (serviceFilter !== null) entries = entries.filter((e) => e.service === serviceFilter);
333
363
  if (testIdFilter !== null) entries = entries.filter((e) => journalEntryTestId(e) === testIdFilter);
364
+ if (requestIdFilter !== null) entries = entries.filter((e) => e.headers["x-request-id"] === requestIdFilter);
334
365
  const total = entries.length;
335
366
  if (offset > 0) entries = entries.slice(offset);
336
367
  if (limit >= 0) entries = entries.slice(0, limit);
@@ -364,7 +395,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
364
395
  fixtures: fixtures.map((fixture, index) => ({
365
396
  index,
366
397
  match: redactForInspection(fixture.match),
367
- responseKind: fixtureResponseKind(fixture.response),
398
+ responseKind: require_fixture_loader.isOneShotError(fixture) ? "error" : fixtureResponseKind(fixture.response),
368
399
  ...fixture.latency !== void 0 ? { latency: fixture.latency } : {},
369
400
  ...fixture.chaos !== void 0 ? { chaos: fixture.chaos } : {}
370
401
  }))
@@ -414,26 +445,29 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
414
445
  res.end(JSON.stringify({ error: "Invalid body: expected a JSON object" }));
415
446
  return true;
416
447
  }
417
- const allowed = [
418
- "dropRate",
419
- "malformedRate",
420
- "disconnectRate"
421
- ];
422
- for (const key of Object.keys(parsed)) if (!allowed.includes(key)) {
448
+ for (const key of Object.keys(parsed)) if (!require_chaos.CHAOS_FIELD_NAMES.includes(key)) {
423
449
  res.writeHead(400, { "Content-Type": "application/json" });
424
450
  res.end(JSON.stringify({ error: `Unknown chaos field: '${key}'` }));
425
451
  return true;
426
452
  }
427
453
  const next = {};
428
- for (const key of allowed) {
454
+ for (const key of require_chaos.CHAOS_FIELD_NAMES) {
429
455
  const value = parsed[key];
430
456
  if (value === void 0) continue;
431
- if (typeof value !== "number" || Number.isNaN(value) || value < 0 || value > 1) {
457
+ const accepted = typeof value === "number" ? require_chaos.parseChaosField(key, value) : void 0;
458
+ if (accepted === void 0) {
459
+ const shape = require_chaos.CHAOS_FIELDS[key].integer ? "a whole number of ms" : "a number";
432
460
  res.writeHead(400, { "Content-Type": "application/json" });
433
- res.end(JSON.stringify({ error: `Invalid '${key}': must be a number between 0 and 1` }));
461
+ res.end(JSON.stringify({ error: `Invalid '${key}': must be ${shape} between 0 and ${require_chaos.CHAOS_FIELDS[key].max}` }));
434
462
  return true;
435
463
  }
436
- next[key] = value;
464
+ next[key] = accepted;
465
+ }
466
+ const shadowed = effectiveChaos(defaults, scopeId);
467
+ const droppedFields = Object.keys(shadowed).filter((key) => shadowed[key] !== void 0 && next[key] === void 0);
468
+ if (droppedFields.length > 0) {
469
+ const lost = droppedFields.map((key) => `${key}=${shadowed[key]}`).join(", ");
470
+ 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
471
  }
438
472
  defaults.chaosByTestId?.set(scopeId, next);
439
473
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -483,7 +517,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
483
517
  return true;
484
518
  }
485
519
  if (subPath === "/fixtures" && req.method === "DELETE") {
486
- fixtures.length = 0;
520
+ require_fixture_loader.clearFixtureQueue(fixtures);
487
521
  if (defaults.registry) defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
488
522
  res.writeHead(200, { "Content-Type": "application/json" });
489
523
  res.end(JSON.stringify({ cleared: true }));
@@ -577,32 +611,7 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
577
611
  }
578
612
  }
579
613
  }
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
- };
614
+ require_fixture_loader.queueOneShotError(fixtures, status, errorBody ?? void 0);
606
615
  res.writeHead(200, { "Content-Type": "application/json" });
607
616
  res.end(JSON.stringify({ queued: true }));
608
617
  return true;
@@ -610,6 +619,19 @@ async function handleControlAPI(req, res, pathname, searchParams, fixtures, jour
610
619
  handleNotFound(res, `Unknown control endpoint: ${pathname}`);
611
620
  return true;
612
621
  }
622
+ /**
623
+ * The client tore the connection down before its request body had fully
624
+ * arrived. Node rejects the pending body read with `aborted` (ECONNRESET) and
625
+ * destroys the response, so the rejection surfaces in the handler's read arm
626
+ * looking like any other thrown error. It is caller behaviour, not a server
627
+ * fault: no status can reach the client, so none must be journaled as
628
+ * delivered. A body that blew its size cap also leaves the request destroyed
629
+ * and incomplete, but that one is aimock's own decision, not a peer abort.
630
+ */
631
+ function clientAbortedMidBody(req, res, err) {
632
+ if (err instanceof require_helpers.RequestBodyTooLargeError) return false;
633
+ return res.destroyed && !req.complete;
634
+ }
613
635
  async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey, openRouter = false) {
614
636
  setCorsHeaders(res);
615
637
  let raw;
@@ -617,19 +639,43 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
617
639
  raw = await require_helpers.readBody(req);
618
640
  } catch (err) {
619
641
  const msg = err instanceof Error ? err.message : "Failed to read request body";
642
+ const clientFault = err instanceof require_helpers.RequestBodyTooLargeError;
643
+ const route = `${req.method ?? "POST"} ${req.url ?? COMPLETIONS_PATH}`;
644
+ const clientAbort = clientAbortedMidBody(req, res, err);
645
+ const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
646
+ if (clientAbort || bodyLimitDisconnect) {
647
+ const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
648
+ defaults.logger.warn(bodyLimitDisconnect ? `${route}: ${msg}` : `${route}: client aborted mid-body (${msg})`);
649
+ journal.add({
650
+ method: req.method ?? "POST",
651
+ path: req.url ?? COMPLETIONS_PATH,
652
+ headers: require_helpers.flattenHeaders(req.headers),
653
+ body: null,
654
+ response: {
655
+ status: 0,
656
+ fixture: null,
657
+ interrupted: true,
658
+ interruptReason
659
+ }
660
+ });
661
+ return;
662
+ }
663
+ const status = clientFault ? 400 : 500;
664
+ if (clientFault) defaults.logger.warn(`${route}: ${msg}`);
665
+ else defaults.logger.error(`${route}: ${msg}`);
620
666
  journal.add({
621
667
  method: req.method ?? "POST",
622
668
  path: req.url ?? COMPLETIONS_PATH,
623
669
  headers: require_helpers.flattenHeaders(req.headers),
624
670
  body: null,
625
671
  response: {
626
- status: 500,
672
+ status,
627
673
  fixture: null
628
674
  }
629
675
  });
630
- require_sse_writer.writeErrorResponse(res, 500, openRouter ? require_openrouter_chat.serializeOpenRouterError(500, `Request body read failed: ${msg}`) : JSON.stringify({ error: {
676
+ require_sse_writer.writeErrorResponse(res, status, openRouter ? require_openrouter_chat.serializeOpenRouterError(status, `Request body read failed: ${msg}`) : JSON.stringify({ error: {
631
677
  message: `Request body read failed: ${msg}`,
632
- type: "server_error"
678
+ type: clientFault ? "invalid_request_error" : "server_error"
633
679
  } }));
634
680
  return;
635
681
  }
@@ -705,7 +751,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
705
751
  let preResolvedResponse = null;
706
752
  let responseModel = body.model;
707
753
  let fixtureCountIncremented = false;
708
- if (openRouter && Array.isArray(body.models) && body.models.length > 0) {
754
+ const openRouterFallback = openRouter && Array.isArray(body.models) && body.models.length > 0;
755
+ const selectFixture = (probe, counts) => {
756
+ for (;;) {
757
+ const attempt = require_router.matchFixtureDiagnostic(fixtures, probe, counts, defaults.requestTransform, matchOptions);
758
+ if (attempt.fixture && require_fixture_loader.isOneShotError(attempt.fixture) && !require_fixture_loader.claimOneShotError(fixtures, attempt.fixture)) continue;
759
+ return attempt;
760
+ }
761
+ };
762
+ if (openRouterFallback) {
709
763
  const candidates = require_openrouter_chat.buildOpenRouterCandidates(body);
710
764
  let matchCounts = journal.getFixtureMatchCountsForTest(testId);
711
765
  const allowFallbacks = body.provider?.allow_fallbacks !== false;
@@ -716,7 +770,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
716
770
  ...body,
717
771
  model: candidate
718
772
  };
719
- const attempt = require_router.matchFixtureDiagnostic(fixtures, probe, matchCounts, defaults.requestTransform, matchOptions);
773
+ const attempt = selectFixture(probe, matchCounts);
720
774
  skippedBySequenceOrTurn = Math.max(skippedBySequenceOrTurn, attempt.skippedBySequenceOrTurn);
721
775
  if (!attempt.fixture) return null;
722
776
  const response = await require_helpers.resolveResponse(attempt.fixture, probe);
@@ -753,35 +807,48 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
753
807
  preResolvedResponse = lastErrorResponse;
754
808
  }
755
809
  } else {
756
- const single = require_router.matchFixtureDiagnostic(fixtures, body, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, matchOptions);
810
+ const single = selectFixture(body, journal.getFixtureMatchCountsForTest(testId));
757
811
  fixture = single.fixture;
758
812
  skippedBySequenceOrTurn = single.skippedBySequenceOrTurn;
759
813
  }
760
814
  if (fixture) {
761
815
  if (!fixtureCountIncremented) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
762
- defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
816
+ defaults.logger.debug(`Fixture matched: ${require_helpers.describeMatch(fixture.match, fixtures.indexOf(fixture))}`);
763
817
  } else {
764
818
  const lastUserMsg = body.messages.filter((m) => m.role === "user").pop();
765
819
  const snippet = typeof lastUserMsg?.content === "string" ? lastUserMsg.content.slice(0, 80) : "";
766
820
  defaults.logger.debug(`No fixture matched for request (model=${body.model ?? "?"}, msg="${snippet}")`);
767
821
  }
768
- const chaosAction = require_chaos.evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger, req.url);
822
+ const chaosConfig = require_chaos.resolveChaosConfig(fixture, defaults.chaos, req.headers, defaults.logger, req.url);
823
+ await require_chaos.awaitChaosLatency(fixture, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
824
+ const goneAfterLatency = require_chaos.responseGoneReason(res);
825
+ if (goneAfterLatency !== null) {
826
+ require_fixture_loader.releaseOneShotError(fixtures, fixture);
827
+ defaults.logger.debug(`[chaos] ${method} ${path}: ${require_chaos.describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
828
+ return;
829
+ }
830
+ const chaosAction = require_chaos.evaluateChaos(fixture, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
769
831
  const chaosContext = {
770
832
  method,
771
833
  path,
772
834
  headers: flatHeaders,
773
835
  body
774
836
  };
775
- if (chaosAction === "drop" || chaosAction === "disconnect") {
776
- require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "proxy", defaults.registry);
837
+ const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
838
+ const missWouldProxy = require_helpers.wouldProxyMiss(effectiveStrict, defaults.record, providerKey);
839
+ const noFixtureSource = missWouldProxy ? "proxy" : "internal";
840
+ if (chaosAction === "drop" || chaosAction === "disconnect" || chaosAction === "rateLimit") {
841
+ require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : noFixtureSource, defaults.registry, defaults.logger);
842
+ require_fixture_loader.releaseOneShotError(fixtures, fixture);
777
843
  return;
778
844
  }
779
- if (fixture && chaosAction === "malformed") {
780
- require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, "fixture", defaults.registry);
845
+ if (chaosAction === "malformed" && (fixture || !missWouldProxy)) {
846
+ require_chaos.applyChaosAction(chaosAction, res, fixture, journal, chaosContext, fixture ? "fixture" : "internal", defaults.registry, defaults.logger);
847
+ require_fixture_loader.releaseOneShotError(fixtures, fixture);
781
848
  return;
782
849
  }
783
850
  if (!fixture) {
784
- if (require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
851
+ if (effectiveStrict) {
785
852
  const strictStatus = 503;
786
853
  const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
787
854
  defaults.logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", req.url ?? COMPLETIONS_PATH, skippedBySequenceOrTurn));
@@ -807,7 +874,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
807
874
  if (defaults.record && providerKey) {
808
875
  const hookOptions = chaosAction === "malformed" ? {
809
876
  beforeWriteResponse: () => {
810
- require_chaos.applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry);
877
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, chaosContext, "proxy", defaults.registry, defaults.logger);
811
878
  return true;
812
879
  },
813
880
  onHookBypassed: (reason) => {
@@ -819,9 +886,22 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
819
886
  });
820
887
  }
821
888
  } : void 0;
822
- const outcome = await require_recorder.proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions);
889
+ let clientHungUp = false;
890
+ const noteClientHangUp = () => {
891
+ if (!res.writableEnded) clientHungUp = true;
892
+ };
893
+ req.socket?.on("end", noteClientHangUp);
894
+ req.socket?.on("error", noteClientHangUp);
895
+ let outcome;
896
+ try {
897
+ outcome = await require_recorder.proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions);
898
+ } finally {
899
+ req.socket?.off("end", noteClientHangUp);
900
+ req.socket?.off("error", noteClientHangUp);
901
+ }
823
902
  if (outcome === "handled_by_hook") return;
824
903
  if (outcome !== "not_configured") {
904
+ const destroyedMidStream = res.destroyed && !res.writableEnded;
825
905
  journal.add({
826
906
  method: req.method ?? "POST",
827
907
  path: req.url ?? COMPLETIONS_PATH,
@@ -830,11 +910,19 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
830
910
  response: {
831
911
  status: res.statusCode ?? 200,
832
912
  fixture: null,
833
- source: "proxy"
913
+ source: "proxy",
914
+ ...destroyedMidStream ? {
915
+ interrupted: true,
916
+ interruptReason: clientHungUp ? "client aborted" : "proxy stream destroyed"
917
+ } : {}
834
918
  }
835
919
  });
836
920
  return;
837
921
  }
922
+ if (chaosAction === "malformed") {
923
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, chaosContext, "internal", defaults.registry, defaults.logger);
924
+ return;
925
+ }
838
926
  }
839
927
  journal.add({
840
928
  method: req.method ?? "POST",
@@ -910,7 +998,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
910
998
  const effectiveStrict = require_helpers.resolveStrictMode(defaults.strict, req.headers);
911
999
  const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
912
1000
  const streaming = body.stream === true;
913
- const streamingBlocks = streaming && response.blocks && response.blocks.length > 0 ? require_helpers.resolveFixtureBlocks(response.blocks) : void 0;
1001
+ const blockOutcome = response.blocks && response.blocks.length > 0 ? require_helpers.resolveFixtureBlockOutcome(response.blocks) : void 0;
914
1002
  const journalEntry = journal.add({
915
1003
  method: req.method ?? "POST",
916
1004
  path: req.url ?? COMPLETIONS_PATH,
@@ -922,12 +1010,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
922
1010
  }
923
1011
  });
924
1012
  if (!streaming) {
925
- const completion = require_helpers.buildContentWithToolCallsCompletion(response.content ?? "", response.toolCalls ?? [], responseModel, effReasoning, overrides, body.messages);
1013
+ const completion = require_helpers.buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? response.toolCalls ?? [], responseModel, effReasoning, blockOutcome ? {
1014
+ ...overrides,
1015
+ finishReason: overrides?.finishReason ?? (blockOutcome.hasToolCalls ? "tool_calls" : "stop")
1016
+ } : overrides, body.messages);
926
1017
  res.writeHead(200, { "Content-Type": "application/json" });
927
1018
  res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
928
1019
  } 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("");
1020
+ const chunks = require_helpers.buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? response.toolCalls ?? [], responseModel, chunkSize, effReasoning, overrides, blockOutcome?.ordered);
1021
+ 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
1022
  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
1023
  shapeORChunks(chunks, usageChunk, overrides);
933
1024
  const interruption = require_interruption.createInterruptionSignal(fixture);
@@ -1111,21 +1202,14 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1111
1202
  }
1112
1203
  };
1113
1204
  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`);
1205
+ const chaosDefaults = options.chaos;
1206
+ for (const field of require_chaos.CHAOS_FIELD_NAMES) {
1207
+ const value = chaosDefaults[field];
1208
+ if (value === void 0) continue;
1209
+ if (typeof value === "number" && require_chaos.parseChaosField(field, value) !== void 0) continue;
1210
+ const shape = require_chaos.CHAOS_FIELDS[field].integer ? "a whole number of ms" : "a number";
1211
+ 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`);
1212
+ }
1129
1213
  }
1130
1214
  for (const { name, config } of [
1131
1215
  {
@@ -1171,62 +1255,241 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1171
1255
  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
1256
  }
1173
1257
  }
1258
+ /**
1259
+ * The in-flight request, scoped to the async execution that serves it.
1260
+ * Entered once in the `http.createServer` callback, so every handler —
1261
+ * including the ones in other modules that only ever see the shared
1262
+ * `journal` — runs inside it.
1263
+ */
1264
+ const requestScope = new node_async_hooks.AsyncLocalStorage();
1265
+ /** The journal entry each request produced, keyed by the request object. */
1266
+ const ownJournalEntry = /* @__PURE__ */ new WeakMap();
1174
1267
  const journal = new require_journal.Journal({
1175
1268
  maxEntries: options?.journalMaxEntries ?? 1e3,
1176
- fixtureCountsMaxTestIds: options?.fixtureCountsMaxTestIds ?? 500
1269
+ fixtureCountsMaxTestIds: options?.fixtureCountsMaxTestIds ?? 500,
1270
+ onAdd: (entry) => {
1271
+ const req = requestScope.getStore();
1272
+ if (req) ownJournalEntry.set(req, entry);
1273
+ }
1177
1274
  });
1178
1275
  const videoStates = new require_video.VideoStateMap();
1179
1276
  const openRouterVideoJobs = new require_openrouter_video.OpenRouterVideoJobMap();
1180
1277
  const veoVideoJobs = new require_veo_video.VeoVideoJobMap();
1181
1278
  const grokVideoJobs = new require_grok_video.GrokVideoJobMap();
1182
1279
  const bytePlusVideoJobs = new require_byteplus_video.BytePlusVideoJobMap();
1280
+ /**
1281
+ * The OpenAI error `type` that goes with a status: 4xx is the caller's
1282
+ * fault, everything else is ours. Shared by `routeError` and the envelopes
1283
+ * it calls so the body can never disagree with the status line.
1284
+ */
1285
+ function errorTypeForStatus(status) {
1286
+ return status >= 400 && status < 500 ? "invalid_request_error" : "server_error";
1287
+ }
1288
+ /**
1289
+ * The newest journal entry this request already produced, or null.
1290
+ *
1291
+ * Attribution is by the identity of the `IncomingMessage` the entry was
1292
+ * written under — never by `x-request-id`. That header is CALLER-supplied
1293
+ * (`resolveRequestId` echoes a well-formed one) and nothing makes it
1294
+ * unique: a retry, a harness that pins one id, or a load generator sends
1295
+ * the same id on many requests, and keying off it made a crash rewrite an
1296
+ * EARLIER request's entry while leaving the crashing request untraced. The
1297
+ * request object cannot be collided by a caller, so one entry belongs to
1298
+ * exactly one request. A handler writes at most one entry; if it writes
1299
+ * more, the newest wins, which is the entry the old scan would have found.
1300
+ */
1301
+ function lastJournalEntryFor(req) {
1302
+ return ownJournalEntry.get(req) ?? null;
1303
+ }
1304
+ /**
1305
+ * Uniform terminal arm for a route handler that threw. Every route error
1306
+ * path logs the route, journals the failure, sets CORS and answers the error
1307
+ * envelope — so a handler crash is never invisible in the logs or in
1308
+ * `/__aimock/journal`.
1309
+ *
1310
+ * ONE request produces ONE entry. `res.headersSent` is not the test for that:
1311
+ * a handler that journaled its 200 and then threw before `writeHead` has
1312
+ * sent no headers, and journaling again would leave two contradicting
1313
+ * entries for one request. So the existing entry is looked up by request
1314
+ * identity ({@link lastJournalEntryFor}) and AMENDED — status to what the
1315
+ * client actually got when the response is still changeable, or interrupted
1316
+ * when it isn't — and a fresh entry is
1317
+ * added only when the handler journaled nothing. That holds after the
1318
+ * response too: a handler that answered and then crashed before its own
1319
+ * journal write gets an entry carrying the status already sent, so a
1320
+ * post-response crash is never invisible.
1321
+ *
1322
+ * Once headers are on the wire the status can't change, so the socket is
1323
+ * destroyed: a clean `end()` reads as EOF and the client treats a truncated
1324
+ * stream as a complete response. The chat-style SSE routes whose clients
1325
+ * surface an error frame pass `streamEvent`; that frame is written first,
1326
+ * but the tear-down still follows it.
1327
+ *
1328
+ * A body that blew its size cap is the CALLER's fault, not a server fault:
1329
+ * it logs at warn, but if the reader destroyed the socket it journals an
1330
+ * interruption with status 0 because no 4xx reached the client. A client
1331
+ * that hung up before its body arrived
1332
+ * ({@link clientAbortedMidBody}) is not a fault at all: it logs at warn and
1333
+ * journals a torn socket (`interrupted`, status 0), never a 500 the client
1334
+ * could not have received.
1335
+ */
1336
+ function routeError(req, res, err, pathname, opts) {
1337
+ const route = `${req.method ?? "?"} ${pathname}`;
1338
+ const msg = err instanceof Error ? err.message : "Internal error";
1339
+ const clientFault = err instanceof require_helpers.RequestBodyTooLargeError;
1340
+ const clientAbort = clientAbortedMidBody(req, res, err);
1341
+ const status = clientFault ? 400 : 500;
1342
+ if (clientAbort) logger.warn(`${route}: client aborted mid-body (${msg})`);
1343
+ else if (clientFault) logger.warn(`${route}: ${msg}`);
1344
+ else logger.error(`${route}: ${msg}`);
1345
+ if (err instanceof Error && err.stack) logger.debug(err.stack);
1346
+ const existing = lastJournalEntryFor(req);
1347
+ const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
1348
+ if ((clientAbort || bodyLimitDisconnect) && !res.headersSent) {
1349
+ const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
1350
+ if (existing) {
1351
+ if (!existing.response.interrupted) {
1352
+ existing.response.status = 0;
1353
+ existing.response.interrupted = true;
1354
+ existing.response.interruptReason = interruptReason;
1355
+ }
1356
+ return;
1357
+ }
1358
+ try {
1359
+ journal.add({
1360
+ method: req.method ?? "?",
1361
+ path: req.url ?? "?",
1362
+ headers: require_helpers.flattenHeaders(req.headers),
1363
+ body: null,
1364
+ ...opts?.service ? { service: opts.service } : {},
1365
+ response: {
1366
+ status: 0,
1367
+ fixture: null,
1368
+ source: "internal",
1369
+ interrupted: true,
1370
+ interruptReason
1371
+ }
1372
+ });
1373
+ } catch (jErr) {
1374
+ logger.warn(`${route}: journal write failed after body-read interruption: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1375
+ }
1376
+ return;
1377
+ }
1378
+ if (!res.headersSent) {
1379
+ if (existing) existing.response.status = status;
1380
+ else try {
1381
+ journal.add({
1382
+ method: req.method ?? "?",
1383
+ path: req.url ?? "?",
1384
+ headers: require_helpers.flattenHeaders(req.headers),
1385
+ body: null,
1386
+ ...opts?.service ? { service: opts.service } : {},
1387
+ response: {
1388
+ status,
1389
+ fixture: null,
1390
+ source: "internal"
1391
+ }
1392
+ });
1393
+ } catch (jErr) {
1394
+ logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1395
+ }
1396
+ setCorsHeaders(res);
1397
+ require_sse_writer.writeErrorResponse(res, status, opts?.envelope ? opts.envelope(msg, status) : JSON.stringify({ error: {
1398
+ message: msg,
1399
+ type: errorTypeForStatus(status)
1400
+ } }));
1401
+ } else {
1402
+ const midStreamReason = clientAbort ? "client aborted" : "handler crashed mid-stream";
1403
+ if (existing) {
1404
+ if (res.writableEnded) {
1405
+ if (existing.response.error === void 0) existing.response.error = msg;
1406
+ } else if (!existing.response.interrupted) {
1407
+ existing.response.interrupted = true;
1408
+ existing.response.interruptReason = midStreamReason;
1409
+ }
1410
+ } else try {
1411
+ journal.add({
1412
+ method: req.method ?? "?",
1413
+ path: req.url ?? "?",
1414
+ headers: require_helpers.flattenHeaders(req.headers),
1415
+ body: null,
1416
+ ...opts?.service ? { service: opts.service } : {},
1417
+ response: {
1418
+ status: res.statusCode,
1419
+ fixture: null,
1420
+ source: "internal",
1421
+ ...res.writableEnded ? { error: msg } : {
1422
+ interrupted: true,
1423
+ interruptReason: midStreamReason
1424
+ }
1425
+ }
1426
+ });
1427
+ } catch (jErr) {
1428
+ logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1429
+ }
1430
+ if (res.writableEnded) return;
1431
+ if (opts?.streamEvent) try {
1432
+ res.write(opts.streamEvent(msg, status), () => {
1433
+ if (!res.destroyed) res.destroy();
1434
+ });
1435
+ } catch (writeErr) {
1436
+ logger.debug("Failed to write error recovery response:", writeErr);
1437
+ res.destroy();
1438
+ }
1439
+ else res.destroy();
1440
+ }
1441
+ }
1183
1442
  if (mounts) for (const { handler } of mounts) {
1184
1443
  if (handler.setJournal) handler.setJournal(journal);
1185
1444
  if (registry && handler.setRegistry) handler.setRegistry(registry);
1186
1445
  }
1187
1446
  if (registry) registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
1188
1447
  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();
1448
+ requestScope.run(req, () => {
1449
+ handleHttpRequest(req, res).catch((err) => {
1450
+ routeError(req, res, err, req.url ?? "?");
1451
+ });
1202
1452
  });
1203
1453
  });
1204
1454
  async function handleHttpRequest(req, res) {
1205
1455
  const startTime = registry ? process.hrtime.bigint() : 0n;
1456
+ const { id: requestId, generated: requestIdMinted } = require_helpers.resolveRequestId(req.headers);
1457
+ req.headers["x-request-id"] = requestId;
1458
+ require_helpers.markMintedRequestId(req, requestIdMinted);
1459
+ res.setHeader("X-Request-Id", requestId);
1460
+ let pathname = "";
1461
+ if (registry) {
1462
+ let recorded = false;
1463
+ const recordOutcome = (status) => {
1464
+ if (recorded) return;
1465
+ recorded = true;
1466
+ try {
1467
+ const normalizedPath = require_metrics.normalizePathLabel(pathname, mounts ? mounts.map((m) => m.path) : []);
1468
+ const method = req.method ?? "UNKNOWN";
1469
+ registry.incrementCounter("aimock_requests_total", {
1470
+ method,
1471
+ path: normalizedPath,
1472
+ status
1473
+ });
1474
+ const elapsed = Number(process.hrtime.bigint() - startTime) / 1e9;
1475
+ registry.observeHistogram("aimock_request_duration_seconds", {
1476
+ method,
1477
+ path: normalizedPath
1478
+ }, elapsed);
1479
+ } catch (err) {
1480
+ defaults.logger.warn("metrics instrumentation error", err);
1481
+ }
1482
+ };
1483
+ res.on("finish", () => recordOutcome(String(res.statusCode)));
1484
+ res.on("close", () => {
1485
+ if (!res.writableFinished) recordOutcome(require_metrics.DESTROYED_STATUS_LABEL);
1486
+ });
1487
+ }
1206
1488
  const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1207
- let pathname = parsedUrl.pathname;
1489
+ pathname = parsedUrl.pathname;
1208
1490
  const originalPathname = pathname;
1209
1491
  const isOpenRouter = require_openrouter_chat.isOpenRouterPath(originalPathname);
1210
1492
  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
1493
  if (req.method === "OPTIONS" && (!resolvedAuth.policy.enabled || req.headers.origin !== void 0 && req.headers["access-control-request-method"] !== void 0)) {
1231
1494
  handleOptions(res);
1232
1495
  return;
@@ -1252,12 +1515,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1252
1515
  try {
1253
1516
  await require_ollama.handleOllama(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1254
1517
  } 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();
1518
+ routeError(req, res, err, pathname);
1261
1519
  }
1262
1520
  return;
1263
1521
  }
@@ -1265,12 +1523,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1265
1523
  try {
1266
1524
  await require_ollama.handleOllamaGenerate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1267
1525
  } 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();
1526
+ routeError(req, res, err, pathname);
1274
1527
  }
1275
1528
  return;
1276
1529
  }
@@ -1278,12 +1531,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1278
1531
  try {
1279
1532
  await require_ollama.handleOllamaEmbeddings(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1280
1533
  } 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();
1534
+ routeError(req, res, err, pathname);
1287
1535
  }
1288
1536
  return;
1289
1537
  }
@@ -1308,28 +1556,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1308
1556
  try {
1309
1557
  await require_openrouter_video.handleOpenRouterVideoContent(req, res, openRouterVideoContentMatch[1], journal, defaults, setCorsHeaders, openRouterVideoJobs);
1310
1558
  } 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();
1559
+ routeError(req, res, err, pathname);
1333
1560
  }
1334
1561
  return;
1335
1562
  }
@@ -1337,28 +1564,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1337
1564
  try {
1338
1565
  await require_openrouter_video.handleOpenRouterVideoModels(req, res, fixtures, journal, defaults, setCorsHeaders);
1339
1566
  } 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();
1567
+ routeError(req, res, err, pathname);
1362
1568
  }
1363
1569
  return;
1364
1570
  }
@@ -1367,28 +1573,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1367
1573
  try {
1368
1574
  await require_openrouter_video.handleOpenRouterVideoStatus(req, res, openRouterVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
1369
1575
  } 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();
1576
+ routeError(req, res, err, pathname);
1392
1577
  }
1393
1578
  return;
1394
1579
  }
@@ -1397,28 +1582,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1397
1582
  try {
1398
1583
  await require_openrouter_video.handleOpenRouterVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, openRouterVideoJobs);
1399
1584
  } 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();
1585
+ routeError(req, res, err, pathname);
1422
1586
  }
1423
1587
  return;
1424
1588
  }
@@ -1427,28 +1591,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1427
1591
  try {
1428
1592
  await require_byteplus_video.handleBytePlusVideoStatus(req, res, bytePlusVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
1429
1593
  } 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();
1594
+ routeError(req, res, err, pathname);
1452
1595
  }
1453
1596
  return;
1454
1597
  }
@@ -1457,28 +1600,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1457
1600
  try {
1458
1601
  await require_byteplus_video.handleBytePlusVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, bytePlusVideoJobs);
1459
1602
  } 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();
1603
+ routeError(req, res, err, pathname);
1482
1604
  }
1483
1605
  return;
1484
1606
  }
@@ -1556,6 +1678,127 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1556
1678
  }));
1557
1679
  return;
1558
1680
  }
1681
+ const batchesCancelMatch = pathname.match(BATCHES_CANCEL_RE);
1682
+ if (batchesCancelMatch && req.method === "POST") {
1683
+ try {
1684
+ await require_helpers.readBody(req);
1685
+ await require_batches.handleBatchesCancel(req, res, batchesCancelMatch[1], journal, defaults, setCorsHeaders);
1686
+ } catch (err) {
1687
+ routeError(req, res, err, pathname, { service: "batches" });
1688
+ }
1689
+ return;
1690
+ }
1691
+ const batchesIdMatch = pathname.match(BATCHES_ID_RE);
1692
+ if (batchesIdMatch && req.method === "GET") {
1693
+ try {
1694
+ await require_batches.handleBatchesRetrieve(req, res, batchesIdMatch[1], journal, defaults, setCorsHeaders);
1695
+ } catch (err) {
1696
+ routeError(req, res, err, pathname, { service: "batches" });
1697
+ }
1698
+ return;
1699
+ }
1700
+ if (pathname === BATCHES_PATH && req.method === "GET") {
1701
+ try {
1702
+ await require_batches.handleBatchesList(req, res, journal, defaults, setCorsHeaders);
1703
+ } catch (err) {
1704
+ routeError(req, res, err, pathname, { service: "batches" });
1705
+ }
1706
+ return;
1707
+ }
1708
+ if (pathname === BATCHES_PATH && req.method === "POST") {
1709
+ try {
1710
+ await require_batches.handleBatchesCreate(req, res, await require_helpers.readBody(req), journal, defaults, setCorsHeaders);
1711
+ } catch (err) {
1712
+ routeError(req, res, err, pathname, { service: "batches" });
1713
+ }
1714
+ return;
1715
+ }
1716
+ const filesContentMatch = pathname.match(require_metrics.FILES_CONTENT_RE);
1717
+ if (filesContentMatch && req.method === "GET") {
1718
+ try {
1719
+ await require_files.handleFilesContent(req, res, filesContentMatch[1], journal, defaults, setCorsHeaders);
1720
+ } catch (err) {
1721
+ routeError(req, res, err, pathname, { service: "files" });
1722
+ }
1723
+ return;
1724
+ }
1725
+ const filesIdMatch = pathname.match(require_metrics.FILES_ID_RE);
1726
+ if (filesIdMatch && req.method === "GET") {
1727
+ try {
1728
+ await require_files.handleFilesRetrieve(req, res, filesIdMatch[1], journal, defaults, setCorsHeaders);
1729
+ } catch (err) {
1730
+ routeError(req, res, err, pathname, { service: "files" });
1731
+ }
1732
+ return;
1733
+ }
1734
+ if (filesIdMatch && req.method === "DELETE") {
1735
+ try {
1736
+ await require_files.handleFilesDelete(req, res, filesIdMatch[1], journal, defaults, setCorsHeaders);
1737
+ } catch (err) {
1738
+ routeError(req, res, err, pathname, { service: "files" });
1739
+ }
1740
+ return;
1741
+ }
1742
+ if (pathname === FILES_PATH && req.method === "GET") {
1743
+ try {
1744
+ await require_files.handleFilesList(req, res, journal, defaults, setCorsHeaders);
1745
+ } catch (err) {
1746
+ routeError(req, res, err, pathname, { service: "files" });
1747
+ }
1748
+ return;
1749
+ }
1750
+ if (pathname === FILES_PATH && req.method === "POST") {
1751
+ try {
1752
+ 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);
1753
+ } catch (err) {
1754
+ routeError(req, res, err, pathname, { service: "files" });
1755
+ }
1756
+ return;
1757
+ }
1758
+ const ftCancelMatch = pathname.match(FINE_TUNING_CANCEL_RE);
1759
+ if (ftCancelMatch && req.method === "POST") {
1760
+ try {
1761
+ await require_helpers.readBody(req);
1762
+ await require_fine_tuning.handleFineTuningCancel(req, res, ftCancelMatch[1], journal, defaults, setCorsHeaders);
1763
+ } catch (err) {
1764
+ routeError(req, res, err, pathname, { service: "fine-tuning" });
1765
+ }
1766
+ return;
1767
+ }
1768
+ const ftEventsMatch = pathname.match(FINE_TUNING_EVENTS_RE);
1769
+ if (ftEventsMatch && req.method === "GET") {
1770
+ try {
1771
+ await require_fine_tuning.handleFineTuningEvents(req, res, ftEventsMatch[1], journal, defaults, setCorsHeaders);
1772
+ } catch (err) {
1773
+ routeError(req, res, err, pathname, { service: "fine-tuning" });
1774
+ }
1775
+ return;
1776
+ }
1777
+ const ftIdMatch = pathname.match(FINE_TUNING_ID_RE);
1778
+ if (ftIdMatch && req.method === "GET") {
1779
+ try {
1780
+ await require_fine_tuning.handleFineTuningRetrieve(req, res, ftIdMatch[1], journal, defaults, setCorsHeaders);
1781
+ } catch (err) {
1782
+ routeError(req, res, err, pathname, { service: "fine-tuning" });
1783
+ }
1784
+ return;
1785
+ }
1786
+ if (pathname === FINE_TUNING_JOBS_PATH && req.method === "GET") {
1787
+ try {
1788
+ await require_fine_tuning.handleFineTuningList(req, res, journal, defaults, setCorsHeaders);
1789
+ } catch (err) {
1790
+ routeError(req, res, err, pathname, { service: "fine-tuning" });
1791
+ }
1792
+ return;
1793
+ }
1794
+ if (pathname === FINE_TUNING_JOBS_PATH && req.method === "POST") {
1795
+ try {
1796
+ await require_fine_tuning.handleFineTuningCreate(req, res, await require_helpers.readBody(req), journal, defaults, setCorsHeaders);
1797
+ } catch (err) {
1798
+ routeError(req, res, err, pathname, { service: "fine-tuning" });
1799
+ }
1800
+ return;
1801
+ }
1559
1802
  if (pathname === REQUESTS_PATH) {
1560
1803
  setCorsHeaders(res);
1561
1804
  if (req.method === "GET") {
@@ -1590,17 +1833,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1590
1833
  try {
1591
1834
  await require_responses.handleResponses(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1592
1835
  } 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
- }
1836
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
1604
1837
  }
1605
1838
  return;
1606
1839
  }
@@ -1608,17 +1841,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1608
1841
  try {
1609
1842
  await require_messages.handleMessages(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1610
1843
  } 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
- }
1844
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
1622
1845
  }
1623
1846
  return;
1624
1847
  }
@@ -1626,17 +1849,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1626
1849
  try {
1627
1850
  await require_cohere.handleCohere(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1628
1851
  } 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
- }
1852
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
1640
1853
  }
1641
1854
  return;
1642
1855
  }
@@ -1644,12 +1857,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1644
1857
  try {
1645
1858
  await require_cohere.handleCohereEmbed(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1646
1859
  } 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();
1860
+ routeError(req, res, err, pathname);
1653
1861
  }
1654
1862
  return;
1655
1863
  }
@@ -1669,12 +1877,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1669
1877
  }
1670
1878
  await require_embeddings.handleEmbeddings(req, res, raw, fixtures, journal, defaults, setCorsHeaders, embeddingsProvider);
1671
1879
  } 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();
1880
+ routeError(req, res, err, pathname);
1678
1881
  }
1679
1882
  return;
1680
1883
  }
@@ -1682,12 +1885,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1682
1885
  try {
1683
1886
  await require_images.handleImages(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, "openai", void 0, isBytePlusArk ? "byteplus" : void 0);
1684
1887
  } 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();
1888
+ routeError(req, res, err, pathname);
1691
1889
  }
1692
1890
  return;
1693
1891
  }
@@ -1695,12 +1893,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1695
1893
  try {
1696
1894
  await require_images.handleImageEdit(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1697
1895
  } 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();
1896
+ routeError(req, res, err, pathname);
1704
1897
  }
1705
1898
  return;
1706
1899
  }
@@ -1708,12 +1901,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1708
1901
  try {
1709
1902
  await require_images.handleImageVariations(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1710
1903
  } 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();
1904
+ routeError(req, res, err, pathname);
1717
1905
  }
1718
1906
  return;
1719
1907
  }
@@ -1721,12 +1909,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1721
1909
  try {
1722
1910
  await require_speech.handleSpeech(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1723
1911
  } 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();
1912
+ routeError(req, res, err, pathname);
1730
1913
  }
1731
1914
  return;
1732
1915
  }
@@ -1734,12 +1917,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1734
1917
  try {
1735
1918
  await require_transcription.handleTranscription(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1736
1919
  } 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();
1920
+ routeError(req, res, err, pathname);
1743
1921
  }
1744
1922
  return;
1745
1923
  }
@@ -1747,12 +1925,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1747
1925
  try {
1748
1926
  await require_transcription.handleTranscription(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, "translation");
1749
1927
  } 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();
1928
+ routeError(req, res, err, pathname);
1756
1929
  }
1757
1930
  return;
1758
1931
  }
@@ -1761,28 +1934,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1761
1934
  try {
1762
1935
  await require_grok_video.handleGrokVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, grokVideoJobs);
1763
1936
  } 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();
1937
+ routeError(req, res, err, pathname);
1786
1938
  }
1787
1939
  return;
1788
1940
  }
@@ -1790,12 +1942,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1790
1942
  try {
1791
1943
  await require_video.handleVideoCreate(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, videoStates);
1792
1944
  } 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();
1945
+ routeError(req, res, err, pathname);
1799
1946
  }
1800
1947
  return;
1801
1948
  }
@@ -1804,28 +1951,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1804
1951
  try {
1805
1952
  await require_grok_video.handleGrokVideoStatus(req, res, grokVideoStatusMatch[1], fixtures, journal, defaults, setCorsHeaders, grokVideoJobs, videoStates);
1806
1953
  } 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();
1954
+ routeError(req, res, err, pathname);
1829
1955
  }
1830
1956
  return;
1831
1957
  }
@@ -1835,28 +1961,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1835
1961
  try {
1836
1962
  await require_veo_video.handleVeoVideoCreate(req, res, await require_helpers.readBody(req), veoPredictMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
1837
1963
  } 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();
1964
+ routeError(req, res, err, pathname);
1860
1965
  }
1861
1966
  return;
1862
1967
  }
@@ -1865,28 +1970,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1865
1970
  try {
1866
1971
  await require_veo_video.handleVeoVideoStatus(req, res, veoOperationMatch[1], fixtures, journal, defaults, setCorsHeaders, veoVideoJobs);
1867
1972
  } 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();
1973
+ routeError(req, res, err, pathname);
1890
1974
  }
1891
1975
  return;
1892
1976
  }
@@ -1896,12 +1980,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1896
1980
  try {
1897
1981
  await require_images.handleImages(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders, "gemini", predictModel);
1898
1982
  } 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();
1983
+ routeError(req, res, err, pathname);
1905
1984
  }
1906
1985
  return;
1907
1986
  }
@@ -1909,17 +1988,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1909
1988
  try {
1910
1989
  await require_gemini_interactions.handleGeminiInteractions(req, res, await require_helpers.readBody(req), fixtures, journal, defaults, setCorsHeaders);
1911
1990
  } 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
- }
1991
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
1923
1992
  }
1924
1993
  return;
1925
1994
  }
@@ -1929,12 +1998,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1929
1998
  try {
1930
1999
  await require_gemini_embeddings.handleGeminiEmbedContent(req, res, await require_helpers.readBody(req), embedModel, fixtures, journal, defaults, setCorsHeaders);
1931
2000
  } 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();
2001
+ routeError(req, res, err, pathname);
1938
2002
  }
1939
2003
  return;
1940
2004
  }
@@ -1945,17 +2009,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1945
2009
  try {
1946
2010
  await require_gemini.handleGemini(req, res, await require_helpers.readBody(req), geminiModel, streaming, fixtures, journal, defaults, setCorsHeaders);
1947
2011
  } 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
- }
2012
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
1959
2013
  }
1960
2014
  return;
1961
2015
  }
@@ -1966,17 +2020,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1966
2020
  try {
1967
2021
  await require_gemini.handleGemini(req, res, await require_helpers.readBody(req), vertexModel, streaming, fixtures, journal, defaults, setCorsHeaders, "vertexai");
1968
2022
  } 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
- }
2023
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
1980
2024
  }
1981
2025
  return;
1982
2026
  }
@@ -1986,12 +2030,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
1986
2030
  try {
1987
2031
  await require_bedrock.handleBedrock(req, res, await require_helpers.readBody(req), bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
1988
2032
  } 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();
2033
+ routeError(req, res, err, pathname);
1995
2034
  }
1996
2035
  return;
1997
2036
  }
@@ -2001,12 +2040,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2001
2040
  try {
2002
2041
  await require_bedrock.handleBedrockStream(req, res, await require_helpers.readBody(req), bedrockModelId, fixtures, journal, defaults, setCorsHeaders);
2003
2042
  } 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();
2043
+ routeError(req, res, err, pathname);
2010
2044
  }
2011
2045
  return;
2012
2046
  }
@@ -2016,12 +2050,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2016
2050
  try {
2017
2051
  await require_bedrock_converse.handleConverse(req, res, await require_helpers.readBody(req), converseModelId, fixtures, journal, defaults, setCorsHeaders);
2018
2052
  } 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();
2053
+ routeError(req, res, err, pathname);
2025
2054
  }
2026
2055
  return;
2027
2056
  }
@@ -2031,12 +2060,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2031
2060
  try {
2032
2061
  await require_bedrock_converse.handleConverseStream(req, res, await require_helpers.readBody(req), converseStreamModelId, fixtures, journal, defaults, setCorsHeaders);
2033
2062
  } 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();
2063
+ routeError(req, res, err, pathname);
2040
2064
  }
2041
2065
  return;
2042
2066
  }
@@ -2044,12 +2068,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2044
2068
  try {
2045
2069
  await require_search.handleSearch(req, res, await require_helpers.readBody(req), serviceFixtures?.search ?? [], journal, defaults, setCorsHeaders);
2046
2070
  } 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();
2071
+ routeError(req, res, err, pathname, { service: "search" });
2053
2072
  }
2054
2073
  return;
2055
2074
  }
@@ -2057,12 +2076,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2057
2076
  try {
2058
2077
  await require_rerank.handleRerank(req, res, await require_helpers.readBody(req), serviceFixtures?.rerank ?? [], journal, defaults, setCorsHeaders);
2059
2078
  } 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();
2079
+ routeError(req, res, err, pathname, { service: "rerank" });
2066
2080
  }
2067
2081
  return;
2068
2082
  }
@@ -2070,12 +2084,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2070
2084
  try {
2071
2085
  await require_moderation.handleModeration(req, res, await require_helpers.readBody(req), serviceFixtures?.moderation ?? [], journal, defaults, setCorsHeaders);
2072
2086
  } 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();
2087
+ routeError(req, res, err, pathname, { service: "moderation" });
2079
2088
  }
2080
2089
  return;
2081
2090
  }
@@ -2084,12 +2093,57 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2084
2093
  try {
2085
2094
  await require_elevenlabs_audio.handleElevenLabsAudio(req, res, await require_helpers.readBody(req), fixtures, defaults, journal, "sound-generation");
2086
2095
  } 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"
2096
+ routeError(req, res, err, pathname);
2097
+ }
2098
+ return;
2099
+ }
2100
+ if (pathname === ELEVENLABS_VOICE_DESIGN_PATH && req.method === "POST") {
2101
+ setCorsHeaders(res);
2102
+ try {
2103
+ await require_elevenlabs_voice.handleElevenLabsVoiceDesign(req, res, await require_helpers.readBody(req), fixtures, defaults, journal);
2104
+ } catch (err) {
2105
+ routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2106
+ }
2107
+ return;
2108
+ }
2109
+ if (pathname === ELEVENLABS_VOICE_CREATE_PATH && req.method === "POST") {
2110
+ setCorsHeaders(res);
2111
+ try {
2112
+ await require_elevenlabs_voice.handleElevenLabsVoiceCreate(req, res, await require_helpers.readBody(req), fixtures, defaults, journal);
2113
+ } catch (err) {
2114
+ routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2115
+ }
2116
+ return;
2117
+ }
2118
+ const elevenLabsVoiceMatch = pathname.match(ELEVENLABS_VOICE_RE);
2119
+ if (elevenLabsVoiceMatch && (req.method === "GET" || req.method === "DELETE")) {
2120
+ setCorsHeaders(res);
2121
+ let voiceId;
2122
+ try {
2123
+ voiceId = decodeURIComponent(elevenLabsVoiceMatch[1]);
2124
+ } catch {
2125
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
2126
+ message: `Invalid voice id '${elevenLabsVoiceMatch[1]}': malformed percent-encoding`,
2127
+ type: "invalid_request_error"
2091
2128
  } }));
2092
- else if (!res.writableEnded) res.destroy();
2129
+ journal.add({
2130
+ method: req.method ?? "GET",
2131
+ path: req.url ?? pathname,
2132
+ headers: require_helpers.flattenHeaders(req.headers),
2133
+ service: "elevenlabs-voice",
2134
+ body: null,
2135
+ response: {
2136
+ status: 400,
2137
+ fixture: null
2138
+ }
2139
+ });
2140
+ return;
2141
+ }
2142
+ try {
2143
+ if (req.method === "GET") await require_elevenlabs_voice.handleElevenLabsVoiceGet(req, res, voiceId, fixtures, defaults, journal);
2144
+ else await require_elevenlabs_voice.handleElevenLabsVoiceDelete(req, res, voiceId, fixtures, defaults, journal);
2145
+ } catch (err) {
2146
+ routeError(req, res, err, pathname, { service: "elevenlabs-voice" });
2093
2147
  }
2094
2148
  return;
2095
2149
  }
@@ -2100,12 +2154,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2100
2154
  try {
2101
2155
  await require_elevenlabs_audio.handleElevenLabsTTS(req, res, await require_helpers.readBody(req), fixtures, defaults, journal, voiceId);
2102
2156
  } 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();
2157
+ routeError(req, res, err, pathname);
2109
2158
  }
2110
2159
  return;
2111
2160
  }
@@ -2116,42 +2165,38 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2116
2165
  try {
2117
2166
  await require_elevenlabs_audio.handleElevenLabsAudio(req, res, await require_helpers.readBody(req), fixtures, defaults, journal, musicSubType);
2118
2167
  } 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();
2168
+ routeError(req, res, err, pathname);
2125
2169
  }
2126
2170
  return;
2127
2171
  }
2128
2172
  let falBody;
2129
- if (FAL_PREFIX_RE.test(pathname) && req.headers["x-fal-target-host"]) {
2173
+ if (require_metrics.FAL_ROUTE_RE.test(pathname) && req.headers["x-fal-target-host"]) {
2130
2174
  setCorsHeaders(res);
2131
2175
  try {
2132
2176
  falBody = req.method === "POST" || req.method === "PUT" ? await require_helpers.readBody(req) : "";
2133
2177
  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;
2178
+ if (require_fal.falWillHandle(req, pathname)) {
2179
+ const chaosConfig = require_chaos.resolveChaosConfig(null, defaults.chaos, req.headers, defaults.logger, req.url);
2180
+ await require_chaos.awaitChaosLatency(null, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
2181
+ const goneAfterLatency = require_chaos.responseGoneReason(res);
2182
+ if (goneAfterLatency !== null) {
2183
+ defaults.logger.debug(`[chaos] ${req.method ?? "GET"} ${pathname}: ${require_chaos.describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
2184
+ return;
2185
+ }
2186
+ const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
2187
+ if (chaosAction) {
2188
+ require_chaos.applyChaosAction(chaosAction, res, null, journal, {
2189
+ method: req.method ?? "GET",
2190
+ path: pathname,
2191
+ headers: require_helpers.flattenHeaders(req.headers),
2192
+ body: null
2193
+ }, "internal", defaults.registry, defaults.logger);
2194
+ return;
2195
+ }
2146
2196
  }
2147
2197
  if (await require_fal.handleFal(req, res, raw, pathname, fixtures, defaults, journal) === "handled") return;
2148
2198
  } 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();
2199
+ routeError(req, res, err, pathname);
2155
2200
  return;
2156
2201
  }
2157
2202
  }
@@ -2160,12 +2205,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2160
2205
  try {
2161
2206
  await require_fal_audio.handleFalQueue(req, res, falBody ?? await require_helpers.readBody(req), pathname, fixtures, defaults, journal);
2162
2207
  } 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();
2208
+ routeError(req, res, err, pathname);
2169
2209
  }
2170
2210
  return;
2171
2211
  }
@@ -2173,27 +2213,26 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2173
2213
  setCorsHeaders(res);
2174
2214
  try {
2175
2215
  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);
2216
+ const chaosConfig = require_chaos.resolveChaosConfig(null, defaults.chaos, req.headers, defaults.logger, req.url);
2217
+ await require_chaos.awaitChaosLatency(null, defaults.chaos, req.headers, defaults.logger, req.url, res, chaosConfig);
2218
+ const goneAfterLatency = require_chaos.responseGoneReason(res);
2219
+ if (goneAfterLatency !== null) {
2220
+ defaults.logger.debug(`[chaos] ${req.method ?? "GET"} ${pathname}: ${require_chaos.describeUnwritableReason(goneAfterLatency)} after the latency delay — not served, not journalled`);
2221
+ return;
2222
+ }
2223
+ const chaosAction = require_chaos.evaluateChaos(null, defaults.chaos, req.headers, defaults.logger, req.url, chaosConfig);
2177
2224
  if (chaosAction) {
2178
2225
  require_chaos.applyChaosAction(chaosAction, res, null, journal, {
2179
2226
  method: req.method ?? "GET",
2180
2227
  path: pathname,
2181
2228
  headers: require_helpers.flattenHeaders(req.headers),
2182
- body: {
2183
- model: "",
2184
- messages: []
2185
- }
2186
- }, "fixture", defaults.registry);
2229
+ body: null
2230
+ }, "internal", defaults.registry, defaults.logger);
2187
2231
  return;
2188
2232
  }
2189
2233
  await require_fal_audio.handleFalQueue(req, res, raw, pathname, fixtures, defaults, journal);
2190
2234
  } 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();
2235
+ routeError(req, res, err, pathname);
2197
2236
  }
2198
2237
  return;
2199
2238
  }
@@ -2202,12 +2241,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2202
2241
  try {
2203
2242
  await require_fal_audio.handleFalQueue(req, res, falBody ?? await require_helpers.readBody(req), pathname, fixtures, defaults, journal);
2204
2243
  } 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();
2244
+ routeError(req, res, err, pathname);
2211
2245
  }
2212
2246
  return;
2213
2247
  }
@@ -2223,23 +2257,25 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2223
2257
  try {
2224
2258
  await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider, isOpenRouter);
2225
2259
  } 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
- }
2260
+ routeError(req, res, err, pathname, {
2261
+ envelope: (m, status) => isOpenRouter ? require_openrouter_chat.serializeOpenRouterError(status, m) : JSON.stringify({ error: {
2262
+ message: m,
2263
+ type: errorTypeForStatus(status)
2264
+ } }),
2265
+ streamEvent: (m, status) => `data: ${isOpenRouter ? require_openrouter_chat.serializeOpenRouterError(status, m) : JSON.stringify({ error: {
2266
+ message: m,
2267
+ type: errorTypeForStatus(status)
2268
+ } })}\n\n`
2269
+ });
2240
2270
  }
2241
2271
  }
2242
2272
  const activeConnections = /* @__PURE__ */ new Set();
2273
+ const liveLimits = require_live_fixture.normalizeLiveOptions(options?.live);
2274
+ const liveSessions = /* @__PURE__ */ new Map();
2275
+ const closeLiveSessions = (testId) => {
2276
+ for (const session of [...liveSessions.values()]) if (testId === void 0 || session.testId === testId) session.dispose();
2277
+ };
2278
+ liveClosers.set(defaults, closeLiveSessions);
2243
2279
  server.on("upgrade", (req, socket, head) => {
2244
2280
  handleUpgradeRequest(req, socket, head).catch((err) => {
2245
2281
  const msg = err instanceof Error ? err.message : "Internal error";
@@ -2302,17 +2338,38 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2302
2338
  }
2303
2339
  }
2304
2340
  if (!pathname.match(AZURE_DEPLOYMENT_RE)) pathname = normalizeCompatPath(pathname, logger);
2305
- if (pathname !== RESPONSES_PATH && pathname !== REALTIME_PATH && pathname !== GEMINI_LIVE_PATH) {
2341
+ if (pathname !== RESPONSES_PATH && pathname !== REALTIME_PATH && pathname !== GEMINI_LIVE_PATH && pathname !== LIVE_PATH) {
2306
2342
  socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
2307
2343
  socket.destroy();
2308
2344
  return;
2309
2345
  }
2346
+ let liveId;
2347
+ if (pathname === LIVE_PATH) {
2348
+ if (req.method !== "GET" || parsedUrl.searchParams.has("model")) {
2349
+ socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
2350
+ return;
2351
+ }
2352
+ if (liveSessions.size >= liveLimits.maxSessions) {
2353
+ socket.end("HTTP/1.1 429 Too Many Requests\r\nConnection: close\r\n\r\n");
2354
+ return;
2355
+ }
2356
+ liveId = Symbol("live-session");
2357
+ const reservedId = liveId;
2358
+ liveSessions.set(reservedId, {
2359
+ testId: require_helpers.getTestId(req),
2360
+ dispose: () => {
2361
+ socket.destroy();
2362
+ liveSessions.delete(reservedId);
2363
+ }
2364
+ });
2365
+ }
2310
2366
  if (head.length > 0) socket.unshift(head);
2311
2367
  let ws;
2312
2368
  try {
2313
- ws = require_ws_framing.upgradeToWebSocket(req, socket);
2369
+ ws = require_ws_framing.upgradeToWebSocket(req, socket, pathname === LIVE_PATH ? liveLimits : void 0);
2314
2370
  } catch (err) {
2315
2371
  const msg = err instanceof Error ? err.message : "WebSocket upgrade failed";
2372
+ if (liveId !== void 0) liveSessions.delete(liveId);
2316
2373
  logger.error(`WebSocket upgrade error: ${msg}`);
2317
2374
  if (!socket.destroyed) socket.destroy();
2318
2375
  return;
@@ -2326,7 +2383,24 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2326
2383
  activeConnections.delete(ws);
2327
2384
  });
2328
2385
  const wsTestId = require_helpers.getTestId(req);
2329
- if (pathname === RESPONSES_PATH) require_ws_responses.handleWebSocketResponses(ws, fixtures, journal, {
2386
+ if (pathname === LIVE_PATH && liveId !== void 0) {
2387
+ const sessionId = liveId;
2388
+ const dispose = require_ws_live.handleLiveSession(ws, fixtures, journal, {
2389
+ defaults,
2390
+ limits: liveLimits,
2391
+ localApiKeys: resolvedAuth.publicConfig?.apiKeys,
2392
+ testId: wsTestId,
2393
+ headers: req.headers,
2394
+ onDispose: () => {
2395
+ liveSessions.delete(sessionId);
2396
+ activeConnections.delete(ws);
2397
+ }
2398
+ });
2399
+ liveSessions.set(sessionId, {
2400
+ testId: wsTestId,
2401
+ dispose
2402
+ });
2403
+ } else if (pathname === RESPONSES_PATH) require_ws_responses.handleWebSocketResponses(ws, fixtures, journal, {
2330
2404
  ...defaults,
2331
2405
  model: "gpt-4",
2332
2406
  testId: wsTestId,
@@ -2351,6 +2425,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2351
2425
  }
2352
2426
  const originalClose = server.close.bind(server);
2353
2427
  server.close = function(callback) {
2428
+ closeLiveSessions();
2354
2429
  for (const ws of activeConnections) ws.close(1001, "Server shutting down");
2355
2430
  activeConnections.clear();
2356
2431
  videoStates.clear();
@@ -2374,6 +2449,7 @@ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mou
2374
2449
  for (const { path: mountPath, handler } of mounts) if (handler.setBaseUrl) handler.setBaseUrl(url + mountPath);
2375
2450
  }
2376
2451
  resolve({
2452
+ closeLiveSessions,
2377
2453
  server,
2378
2454
  journal,
2379
2455
  url,