@copilotkit/aimock 1.45.1 → 1.47.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 (492) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +25 -1
  4. package/README.md +11 -2
  5. package/dist/agui-mock.cjs +25 -25
  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 +25 -25
  10. package/dist/agui-mock.js.map +1 -1
  11. package/dist/agui-types.d.cts.map +1 -1
  12. package/dist/aimock-cli.cjs +15 -1
  13. package/dist/aimock-cli.cjs.map +1 -1
  14. package/dist/aimock-cli.d.cts.map +1 -1
  15. package/dist/aimock-cli.d.ts.map +1 -1
  16. package/dist/aimock-cli.js +16 -2
  17. package/dist/aimock-cli.js.map +1 -1
  18. package/dist/api-key-auth.cjs +10 -9
  19. package/dist/api-key-auth.cjs.map +1 -1
  20. package/dist/api-key-auth.d.cts.map +1 -1
  21. package/dist/api-key-auth.d.ts.map +1 -1
  22. package/dist/api-key-auth.js +11 -10
  23. package/dist/api-key-auth.js.map +1 -1
  24. package/dist/aws-event-stream.cjs +20 -23
  25. package/dist/aws-event-stream.cjs.map +1 -1
  26. package/dist/aws-event-stream.d.cts +0 -1
  27. package/dist/aws-event-stream.d.ts +0 -1
  28. package/dist/aws-event-stream.js +20 -24
  29. package/dist/aws-event-stream.js.map +1 -1
  30. package/dist/batches.cjs +2 -4
  31. package/dist/batches.cjs.map +1 -1
  32. package/dist/batches.d.cts.map +1 -1
  33. package/dist/batches.d.ts.map +1 -1
  34. package/dist/batches.js +2 -4
  35. package/dist/batches.js.map +1 -1
  36. package/dist/bedrock-converse.cjs +139 -161
  37. package/dist/bedrock-converse.cjs.map +1 -1
  38. package/dist/bedrock-converse.d.cts.map +1 -1
  39. package/dist/bedrock-converse.d.ts.map +1 -1
  40. package/dist/bedrock-converse.js +137 -159
  41. package/dist/bedrock-converse.js.map +1 -1
  42. package/dist/bedrock.cjs +175 -216
  43. package/dist/bedrock.cjs.map +1 -1
  44. package/dist/bedrock.d.cts.map +1 -1
  45. package/dist/bedrock.d.ts.map +1 -1
  46. package/dist/bedrock.js +172 -213
  47. package/dist/bedrock.js.map +1 -1
  48. package/dist/byteplus-video.cjs +11 -10
  49. package/dist/byteplus-video.cjs.map +1 -1
  50. package/dist/byteplus-video.d.cts.map +1 -1
  51. package/dist/byteplus-video.d.ts.map +1 -1
  52. package/dist/byteplus-video.js +5 -4
  53. package/dist/byteplus-video.js.map +1 -1
  54. package/dist/chaos.cjs +5 -4
  55. package/dist/chaos.cjs.map +1 -1
  56. package/dist/chaos.d.cts.map +1 -1
  57. package/dist/chaos.d.ts.map +1 -1
  58. package/dist/chaos.js +5 -4
  59. package/dist/chaos.js.map +1 -1
  60. package/dist/cli.cjs +15 -0
  61. package/dist/cli.cjs.map +1 -1
  62. package/dist/cli.js +15 -0
  63. package/dist/cli.js.map +1 -1
  64. package/dist/cohere.cjs +107 -180
  65. package/dist/cohere.cjs.map +1 -1
  66. package/dist/cohere.d.cts.map +1 -1
  67. package/dist/cohere.d.ts.map +1 -1
  68. package/dist/cohere.js +106 -179
  69. package/dist/cohere.js.map +1 -1
  70. package/dist/config-loader.cjs +29 -1
  71. package/dist/config-loader.cjs.map +1 -1
  72. package/dist/config-loader.d.cts +7 -1
  73. package/dist/config-loader.d.cts.map +1 -1
  74. package/dist/config-loader.d.ts +7 -1
  75. package/dist/config-loader.d.ts.map +1 -1
  76. package/dist/config-loader.js +29 -2
  77. package/dist/config-loader.js.map +1 -1
  78. package/dist/count-tokens.cjs +146 -0
  79. package/dist/count-tokens.cjs.map +1 -0
  80. package/dist/count-tokens.js +146 -0
  81. package/dist/count-tokens.js.map +1 -0
  82. package/dist/elevenlabs-audio.cjs +36 -34
  83. package/dist/elevenlabs-audio.cjs.map +1 -1
  84. package/dist/elevenlabs-audio.d.cts.map +1 -1
  85. package/dist/elevenlabs-audio.d.ts.map +1 -1
  86. package/dist/elevenlabs-audio.js +33 -31
  87. package/dist/elevenlabs-audio.js.map +1 -1
  88. package/dist/elevenlabs-voice.cjs +38 -37
  89. package/dist/elevenlabs-voice.cjs.map +1 -1
  90. package/dist/elevenlabs-voice.d.cts.map +1 -1
  91. package/dist/elevenlabs-voice.d.ts.map +1 -1
  92. package/dist/elevenlabs-voice.js +33 -32
  93. package/dist/elevenlabs-voice.js.map +1 -1
  94. package/dist/embedding-request.cjs +16 -0
  95. package/dist/embedding-request.cjs.map +1 -0
  96. package/dist/embedding-request.js +14 -0
  97. package/dist/embedding-request.js.map +1 -0
  98. package/dist/embeddings.cjs +30 -20
  99. package/dist/embeddings.cjs.map +1 -1
  100. package/dist/embeddings.d.cts.map +1 -1
  101. package/dist/embeddings.d.ts.map +1 -1
  102. package/dist/embeddings.js +30 -20
  103. package/dist/embeddings.js.map +1 -1
  104. package/dist/fal-audio.cjs +45 -43
  105. package/dist/fal-audio.cjs.map +1 -1
  106. package/dist/fal-audio.d.cts.map +1 -1
  107. package/dist/fal-audio.d.ts.map +1 -1
  108. package/dist/fal-audio.js +42 -40
  109. package/dist/fal-audio.js.map +1 -1
  110. package/dist/fal.cjs +17 -15
  111. package/dist/fal.cjs.map +1 -1
  112. package/dist/fal.js +16 -14
  113. package/dist/fal.js.map +1 -1
  114. package/dist/files.cjs +2 -4
  115. package/dist/files.cjs.map +1 -1
  116. package/dist/files.d.cts.map +1 -1
  117. package/dist/files.d.ts.map +1 -1
  118. package/dist/files.js +2 -4
  119. package/dist/files.js.map +1 -1
  120. package/dist/fine-tuning.cjs +2 -4
  121. package/dist/fine-tuning.cjs.map +1 -1
  122. package/dist/fine-tuning.d.cts.map +1 -1
  123. package/dist/fine-tuning.d.ts.map +1 -1
  124. package/dist/fine-tuning.js +2 -4
  125. package/dist/fine-tuning.js.map +1 -1
  126. package/dist/fixture-loader.cjs +140 -44
  127. package/dist/fixture-loader.cjs.map +1 -1
  128. package/dist/fixture-loader.d.cts.map +1 -1
  129. package/dist/fixture-loader.d.ts.map +1 -1
  130. package/dist/fixture-loader.js +141 -46
  131. package/dist/fixture-loader.js.map +1 -1
  132. package/dist/gemini-embeddings.cjs +26 -24
  133. package/dist/gemini-embeddings.cjs.map +1 -1
  134. package/dist/gemini-embeddings.js +26 -24
  135. package/dist/gemini-embeddings.js.map +1 -1
  136. package/dist/gemini-interactions.cjs +113 -156
  137. package/dist/gemini-interactions.cjs.map +1 -1
  138. package/dist/gemini-interactions.d.cts +0 -1
  139. package/dist/gemini-interactions.d.cts.map +1 -1
  140. package/dist/gemini-interactions.d.ts +0 -1
  141. package/dist/gemini-interactions.d.ts.map +1 -1
  142. package/dist/gemini-interactions.js +113 -156
  143. package/dist/gemini-interactions.js.map +1 -1
  144. package/dist/gemini.cjs +295 -218
  145. package/dist/gemini.cjs.map +1 -1
  146. package/dist/gemini.d.cts.map +1 -1
  147. package/dist/gemini.d.ts.map +1 -1
  148. package/dist/gemini.js +295 -219
  149. package/dist/gemini.js.map +1 -1
  150. package/dist/grok-video.cjs +14 -12
  151. package/dist/grok-video.cjs.map +1 -1
  152. package/dist/grok-video.d.cts.map +1 -1
  153. package/dist/grok-video.d.ts.map +1 -1
  154. package/dist/grok-video.js +8 -6
  155. package/dist/grok-video.js.map +1 -1
  156. package/dist/helpers.cjs +411 -75
  157. package/dist/helpers.cjs.map +1 -1
  158. package/dist/helpers.d.cts +51 -1
  159. package/dist/helpers.d.cts.map +1 -1
  160. package/dist/helpers.d.ts +51 -1
  161. package/dist/helpers.d.ts.map +1 -1
  162. package/dist/helpers.js +402 -74
  163. package/dist/helpers.js.map +1 -1
  164. package/dist/images.cjs +30 -28
  165. package/dist/images.cjs.map +1 -1
  166. package/dist/images.d.cts.map +1 -1
  167. package/dist/images.d.ts.map +1 -1
  168. package/dist/images.js +27 -25
  169. package/dist/images.js.map +1 -1
  170. package/dist/index.cjs +4 -2
  171. package/dist/index.d.cts +4 -4
  172. package/dist/index.d.ts +4 -4
  173. package/dist/index.js +4 -4
  174. package/dist/journal.cjs +3 -1
  175. package/dist/journal.cjs.map +1 -1
  176. package/dist/journal.d.cts.map +1 -1
  177. package/dist/journal.d.ts.map +1 -1
  178. package/dist/journal.js +2 -1
  179. package/dist/journal.js.map +1 -1
  180. package/dist/llmock.cjs +3 -0
  181. package/dist/llmock.cjs.map +1 -1
  182. package/dist/llmock.d.cts.map +1 -1
  183. package/dist/llmock.d.ts.map +1 -1
  184. package/dist/llmock.js +3 -0
  185. package/dist/llmock.js.map +1 -1
  186. package/dist/mcp-fakes.d.cts.map +1 -1
  187. package/dist/mcp-handler.cjs +7 -3
  188. package/dist/mcp-handler.cjs.map +1 -1
  189. package/dist/mcp-handler.js +7 -3
  190. package/dist/mcp-handler.js.map +1 -1
  191. package/dist/mcp-recorder.cjs +2 -1
  192. package/dist/mcp-recorder.cjs.map +1 -1
  193. package/dist/mcp-recorder.js +3 -2
  194. package/dist/mcp-recorder.js.map +1 -1
  195. package/dist/messages.cjs +235 -202
  196. package/dist/messages.cjs.map +1 -1
  197. package/dist/messages.d.cts.map +1 -1
  198. package/dist/messages.d.ts.map +1 -1
  199. package/dist/messages.js +234 -202
  200. package/dist/messages.js.map +1 -1
  201. package/dist/metrics.cjs +5 -2
  202. package/dist/metrics.cjs.map +1 -1
  203. package/dist/metrics.d.cts.map +1 -1
  204. package/dist/metrics.d.ts.map +1 -1
  205. package/dist/metrics.js +6 -3
  206. package/dist/metrics.js.map +1 -1
  207. package/dist/misbehavior.cjs +62 -3
  208. package/dist/misbehavior.cjs.map +1 -1
  209. package/dist/misbehavior.d.cts.map +1 -1
  210. package/dist/misbehavior.d.ts.map +1 -1
  211. package/dist/misbehavior.js +63 -4
  212. package/dist/misbehavior.js.map +1 -1
  213. package/dist/model-utils.cjs +1 -0
  214. package/dist/model-utils.cjs.map +1 -1
  215. package/dist/model-utils.js +1 -1
  216. package/dist/model-utils.js.map +1 -1
  217. package/dist/moderation.cjs +15 -12
  218. package/dist/moderation.cjs.map +1 -1
  219. package/dist/moderation.js +14 -11
  220. package/dist/moderation.js.map +1 -1
  221. package/dist/ndjson-writer.cjs +21 -23
  222. package/dist/ndjson-writer.cjs.map +1 -1
  223. package/dist/ndjson-writer.d.cts +0 -1
  224. package/dist/ndjson-writer.d.cts.map +1 -1
  225. package/dist/ndjson-writer.d.ts +0 -1
  226. package/dist/ndjson-writer.d.ts.map +1 -1
  227. package/dist/ndjson-writer.js +21 -24
  228. package/dist/ndjson-writer.js.map +1 -1
  229. package/dist/ollama.cjs +276 -210
  230. package/dist/ollama.cjs.map +1 -1
  231. package/dist/ollama.d.cts.map +1 -1
  232. package/dist/ollama.d.ts.map +1 -1
  233. package/dist/ollama.js +273 -207
  234. package/dist/ollama.js.map +1 -1
  235. package/dist/openapi.cjs +323 -3
  236. package/dist/openapi.cjs.map +1 -1
  237. package/dist/openapi.d.cts.map +1 -1
  238. package/dist/openapi.d.ts.map +1 -1
  239. package/dist/openapi.js +324 -4
  240. package/dist/openapi.js.map +1 -1
  241. package/dist/openrouter-chat.cjs +11 -155
  242. package/dist/openrouter-chat.cjs.map +1 -1
  243. package/dist/openrouter-chat.js +11 -155
  244. package/dist/openrouter-chat.js.map +1 -1
  245. package/dist/openrouter-video.cjs +51 -74
  246. package/dist/openrouter-video.cjs.map +1 -1
  247. package/dist/openrouter-video.d.cts.map +1 -1
  248. package/dist/openrouter-video.d.ts.map +1 -1
  249. package/dist/openrouter-video.js +37 -60
  250. package/dist/openrouter-video.js.map +1 -1
  251. package/dist/raw-validation.cjs +177 -0
  252. package/dist/raw-validation.cjs.map +1 -0
  253. package/dist/raw-validation.js +176 -0
  254. package/dist/raw-validation.js.map +1 -0
  255. package/dist/recorder.cjs +176 -95
  256. package/dist/recorder.cjs.map +1 -1
  257. package/dist/recorder.d.cts.map +1 -1
  258. package/dist/recorder.d.ts.map +1 -1
  259. package/dist/recorder.js +176 -95
  260. package/dist/recorder.js.map +1 -1
  261. package/dist/rerank.cjs +13 -11
  262. package/dist/rerank.cjs.map +1 -1
  263. package/dist/rerank.js +12 -10
  264. package/dist/rerank.js.map +1 -1
  265. package/dist/response/catalog/anthropic-page.cjs +39 -0
  266. package/dist/response/catalog/anthropic-page.cjs.map +1 -0
  267. package/dist/response/catalog/anthropic-page.js +38 -0
  268. package/dist/response/catalog/anthropic-page.js.map +1 -0
  269. package/dist/response/catalog/collect.cjs +61 -0
  270. package/dist/response/catalog/collect.cjs.map +1 -0
  271. package/dist/response/catalog/collect.js +59 -0
  272. package/dist/response/catalog/collect.js.map +1 -0
  273. package/dist/response/catalog/gemini-page.cjs +25 -0
  274. package/dist/response/catalog/gemini-page.cjs.map +1 -0
  275. package/dist/response/catalog/gemini-page.js +24 -0
  276. package/dist/response/catalog/gemini-page.js.map +1 -0
  277. package/dist/response/catalog/render.cjs +182 -0
  278. package/dist/response/catalog/render.cjs.map +1 -0
  279. package/dist/response/catalog/render.js +171 -0
  280. package/dist/response/catalog/render.js.map +1 -0
  281. package/dist/response/deliver.cjs +144 -0
  282. package/dist/response/deliver.cjs.map +1 -0
  283. package/dist/response/deliver.js +143 -0
  284. package/dist/response/deliver.js.map +1 -0
  285. package/dist/response/errors.cjs +225 -0
  286. package/dist/response/errors.cjs.map +1 -0
  287. package/dist/response/errors.js +224 -0
  288. package/dist/response/errors.js.map +1 -0
  289. package/dist/response/finish.cjs +39 -0
  290. package/dist/response/finish.cjs.map +1 -0
  291. package/dist/response/finish.js +35 -0
  292. package/dist/response/finish.js.map +1 -0
  293. package/dist/response/frame-loop.cjs +102 -0
  294. package/dist/response/frame-loop.cjs.map +1 -0
  295. package/dist/response/frame-loop.js +100 -0
  296. package/dist/response/frame-loop.js.map +1 -0
  297. package/dist/response/lifecycle.cjs +223 -0
  298. package/dist/response/lifecycle.cjs.map +1 -0
  299. package/dist/response/lifecycle.js +211 -0
  300. package/dist/response/lifecycle.js.map +1 -0
  301. package/dist/response/policy.cjs +126 -0
  302. package/dist/response/policy.cjs.map +1 -0
  303. package/dist/response/policy.js +120 -0
  304. package/dist/response/policy.js.map +1 -0
  305. package/dist/response/profiles.cjs +69 -0
  306. package/dist/response/profiles.cjs.map +1 -0
  307. package/dist/response/profiles.js +65 -0
  308. package/dist/response/profiles.js.map +1 -0
  309. package/dist/response/proxy.cjs +71 -0
  310. package/dist/response/proxy.cjs.map +1 -0
  311. package/dist/response/proxy.js +70 -0
  312. package/dist/response/proxy.js.map +1 -0
  313. package/dist/response/raw.cjs +228 -0
  314. package/dist/response/raw.cjs.map +1 -0
  315. package/dist/response/raw.js +227 -0
  316. package/dist/response/raw.js.map +1 -0
  317. package/dist/response/timing.cjs +157 -0
  318. package/dist/response/timing.cjs.map +1 -0
  319. package/dist/response/timing.js +150 -0
  320. package/dist/response/timing.js.map +1 -0
  321. package/dist/response/usage/codecs/anthropic.cjs +52 -0
  322. package/dist/response/usage/codecs/anthropic.cjs.map +1 -0
  323. package/dist/response/usage/codecs/anthropic.js +50 -0
  324. package/dist/response/usage/codecs/anthropic.js.map +1 -0
  325. package/dist/response/usage/codecs/cohere.cjs +36 -0
  326. package/dist/response/usage/codecs/cohere.cjs.map +1 -0
  327. package/dist/response/usage/codecs/cohere.js +35 -0
  328. package/dist/response/usage/codecs/cohere.js.map +1 -0
  329. package/dist/response/usage/codecs/converse.cjs +16 -0
  330. package/dist/response/usage/codecs/converse.cjs.map +1 -0
  331. package/dist/response/usage/codecs/converse.js +15 -0
  332. package/dist/response/usage/codecs/converse.js.map +1 -0
  333. package/dist/response/usage/codecs/gemini.cjs +31 -0
  334. package/dist/response/usage/codecs/gemini.cjs.map +1 -0
  335. package/dist/response/usage/codecs/gemini.js +31 -0
  336. package/dist/response/usage/codecs/gemini.js.map +1 -0
  337. package/dist/response/usage/codecs/interactions.cjs +22 -0
  338. package/dist/response/usage/codecs/interactions.cjs.map +1 -0
  339. package/dist/response/usage/codecs/interactions.js +21 -0
  340. package/dist/response/usage/codecs/interactions.js.map +1 -0
  341. package/dist/response/usage/codecs/ollama.cjs +17 -0
  342. package/dist/response/usage/codecs/ollama.cjs.map +1 -0
  343. package/dist/response/usage/codecs/ollama.js +17 -0
  344. package/dist/response/usage/codecs/ollama.js.map +1 -0
  345. package/dist/response/usage/codecs/openai.cjs +52 -0
  346. package/dist/response/usage/codecs/openai.cjs.map +1 -0
  347. package/dist/response/usage/codecs/openai.js +50 -0
  348. package/dist/response/usage/codecs/openai.js.map +1 -0
  349. package/dist/response/usage/codecs/openrouter.cjs +68 -0
  350. package/dist/response/usage/codecs/openrouter.cjs.map +1 -0
  351. package/dist/response/usage/codecs/openrouter.js +67 -0
  352. package/dist/response/usage/codecs/openrouter.js.map +1 -0
  353. package/dist/response/usage/codecs/realtime.cjs +19 -0
  354. package/dist/response/usage/codecs/realtime.cjs.map +1 -0
  355. package/dist/response/usage/codecs/realtime.js +18 -0
  356. package/dist/response/usage/codecs/realtime.js.map +1 -0
  357. package/dist/response/usage/codecs/responses.cjs +21 -0
  358. package/dist/response/usage/codecs/responses.cjs.map +1 -0
  359. package/dist/response/usage/codecs/responses.js +20 -0
  360. package/dist/response/usage/codecs/responses.js.map +1 -0
  361. package/dist/response/usage/estimate.cjs +23 -0
  362. package/dist/response/usage/estimate.cjs.map +1 -0
  363. package/dist/response/usage/estimate.js +21 -0
  364. package/dist/response/usage/estimate.js.map +1 -0
  365. package/dist/response/usage/fields.cjs +72 -0
  366. package/dist/response/usage/fields.cjs.map +1 -0
  367. package/dist/response/usage/fields.js +70 -0
  368. package/dist/response/usage/fields.js.map +1 -0
  369. package/dist/response/usage/recording.cjs +52 -0
  370. package/dist/response/usage/recording.cjs.map +1 -0
  371. package/dist/response/usage/recording.js +52 -0
  372. package/dist/response/usage/recording.js.map +1 -0
  373. package/dist/responses.cjs +304 -196
  374. package/dist/responses.cjs.map +1 -1
  375. package/dist/responses.d.cts.map +1 -1
  376. package/dist/responses.d.ts.map +1 -1
  377. package/dist/responses.js +303 -195
  378. package/dist/responses.js.map +1 -1
  379. package/dist/route-registry.cjs +45 -0
  380. package/dist/route-registry.cjs.map +1 -1
  381. package/dist/route-registry.d.cts.map +1 -1
  382. package/dist/route-registry.d.ts.map +1 -1
  383. package/dist/route-registry.js +41 -1
  384. package/dist/route-registry.js.map +1 -1
  385. package/dist/router.cjs +24 -4
  386. package/dist/router.cjs.map +1 -1
  387. package/dist/router.d.cts +7 -1
  388. package/dist/router.d.cts.map +1 -1
  389. package/dist/router.d.ts +7 -1
  390. package/dist/router.d.ts.map +1 -1
  391. package/dist/router.js +24 -5
  392. package/dist/router.js.map +1 -1
  393. package/dist/search.cjs +13 -11
  394. package/dist/search.cjs.map +1 -1
  395. package/dist/search.js +12 -10
  396. package/dist/search.js.map +1 -1
  397. package/dist/server.cjs +293 -248
  398. package/dist/server.cjs.map +1 -1
  399. package/dist/server.d.cts.map +1 -1
  400. package/dist/server.d.ts.map +1 -1
  401. package/dist/server.js +294 -249
  402. package/dist/server.js.map +1 -1
  403. package/dist/speech.cjs +20 -18
  404. package/dist/speech.cjs.map +1 -1
  405. package/dist/speech.d.cts.map +1 -1
  406. package/dist/speech.d.ts.map +1 -1
  407. package/dist/speech.js +19 -17
  408. package/dist/speech.js.map +1 -1
  409. package/dist/sse-writer.cjs +56 -30
  410. package/dist/sse-writer.cjs.map +1 -1
  411. package/dist/sse-writer.d.cts +0 -1
  412. package/dist/sse-writer.d.cts.map +1 -1
  413. package/dist/sse-writer.d.ts +0 -1
  414. package/dist/sse-writer.d.ts.map +1 -1
  415. package/dist/sse-writer.js +55 -31
  416. package/dist/sse-writer.js.map +1 -1
  417. package/dist/stream-collapse.cjs +118 -11
  418. package/dist/stream-collapse.cjs.map +1 -1
  419. package/dist/stream-collapse.d.cts +10 -9
  420. package/dist/stream-collapse.d.cts.map +1 -1
  421. package/dist/stream-collapse.d.ts +10 -9
  422. package/dist/stream-collapse.d.ts.map +1 -1
  423. package/dist/stream-collapse.js +118 -11
  424. package/dist/stream-collapse.js.map +1 -1
  425. package/dist/transcription.cjs +31 -43
  426. package/dist/transcription.cjs.map +1 -1
  427. package/dist/transcription.d.cts.map +1 -1
  428. package/dist/transcription.d.ts.map +1 -1
  429. package/dist/transcription.js +31 -43
  430. package/dist/transcription.js.map +1 -1
  431. package/dist/types.cjs.map +1 -1
  432. package/dist/types.d.cts +231 -9
  433. package/dist/types.d.cts.map +1 -1
  434. package/dist/types.d.ts +231 -9
  435. package/dist/types.d.ts.map +1 -1
  436. package/dist/types.js.map +1 -1
  437. package/dist/vector-mock.cjs +3 -8
  438. package/dist/vector-mock.cjs.map +1 -1
  439. package/dist/vector-mock.d.cts.map +1 -1
  440. package/dist/vector-mock.d.ts.map +1 -1
  441. package/dist/vector-mock.js +3 -8
  442. package/dist/vector-mock.js.map +1 -1
  443. package/dist/vector-stores.cjs +2 -4
  444. package/dist/vector-stores.cjs.map +1 -1
  445. package/dist/vector-stores.d.cts.map +1 -1
  446. package/dist/vector-stores.d.ts.map +1 -1
  447. package/dist/vector-stores.js +2 -4
  448. package/dist/vector-stores.js.map +1 -1
  449. package/dist/veo-video.cjs +16 -14
  450. package/dist/veo-video.cjs.map +1 -1
  451. package/dist/veo-video.js +10 -8
  452. package/dist/veo-video.js.map +1 -1
  453. package/dist/video.cjs +21 -19
  454. package/dist/video.cjs.map +1 -1
  455. package/dist/video.d.cts.map +1 -1
  456. package/dist/video.d.ts.map +1 -1
  457. package/dist/video.js +20 -18
  458. package/dist/video.js.map +1 -1
  459. package/dist/ws-framing.cjs +12 -6
  460. package/dist/ws-framing.cjs.map +1 -1
  461. package/dist/ws-framing.d.cts +2 -0
  462. package/dist/ws-framing.d.cts.map +1 -1
  463. package/dist/ws-framing.d.ts +2 -0
  464. package/dist/ws-framing.d.ts.map +1 -1
  465. package/dist/ws-framing.js +12 -6
  466. package/dist/ws-framing.js.map +1 -1
  467. package/dist/ws-gemini-live.cjs +131 -96
  468. package/dist/ws-gemini-live.cjs.map +1 -1
  469. package/dist/ws-gemini-live.d.cts +0 -1
  470. package/dist/ws-gemini-live.d.cts.map +1 -1
  471. package/dist/ws-gemini-live.d.ts +0 -1
  472. package/dist/ws-gemini-live.d.ts.map +1 -1
  473. package/dist/ws-gemini-live.js +130 -95
  474. package/dist/ws-gemini-live.js.map +1 -1
  475. package/dist/ws-live.cjs +6 -5
  476. package/dist/ws-live.cjs.map +1 -1
  477. package/dist/ws-live.js +5 -4
  478. package/dist/ws-live.js.map +1 -1
  479. package/dist/ws-realtime.cjs +108 -124
  480. package/dist/ws-realtime.cjs.map +1 -1
  481. package/dist/ws-realtime.d.cts.map +1 -1
  482. package/dist/ws-realtime.d.ts.map +1 -1
  483. package/dist/ws-realtime.js +105 -121
  484. package/dist/ws-realtime.js.map +1 -1
  485. package/dist/ws-responses.cjs +80 -59
  486. package/dist/ws-responses.cjs.map +1 -1
  487. package/dist/ws-responses.d.cts.map +1 -1
  488. package/dist/ws-responses.d.ts.map +1 -1
  489. package/dist/ws-responses.js +79 -58
  490. package/dist/ws-responses.js.map +1 -1
  491. package/package.json +4 -1
  492. package/skills/write-fixtures/SKILL.md +57 -16
package/dist/server.js CHANGED
@@ -1,9 +1,12 @@
1
+ import { isAnthropicModelRequest, missingRequiredHeader } from "./response/profiles.js";
2
+ import { renderError } from "./response/errors.js";
1
3
  import { DEFAULT_TEST_ID } from "./constants.js";
2
4
  import { build, msg, quote } from "./message-text.js";
3
5
  import { markAuthenticatedRequest, resolveInboundAuth, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection } from "./api-key-auth.js";
4
- import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildOpenAIContentFilterChunks, buildOpenAIContentFilterCompletion, buildOpenAIReasoningChunks, buildOpenAIReasoningCompletion, buildOpenAIRefusalChunks, buildOpenAIRefusalCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, createServerFixtureList, describeMatch, extractOverrides, fixtureToolCallErrorCode, flattenHeaders, getContext, getTestId, googleFixtureToolCallErrorDetails, isAudioResponse, isCombinedFixtureResponse, isEmbeddingResponse, isErrorResponse, isExtendedResponsesToolsList, isFixtureToolCallError, isImageResponse, isJSONResponse, isJsonObject, isResponseFactory, isServerOwnedFixtures, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, prepareOpenAIChatMisbehavior, readBody, readBodyBufferBounded, requireEmittedFunctionToolCalls, requireServedFunctionToolCalls, resolveFixtureBlockOutcome, resolveOpenAIChatMisbehaviorUsage, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveStrictMode, resolveTestId, resolveUsage, runWithToolArgumentsScope, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, validateToolsField, withoutResponsesToolKeys, wouldProxyMiss } from "./helpers.js";
6
+ import { parsePolicy, resolvePolicy, resolveRequestDecisions, strictOverrideField } from "./response/policy.js";
7
+ import { RequestBodyTooLargeError, buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildOpenAIContentFilterChunks, buildOpenAIContentFilterCompletion, buildOpenAIReasoningChunks, buildOpenAIReasoningCompletion, buildOpenAIRefusalChunks, buildOpenAIRefusalCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, createServerFixtureList, describeMatch, extractOverrides, fixtureToolCallErrorCode, flattenHeaders, getContext, getTestId, googleFixtureToolCallErrorDetails, isAudioResponse, isCombinedFixtureResponse, isEmbeddingResponse, isErrorResponse, isExtendedResponsesToolsList, isFixtureToolCallError, isImageResponse, isJSONResponse, isJsonObject, isRawResponse, isResponseFactory, isServerOwnedFixtures, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, prepareOpenAIChatMisbehavior, readBody, readBodyBufferBounded, rejectGeneratedFiles, requireEmittedFunctionToolCalls, requireServedFunctionToolCalls, resolveFixtureBlockOutcome, resolveOpenAIChatMisbehaviorUsage, resolveReasoningForModel, resolveRequestId, resolveResponse, resolveTestId, resolveUsage, runWithToolArgumentsScope, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, validateToolsField, withoutResponsesToolKeys, wouldProxyMiss } from "./helpers.js";
5
8
  import { normalizeLiveOptions } from "./live-fixture.js";
6
- import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
9
+ import { sseCodec, writeErrorResponse } from "./sse-writer.js";
7
10
  import { CHAOS_FIELDS, CHAOS_FIELD_NAMES, applyChaosAction, awaitChaosLatency, describeUnwritableReason, evaluateChaos, isChaosScope, parseChaosField, resetChaosWarnings, resolveChaosConfig, responseGoneReason } from "./chaos.js";
8
11
  import { MISBEHAVIOR_CATALOG, WIRE_SUPPORT, getFixtureMisbehaviorPosition, parseMisbehavior, parseMisbehaviorHeader, planMisbehavior, recordMisbehaviorOutcome, resolveMisbehaviorSeed, resolveMisbehaviorShortCircuit, setFixtureMisbehaviorPosition } from "./misbehavior.js";
9
12
  import { FixtureLoadError, INJECTED_STATUS_RANGE, MisbehaviorConfigError, claimOneShotError, clearFixtureQueue, enableHeldFixtureMisbehavior, entryToFixture, isInjectableStatus, isOneShotError, markFixtureResponsesToolsExtended, queueOneShotError, releaseOneShotError, responsesToolsExtendedView, responsesToolsViewSource, validateFixtures } from "./fixture-loader.js";
@@ -11,11 +14,20 @@ import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
11
14
  import { proxyAndRecord } from "./recorder.js";
12
15
  import { Logger } from "./logger.js";
13
16
  import { Journal } from "./journal.js";
14
- import { createInterruptionSignal } from "./interruption.js";
17
+ import { CLIENT_ABORTED } from "./response/lifecycle.js";
18
+ import { extendRecordedTimings, misbehaviorPayloadLength } from "./response/timing.js";
19
+ import { deliverJson, deliverStream } from "./response/deliver.js";
20
+ import { proxyInterruption, withHangUpDetection } from "./response/proxy.js";
21
+ import { collect, idsFor } from "./response/catalog/collect.js";
22
+ import { coreModelIds, renderAnthropicModel, renderAnthropicModels, renderGeminiModel, renderGeminiModels, renderOllamaTags, renderOpenAIModel, renderOpenAIModels } from "./response/catalog/render.js";
15
23
  import { buildOpenRouterCandidates, handleOpenRouterCredits, handleOpenRouterKey, handleOpenRouterModels, isOpenRouterPath, resolveOpenRouterShaping, serializeOpenRouterError, shapeOpenRouterChunks, shapeOpenRouterCompletion } from "./openrouter-chat.js";
24
+ import { serveRawResponse } from "./response/raw.js";
25
+ import { anthropicPage } from "./response/catalog/anthropic-page.js";
26
+ import { geminiPage } from "./response/catalog/gemini-page.js";
16
27
  import { handleResponses } from "./responses.js";
17
28
  import { handleMessages } from "./messages.js";
18
29
  import { handleGemini } from "./gemini.js";
30
+ import { handleCountTokens } from "./count-tokens.js";
19
31
  import { handleGeminiEmbedContent } from "./gemini-embeddings.js";
20
32
  import { handleBedrock, handleBedrockStream } from "./bedrock.js";
21
33
  import { handleConverse, handleConverseStream } from "./bedrock-converse.js";
@@ -47,7 +59,7 @@ import { handleLiveSession } from "./ws-live.js";
47
59
  import { handleWebSocketResponses } from "./ws-responses.js";
48
60
  import { handleWebSocketRealtime } from "./ws-realtime.js";
49
61
  import { handleWebSocketGeminiLive } from "./ws-gemini-live.js";
50
- import { AZURE_DEPLOYMENT_RE, BATCHES_CANCEL_RE, BATCHES_ID_RE, BATCHES_PATH, BEDROCK_CONVERSE_RE, BEDROCK_CONVERSE_STREAM_RE, BEDROCK_INVOKE_RE, BEDROCK_STREAM_RE, BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE, COHERE_CHAT_PATH, COHERE_EMBED_PATH, COMPLETIONS_PATH, CONTROL_PREFIX, ELEVENLABS_MUSIC_RE, ELEVENLABS_SOUND_GENERATION_PATH, ELEVENLABS_TTS_RE, ELEVENLABS_VOICE_CREATE_PATH, ELEVENLABS_VOICE_DESIGN_PATH, ELEVENLABS_VOICE_RE, EMBEDDINGS_PATH, FAL_QUEUE_REQUESTS_RE, FAL_QUEUE_SUBMIT_RE, FAL_ROUTE_RE, FAL_RUN_RE, FILES_CONTENT_RE, FILES_ID_RE, FILES_PATH, FINE_TUNING_CANCEL_RE, FINE_TUNING_EVENTS_RE, FINE_TUNING_ID_RE, FINE_TUNING_JOBS_PATH, GEMINI_EMBED_RE, GEMINI_INTERACTIONS_PATH, GEMINI_LIVE_PATH, GEMINI_PATH_RE, GEMINI_PREDICT_RE, GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, HEALTH_PATH, IMAGES_EDIT_PATH, IMAGES_PATH, IMAGES_VARIATIONS_PATH, LIVE_PATH, MESSAGES_PATH, METRICS_PATH, MODELS_PATH, MODERATIONS_PATH, OLLAMA_CHAT_PATH, OLLAMA_EMBEDDINGS_PATH, OLLAMA_EMBED_PATH, OLLAMA_GENERATE_PATH, OLLAMA_TAGS_PATH, OPENROUTER_CREDITS_PATH, OPENROUTER_KEY_PATH, OPENROUTER_MODELS_PATH, OPENROUTER_VIDEOS_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_MODELS_PATH, OPENROUTER_VIDEO_STATUS_RE, READY_PATH, REALTIME_PATH, REQUESTS_PATH, RERANK_PATH, RESPONSES_PATH, SEARCH_PATH, SPEECH_PATH, TRANSCRIPTIONS_PATH, TRANSLATIONS_PATH, VECTOR_STORES_BATCH_CANCEL_RE, VECTOR_STORES_BATCH_FILES_RE, VECTOR_STORES_BATCH_RE, VECTOR_STORES_FILES_RE, VECTOR_STORES_FILE_BATCHES_RE, VECTOR_STORES_FILE_RE, VECTOR_STORES_ID_RE, VECTOR_STORES_PATH, VECTOR_STORES_SEARCH_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, VERTEX_AI_RE, VIDEOS_PATH, matchRouteDefinition } from "./route-registry.js";
62
+ import { AZURE_DEPLOYMENT_RE, BATCHES_CANCEL_RE, BATCHES_ID_RE, BATCHES_PATH, BEDROCK_CONVERSE_RE, BEDROCK_CONVERSE_STREAM_RE, BEDROCK_INVOKE_RE, BEDROCK_STREAM_RE, BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE, COHERE_CHAT_PATH, COHERE_EMBED_PATH, COMPLETIONS_PATH, CONTROL_PREFIX, ELEVENLABS_MUSIC_RE, ELEVENLABS_SOUND_GENERATION_PATH, ELEVENLABS_TTS_RE, ELEVENLABS_VOICE_CREATE_PATH, ELEVENLABS_VOICE_DESIGN_PATH, ELEVENLABS_VOICE_RE, EMBEDDINGS_PATH, FAL_QUEUE_REQUESTS_RE, FAL_QUEUE_SUBMIT_RE, FAL_ROUTE_RE, FAL_RUN_RE, FILES_CONTENT_RE, FILES_ID_RE, FILES_PATH, FINE_TUNING_CANCEL_RE, FINE_TUNING_EVENTS_RE, FINE_TUNING_ID_RE, FINE_TUNING_JOBS_PATH, GEMINI_COUNT_RE, GEMINI_EMBED_RE, GEMINI_INTERACTIONS_PATH, GEMINI_LIVE_PATH, GEMINI_MODELS_PATH, GEMINI_MODEL_DETAIL_RE, GEMINI_PATH_RE, GEMINI_PREDICT_RE, GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, HEALTH_PATH, IMAGES_EDIT_PATH, IMAGES_PATH, IMAGES_VARIATIONS_PATH, LIVE_PATH, MESSAGES_COUNT_PATH, MESSAGES_PATH, METRICS_PATH, MODELS_DETAIL_RE, MODELS_PATH, MODERATIONS_PATH, OLLAMA_CHAT_PATH, OLLAMA_EMBEDDINGS_PATH, OLLAMA_EMBED_PATH, OLLAMA_GENERATE_PATH, OLLAMA_TAGS_PATH, OPENROUTER_CREDITS_PATH, OPENROUTER_KEY_PATH, OPENROUTER_MODELS_PATH, OPENROUTER_VIDEOS_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_MODELS_PATH, OPENROUTER_VIDEO_STATUS_RE, READY_PATH, REALTIME_PATH, REQUESTS_PATH, RERANK_PATH, RESPONSES_PATH, SEARCH_PATH, SPEECH_PATH, TRANSCRIPTIONS_PATH, TRANSLATIONS_PATH, VECTOR_STORES_BATCH_CANCEL_RE, VECTOR_STORES_BATCH_FILES_RE, VECTOR_STORES_BATCH_RE, VECTOR_STORES_FILES_RE, VECTOR_STORES_FILE_BATCHES_RE, VECTOR_STORES_FILE_RE, VECTOR_STORES_ID_RE, VECTOR_STORES_PATH, VECTOR_STORES_SEARCH_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, VERTEX_AI_RE, VIDEOS_PATH, matchRouteDefinition } from "./route-registry.js";
51
63
  import { CATALOG_ROUTES, buildOpenApiDocument } from "./openapi.js";
52
64
  import { DESTROYED_STATUS_LABEL, createMetricsRegistry, normalizePathLabel } from "./metrics.js";
53
65
  import { MCP_FAKES_DEFAULT_MOUNT, McpFakeStore, McpFakesAddError, blockIdOf } from "./mcp-fakes.js";
@@ -87,13 +99,6 @@ function normalizeCompatPath(pathname, logger) {
87
99
  }
88
100
  return pathname;
89
101
  }
90
- const DEFAULT_MODELS = [
91
- "gpt-4",
92
- "gpt-4o",
93
- "claude-3-5-sonnet-20241022",
94
- "gemini-2.0-flash",
95
- "text-embedding-3-small"
96
- ];
97
102
  const CORS_HEADERS = {
98
103
  "Access-Control-Allow-Origin": "*",
99
104
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
@@ -110,10 +115,10 @@ function handleOptions(res) {
110
115
  }
111
116
  function handleNotFound(res, message) {
112
117
  setCorsHeaders(res);
113
- writeErrorResponse(res, 404, JSON.stringify({ error: {
118
+ writeErrorResponse(res, 404, renderError("openai", "route-failure", {
114
119
  message,
115
120
  type: "not_found"
116
- } }));
121
+ }).body);
117
122
  }
118
123
  /** The complete `GET /__aimock/fixtures` query-param vocabulary; anything else 400s. */
119
124
  const FIXTURES_PARAMS = new Set(["include"]);
@@ -210,6 +215,7 @@ function fixtureResponseKind(response) {
210
215
  if (isCombinedFixtureResponse(response)) return "contentWithToolCalls";
211
216
  if (isToolCallResponse(response)) return "toolCalls";
212
217
  if (isTextResponse(response)) return "text";
218
+ if (isRawResponse(response)) return "raw";
213
219
  return "unknown";
214
220
  }
215
221
  /**
@@ -895,7 +901,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
895
901
  const clientAbort = clientAbortedMidBody(req, res, err);
896
902
  const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
897
903
  if (clientAbort || bodyLimitDisconnect) {
898
- const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
904
+ const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : CLIENT_ABORTED;
899
905
  defaults.logger.warn(bodyLimitDisconnect ? `${route}: ${msg}` : `${route}: client aborted mid-body (${msg})`);
900
906
  journal.add({
901
907
  method: req.method ?? "POST",
@@ -924,10 +930,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
924
930
  fixture: null
925
931
  }
926
932
  });
927
- writeErrorResponse(res, status, openRouter ? serializeOpenRouterError(status, `Request body read failed: ${msg}`) : JSON.stringify({ error: {
933
+ writeErrorResponse(res, status, openRouter ? serializeOpenRouterError(status, `Request body read failed: ${msg}`) : renderError("openai", "transport-failure", {
928
934
  message: `Request body read failed: ${msg}`,
929
935
  type: clientFault ? "invalid_request_error" : "server_error"
930
- } }));
936
+ }).body);
931
937
  return;
932
938
  }
933
939
  let body;
@@ -945,12 +951,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
945
951
  fixture: null
946
952
  }
947
953
  });
948
- writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, `Malformed JSON: ${detail}`) : JSON.stringify({ error: {
954
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, `Malformed JSON: ${detail}`) : renderError("openai", "validation", {
949
955
  message: `Malformed JSON: ${detail}`,
950
956
  type: "invalid_request_error",
951
957
  param: null,
952
- code: "invalid_json"
953
- } }));
958
+ code: "invalid_json",
959
+ includeParam: true
960
+ }).body);
954
961
  return;
955
962
  }
956
963
  if (!isJsonObject(body)) {
@@ -964,10 +971,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
964
971
  fixture: null
965
972
  }
966
973
  });
967
- writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Request body must be a JSON object") : JSON.stringify({ error: {
974
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Request body must be a JSON object") : renderError("openai", "validation", {
968
975
  message: "Request body must be a JSON object",
969
976
  type: "invalid_request_error"
970
- } }));
977
+ }).body);
971
978
  return;
972
979
  }
973
980
  if (modelFallback && !body.model) body.model = modelFallback;
@@ -982,12 +989,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
982
989
  fixture: null
983
990
  }
984
991
  });
985
- writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Missing required parameter: 'messages'") : JSON.stringify({ error: {
992
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Missing required parameter: 'messages'") : renderError("openai", "validation", {
986
993
  message: "Missing required parameter: 'messages'",
987
994
  type: "invalid_request_error",
988
995
  param: null,
989
- code: null
990
- } }));
996
+ code: null,
997
+ includeParam: true
998
+ }).body);
991
999
  return;
992
1000
  }
993
1001
  const nullMessageIndex = body.messages.findIndex((message) => message === null);
@@ -1003,12 +1011,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1003
1011
  fixture: null
1004
1012
  }
1005
1013
  });
1006
- writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, message) : JSON.stringify({ error: {
1014
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, message) : renderError("openai", "validation", {
1007
1015
  message,
1008
1016
  type: "invalid_request_error",
1009
1017
  param: null,
1010
- code: null
1011
- } }));
1018
+ code: null,
1019
+ includeParam: true
1020
+ }).body);
1012
1021
  return;
1013
1022
  }
1014
1023
  if (new URL(req.url ?? COMPLETIONS_PATH, "http://localhost").pathname === COMPLETIONS_PATH) {
@@ -1028,10 +1037,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1028
1037
  fixture: null
1029
1038
  }
1030
1039
  });
1031
- writeErrorResponse(res, 400, JSON.stringify({ error: {
1040
+ writeErrorResponse(res, 400, renderError("openai", "validation", {
1032
1041
  message: toolsError,
1033
1042
  type: "invalid_request_error"
1034
- } }));
1043
+ }).body);
1035
1044
  return;
1036
1045
  }
1037
1046
  }
@@ -1041,7 +1050,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1041
1050
  body._endpointType = "chat";
1042
1051
  body._context = getContext(req);
1043
1052
  const testId = getTestId(req);
1044
- const matchOptions = recordMatchOptions(!!(defaults.record && providerKey), defaults.logger);
1053
+ const matchOptions = {
1054
+ ...recordMatchOptions(!!(defaults.record && providerKey), defaults.logger),
1055
+ wire: "openai-chat"
1056
+ };
1045
1057
  let fixture = null;
1046
1058
  let skippedBySequenceOrTurn = 0;
1047
1059
  let preResolvedResponse = null;
@@ -1071,10 +1083,11 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1071
1083
  skippedBySequenceOrTurn = Math.max(skippedBySequenceOrTurn, attempt.skippedBySequenceOrTurn);
1072
1084
  if (!attempt.fixture) return null;
1073
1085
  const response = await resolveResponse(attempt.fixture, probe);
1086
+ const isError = isErrorResponse(response) || isRawResponse(response) && (response.raw.status ?? 200) >= 400;
1074
1087
  return {
1075
1088
  fixture: attempt.fixture,
1076
1089
  response,
1077
- isError: isErrorResponse(response)
1090
+ isError
1078
1091
  };
1079
1092
  };
1080
1093
  for (const candidate of candidates) {
@@ -1088,7 +1101,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1088
1101
  matchCounts = journal.getFixtureMatchCountsForTest(testId);
1089
1102
  responseModel = candidate;
1090
1103
  if (outcome.isError) {
1091
- const errorFallsThrough = outcome.response.fallthrough !== false;
1104
+ const errorFallsThrough = !isErrorResponse(outcome.response) || outcome.response.fallthrough !== false;
1092
1105
  if (allowFallbacks && errorFallsThrough) {
1093
1106
  lastErrorFixture = outcome.fixture;
1094
1107
  lastErrorResponse = outcome.response;
@@ -1131,8 +1144,9 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1131
1144
  headers: flatHeaders,
1132
1145
  body
1133
1146
  };
1134
- const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1135
- const missWouldProxy = wouldProxyMiss(effectiveStrict, defaults.record, providerKey);
1147
+ const missDecisions = resolveRequestDecisions(defaults.strict, req.headers);
1148
+ const effectiveStrict = missDecisions.fixtureMiss;
1149
+ const missWouldProxy = wouldProxyMiss(!missDecisions.upstream, defaults.record, providerKey);
1136
1150
  const noFixtureSource = missWouldProxy ? "proxy" : "internal";
1137
1151
  const recordBypass = (reason) => {
1138
1152
  const entry = getRequestEntry();
@@ -1179,12 +1193,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1179
1193
  ...strictOverrideField(defaults.strict, req.headers)
1180
1194
  }
1181
1195
  });
1182
- writeErrorResponse(res, strictStatus, openRouter ? serializeOpenRouterError(strictStatus, strictMessage) : JSON.stringify({ error: {
1196
+ writeErrorResponse(res, strictStatus, openRouter ? serializeOpenRouterError(strictStatus, strictMessage) : renderError("openai", "strict-miss", {
1183
1197
  message: strictMessage,
1184
1198
  type: "invalid_request_error",
1185
1199
  param: null,
1186
- code: "no_fixture_match"
1187
- } }));
1200
+ code: "no_fixture_match",
1201
+ includeParam: true
1202
+ }).body);
1188
1203
  return;
1189
1204
  }
1190
1205
  if (defaults.record && providerKey) {
@@ -1203,22 +1218,9 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1203
1218
  });
1204
1219
  }
1205
1220
  } : void 0;
1206
- let clientHungUp = false;
1207
- const noteClientHangUp = () => {
1208
- if (!res.writableEnded) clientHungUp = true;
1209
- };
1210
- req.socket?.on("end", noteClientHangUp);
1211
- req.socket?.on("error", noteClientHangUp);
1212
- let outcome;
1213
- try {
1214
- outcome = await proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions);
1215
- } finally {
1216
- req.socket?.off("end", noteClientHangUp);
1217
- req.socket?.off("error", noteClientHangUp);
1218
- }
1221
+ const { outcome, clientHungUp } = await withHangUpDetection(req, res, () => proxyAndRecord(req, res, body, providerKey, req.url ?? COMPLETIONS_PATH, fixtures, defaults, raw, hookOptions));
1219
1222
  if (outcome === "handled_by_hook") return;
1220
1223
  if (outcome !== "not_configured") {
1221
- const destroyedMidStream = res.destroyed && !res.writableEnded;
1222
1224
  journal.add({
1223
1225
  method: req.method ?? "POST",
1224
1226
  path: req.url ?? COMPLETIONS_PATH,
@@ -1228,10 +1230,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1228
1230
  status: res.statusCode ?? 200,
1229
1231
  fixture: null,
1230
1232
  source: "proxy",
1231
- ...destroyedMidStream ? {
1232
- interrupted: true,
1233
- interruptReason: clientHungUp ? "client aborted" : "proxy stream destroyed"
1234
- } : {}
1233
+ ...proxyInterruption(res, clientHungUp)
1235
1234
  }
1236
1235
  });
1237
1236
  recordBypass("proxied");
@@ -1254,12 +1253,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1254
1253
  ...strictOverrideField(defaults.strict, req.headers)
1255
1254
  }
1256
1255
  });
1257
- writeErrorResponse(res, 404, openRouter ? serializeOpenRouterError(404, "No fixture matched") : JSON.stringify({ error: {
1256
+ writeErrorResponse(res, 404, openRouter ? serializeOpenRouterError(404, "No fixture matched") : renderError("openai", "fixture-miss", {
1258
1257
  message: "No fixture matched",
1259
1258
  type: "invalid_request_error",
1260
1259
  param: null,
1261
- code: "no_fixture_match"
1262
- } }));
1260
+ code: "no_fixture_match",
1261
+ includeParam: true
1262
+ }).body);
1263
1263
  return;
1264
1264
  }
1265
1265
  const resolvedResponse = preResolvedResponse ?? await resolveResponse(fixture, body);
@@ -1290,12 +1290,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1290
1290
  testId
1291
1291
  });
1292
1292
  if (!evaluation.summary?.evaluations.some((row) => row.outcome === "error")) defaults.logger.error(`${evaluation.code}: ${evaluation.message}`);
1293
- writeErrorResponse(res, evaluation.status, openRouter ? serializeOpenRouterError(evaluation.status, `${evaluation.code}: ${evaluation.message}`) : JSON.stringify({ error: {
1293
+ writeErrorResponse(res, evaluation.status, openRouter ? serializeOpenRouterError(evaluation.status, `${evaluation.code}: ${evaluation.message}`) : renderError("openai", "misbehavior", {
1294
1294
  message: evaluation.message,
1295
1295
  type: "invalid_request_error",
1296
1296
  param: null,
1297
- code: evaluation.code
1298
- } }));
1297
+ code: evaluation.code,
1298
+ includeParam: true
1299
+ }).body);
1299
1300
  return;
1300
1301
  }
1301
1302
  const appliedPlan = evaluation.kind === "applied" ? prepareOpenAIChatMisbehavior(evaluation) : void 0;
@@ -1309,21 +1310,30 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1309
1310
  testId
1310
1311
  });
1311
1312
  };
1312
- const appliedReasoning = appliedPlan && appliedPlan.reasoning === void 0 && (isTextResponse(response) || isToolCallResponse(response) || isCombinedFixtureResponse(response)) ? resolveReasoningForModel(response.reasoning, responseModel, resolveStrictMode(defaults.strict, req.headers), defaults.logger) : void 0;
1313
+ const appliedReasoning = appliedPlan && appliedPlan.reasoning === void 0 && (isTextResponse(response) || isToolCallResponse(response) || isCombinedFixtureResponse(response)) ? resolveReasoningForModel(response.reasoning, responseModel, resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, defaults.logger) : void 0;
1313
1314
  const appliedUsage = appliedPlan ? resolveOpenAIChatMisbehaviorUsage(appliedPlan, body, appliedPlan.reasoning === void 0 ? appliedReasoning !== void 0 : openRouter) : void 0;
1314
1315
  const latency = fixture.latency ?? defaults.latency;
1315
1316
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
1316
1317
  const emitStreamingUsage = body.stream === true && body.stream_options?.include_usage === true || openRouter;
1317
1318
  const streamingPromptText = body.messages.map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((p) => p.text ?? "").join("") : "").join("");
1318
1319
  const resolveStreamingUsageTokens = (overrides, completionText) => appliedUsage ?? resolveUsage(overrides, streamingPromptText, completionText);
1319
- const resolveRecordedTimings = (chunks) => {
1320
- const timings = fixture.recordedTimings;
1321
- if (!appliedPlan?.duplicateId || !timings) return timings;
1322
- const gaps = timings.interChunkDelaysMs;
1323
- return {
1324
- ...timings,
1325
- interChunkDelaysMs: Array.from({ length: Math.max(gaps.length, chunks.length - 1) }, (_, index) => gaps[index] ?? gaps.at(-1) ?? 0)
1326
- };
1320
+ const nonStreamingOpts = (payloadLength) => ({
1321
+ surface: "openai-chat",
1322
+ payloadLength,
1323
+ chunkSize,
1324
+ recordedTimings: fixture.recordedTimings,
1325
+ duplicateId: !!appliedPlan?.duplicateId,
1326
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
1327
+ });
1328
+ const streamTiming = (frameCount) => ({
1329
+ latency,
1330
+ streamingProfile: fixture.streamingProfile,
1331
+ recordedTimings: extendRecordedTimings(fixture.recordedTimings, frameCount, !!appliedPlan?.duplicateId),
1332
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
1333
+ });
1334
+ const commitNonStream = async (entry, reply, payloadLength) => {
1335
+ recordOutcome(entry);
1336
+ await deliverJson(res, entry, fixture, () => reply, nonStreamingOpts(payloadLength));
1327
1337
  };
1328
1338
  const shapeORCompletion = (completion, overrides) => {
1329
1339
  if (appliedUsage) completion.usage = appliedUsage;
@@ -1337,6 +1347,24 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1337
1347
  if (usageChunk) shapeOpenRouterChunks([usageChunk], shaping, overrides?.id !== void 0);
1338
1348
  };
1339
1349
  const openRouterProcessing = !!(openRouter && fixture.openRouterProcessing);
1350
+ if (isRawResponse(response)) {
1351
+ await serveRawResponse({
1352
+ wire: "openai-chat",
1353
+ stream: body.stream === true,
1354
+ req,
1355
+ res,
1356
+ fixture,
1357
+ response,
1358
+ body,
1359
+ journal,
1360
+ defaults,
1361
+ testId,
1362
+ misbehaviorSummary: evaluation.summary,
1363
+ errorWire: "openai",
1364
+ openRouter
1365
+ });
1366
+ return;
1367
+ }
1340
1368
  if (isErrorResponse(response)) {
1341
1369
  const status = response.status ?? 500;
1342
1370
  recordOutcome(journal.add({
@@ -1363,10 +1391,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1363
1391
  fixture
1364
1392
  }
1365
1393
  }));
1366
- writeErrorResponse(res, 422, openRouter ? serializeOpenRouterError(422, "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.") : JSON.stringify({ error: {
1394
+ writeErrorResponse(res, 422, openRouter ? serializeOpenRouterError(422, "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.") : renderError("openai", "validation", {
1367
1395
  message: "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.",
1368
1396
  type: "invalid_request_error"
1369
- } }));
1397
+ }).body);
1370
1398
  return;
1371
1399
  }
1372
1400
  if (appliedPlan && isTextResponse(response) && (appliedPlan.stop === "refusal" || appliedPlan.stop === "content_filter" || appliedPlan.reasoning !== void 0)) {
@@ -1381,38 +1409,25 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1381
1409
  fixture
1382
1410
  }
1383
1411
  });
1384
- recordOutcome(entry);
1385
- if (body.stream !== true) {
1386
- const completion = appliedPlan.stop === "refusal" ? buildOpenAIRefusalCompletion(appliedPlan.refusal ?? "", responseModel, overrides, body.messages) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterCompletion(responseModel, overrides, body.messages) : buildOpenAIReasoningCompletion(appliedPlan.reasoning ?? "", responseModel, openRouter, overrides, body.messages);
1387
- res.writeHead(200, { "Content-Type": "application/json" });
1388
- res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1389
- } else {
1412
+ rejectGeneratedFiles(response, wire);
1413
+ if (body.stream !== true) await commitNonStream(entry, shapeORCompletion(appliedPlan.stop === "refusal" ? buildOpenAIRefusalCompletion(appliedPlan.refusal ?? "", responseModel, overrides, body.messages) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterCompletion(responseModel, overrides, body.messages) : buildOpenAIReasoningCompletion(appliedPlan.reasoning ?? "", responseModel, openRouter, overrides, body.messages), overrides), misbehaviorPayloadLength(appliedPlan, appliedReasoning));
1414
+ else {
1390
1415
  const chunks = appliedPlan.stop === "refusal" ? buildOpenAIRefusalChunks(appliedPlan.refusal ?? "", responseModel, chunkSize, overrides) : appliedPlan.stop === "content_filter" ? buildOpenAIContentFilterChunks(responseModel, overrides) : buildOpenAIReasoningChunks(appliedPlan.reasoning ?? "", responseModel, chunkSize, openRouter, overrides);
1391
1416
  const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0].id, overrides?.model ?? responseModel, chunks[0].created, resolveStreamingUsageTokens(overrides, ""), overrides?.systemFingerprint) : void 0;
1392
1417
  shapeORChunks(chunks, usageChunk, overrides);
1393
- const interruption = createInterruptionSignal(fixture);
1394
- if (!await writeSSEStream(res, chunks, {
1395
- latency,
1396
- streamingProfile: fixture.streamingProfile,
1397
- signal: interruption?.signal,
1398
- onChunkSent: interruption?.tick,
1418
+ recordOutcome(entry);
1419
+ await deliverStream(res, entry, fixture, sseCodec({
1420
+ done: true,
1399
1421
  usageChunk,
1400
- recordedTimings: resolveRecordedTimings(chunks),
1401
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1402
1422
  openRouterProcessing
1403
- })) {
1404
- if (!res.writableEnded) res.destroy();
1405
- entry.response.interrupted = true;
1406
- entry.response.interruptReason = interruption?.reason();
1407
- }
1408
- interruption?.cleanup();
1423
+ }), chunks, streamTiming(chunks.length));
1409
1424
  }
1410
1425
  return;
1411
1426
  }
1412
1427
  if (isCombinedFixtureResponse(response)) {
1413
1428
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
1414
1429
  const overrides = extractOverrides(response);
1415
- const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1430
+ const effectiveStrict = resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression;
1416
1431
  const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1417
1432
  const streaming = body.stream === true;
1418
1433
  const blockOutcome = response.blocks && response.blocks.length > 0 ? resolveFixtureBlockOutcome(response.blocks) : void 0;
@@ -1426,43 +1441,33 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1426
1441
  fixture
1427
1442
  }
1428
1443
  });
1429
- recordOutcome(journalEntry);
1444
+ rejectGeneratedFiles(response, wire);
1430
1445
  const toolCalls = requireEmittedFunctionToolCalls(response, wire);
1446
+ const completionText = blockOutcome ? blockOutcome.content + blockOutcome.toolCalls.map((tc) => tc.name + tc.arguments).join("") : (response.content ?? "") + toolCalls.map((tc) => tc.name + tc.arguments).join("");
1431
1447
  if (!streaming) {
1432
1448
  const completion = buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? toolCalls, responseModel, effReasoning, blockOutcome ? {
1433
1449
  ...overrides,
1434
1450
  finishReason: overrides?.finishReason ?? (blockOutcome.hasToolCalls ? "tool_calls" : "stop")
1435
1451
  } : overrides, body.messages);
1436
- res.writeHead(200, { "Content-Type": "application/json" });
1437
- res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1452
+ const contentLength = (effReasoning?.length ?? 0) + completionText.length;
1453
+ await commitNonStream(journalEntry, shapeORCompletion(completion, overrides), contentLength);
1438
1454
  } else {
1439
1455
  const chunks = buildContentWithToolCallsChunks(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? toolCalls, responseModel, chunkSize, effReasoning, overrides, blockOutcome?.ordered);
1440
- const completionText = blockOutcome ? blockOutcome.content + blockOutcome.toolCalls.map((tc) => tc.name + tc.arguments).join("") : (response.content ?? "") + toolCalls.map((tc) => tc.name + tc.arguments).join("");
1441
1456
  const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
1442
1457
  shapeORChunks(chunks, usageChunk, overrides);
1443
- const interruption = createInterruptionSignal(fixture);
1444
- if (!await writeSSEStream(res, chunks, {
1445
- latency,
1446
- streamingProfile: fixture.streamingProfile,
1447
- signal: interruption?.signal,
1448
- onChunkSent: interruption?.tick,
1458
+ recordOutcome(journalEntry);
1459
+ await deliverStream(res, journalEntry, fixture, sseCodec({
1460
+ done: true,
1449
1461
  usageChunk,
1450
- recordedTimings: resolveRecordedTimings(chunks),
1451
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1452
1462
  openRouterProcessing
1453
- })) {
1454
- if (!res.writableEnded) res.destroy();
1455
- journalEntry.response.interrupted = true;
1456
- journalEntry.response.interruptReason = interruption?.reason();
1457
- }
1458
- interruption?.cleanup();
1463
+ }), chunks, streamTiming(chunks.length));
1459
1464
  }
1460
1465
  return;
1461
1466
  }
1462
1467
  if (isTextResponse(response)) {
1463
1468
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
1464
1469
  const overrides = extractOverrides(response);
1465
- const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1470
+ const effectiveStrict = resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression;
1466
1471
  const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1467
1472
  const journalEntry = journal.add({
1468
1473
  method: req.method ?? "POST",
@@ -1474,38 +1479,28 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1474
1479
  fixture
1475
1480
  }
1476
1481
  });
1477
- recordOutcome(journalEntry);
1482
+ rejectGeneratedFiles(response, wire);
1478
1483
  if (body.stream !== true) {
1479
1484
  const completion = buildTextCompletion(response.content, responseModel, effReasoning, overrides, body.messages);
1480
- res.writeHead(200, { "Content-Type": "application/json" });
1481
- res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1485
+ const contentLength = (effReasoning?.length ?? 0) + response.content.length;
1486
+ await commitNonStream(journalEntry, shapeORCompletion(completion, overrides), contentLength);
1482
1487
  } else {
1483
1488
  const chunks = buildTextChunks(response.content, responseModel, chunkSize, effReasoning, overrides);
1484
1489
  const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, response.content), overrides?.systemFingerprint) : void 0;
1485
1490
  shapeORChunks(chunks, usageChunk, overrides);
1486
- const interruption = createInterruptionSignal(fixture);
1487
- if (!await writeSSEStream(res, chunks, {
1488
- latency,
1489
- streamingProfile: fixture.streamingProfile,
1490
- signal: interruption?.signal,
1491
- onChunkSent: interruption?.tick,
1491
+ recordOutcome(journalEntry);
1492
+ await deliverStream(res, journalEntry, fixture, sseCodec({
1493
+ done: true,
1492
1494
  usageChunk,
1493
- recordedTimings: resolveRecordedTimings(chunks),
1494
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1495
1495
  openRouterProcessing
1496
- })) {
1497
- if (!res.writableEnded) res.destroy();
1498
- journalEntry.response.interrupted = true;
1499
- journalEntry.response.interruptReason = interruption?.reason();
1500
- }
1501
- interruption?.cleanup();
1496
+ }), chunks, streamTiming(chunks.length));
1502
1497
  }
1503
1498
  return;
1504
1499
  }
1505
1500
  if (isToolCallResponse(response)) {
1506
1501
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
1507
1502
  const overrides = extractOverrides(response);
1508
- const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1503
+ const effectiveStrict = resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression;
1509
1504
  const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1510
1505
  const journalEntry = journal.add({
1511
1506
  method: req.method ?? "POST",
@@ -1517,33 +1512,24 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1517
1512
  fixture
1518
1513
  }
1519
1514
  });
1520
- recordOutcome(journalEntry);
1515
+ rejectGeneratedFiles(response, wire);
1521
1516
  const toolCalls = requireServedFunctionToolCalls(response, wire);
1522
1517
  if (body.stream !== true) {
1518
+ const toolCallText = toolCalls.map((tc) => tc.name + tc.arguments).join("");
1523
1519
  const completion = buildToolCallCompletion(toolCalls, responseModel, effReasoning, overrides, body.messages);
1524
- res.writeHead(200, { "Content-Type": "application/json" });
1525
- res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1520
+ const contentLength = (effReasoning?.length ?? 0) + toolCallText.length;
1521
+ await commitNonStream(journalEntry, shapeORCompletion(completion, overrides), contentLength);
1526
1522
  } else {
1527
1523
  const chunks = buildToolCallChunks(toolCalls, responseModel, chunkSize, effReasoning, overrides);
1528
1524
  const completionText = toolCalls.map((tc) => tc.name + tc.arguments).join("");
1529
1525
  const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
1530
1526
  shapeORChunks(chunks, usageChunk, overrides);
1531
- const interruption = createInterruptionSignal(fixture);
1532
- if (!await writeSSEStream(res, chunks, {
1533
- latency,
1534
- streamingProfile: fixture.streamingProfile,
1535
- signal: interruption?.signal,
1536
- onChunkSent: interruption?.tick,
1527
+ recordOutcome(journalEntry);
1528
+ await deliverStream(res, journalEntry, fixture, sseCodec({
1529
+ done: true,
1537
1530
  usageChunk,
1538
- recordedTimings: resolveRecordedTimings(chunks),
1539
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1540
1531
  openRouterProcessing
1541
- })) {
1542
- if (!res.writableEnded) res.destroy();
1543
- journalEntry.response.interrupted = true;
1544
- journalEntry.response.interruptReason = interruption?.reason();
1545
- }
1546
- interruption?.cleanup();
1532
+ }), chunks, streamTiming(chunks.length));
1547
1533
  }
1548
1534
  return;
1549
1535
  }
@@ -1557,10 +1543,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1557
1543
  fixture
1558
1544
  }
1559
1545
  }));
1560
- writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, "Fixture response did not match any known type") : JSON.stringify({ error: {
1546
+ writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, "Fixture response did not match any known type") : renderError("openai", "unknown-response", {
1561
1547
  message: "Fixture response did not match any known type",
1562
1548
  type: "server_error"
1563
- } }));
1549
+ }).body);
1564
1550
  }
1565
1551
  async function createServer(fixtures, options, mounts, serviceFixtures) {
1566
1552
  return createServerWithResolvedAuth(fixtures, options, resolveInboundAuth({
@@ -1629,6 +1615,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1629
1615
  const port = options?.port ?? 0;
1630
1616
  const registry = options?.metrics ? createMetricsRegistry() : void 0;
1631
1617
  const serverOptions = options ?? {};
1618
+ parsePolicy(serverOptions.policy, (message) => new Logger("warn").warn(message));
1632
1619
  const validResponsesTools = (mode) => mode === "legacy" || mode === "extended";
1633
1620
  if (serverOptions.responsesTools !== void 0 && !validResponsesTools(serverOptions.responsesTools)) new Logger("warn").warn(`Ignoring responsesTools because it must be "legacy" or "extended", got ${JSON.stringify(serverOptions.responsesTools)}. Using "legacy".`);
1634
1621
  fixtures = createServerFixtureList(callerFixtures, () => serverOptions.responsesTools, responsesToolsExtendedView);
@@ -1687,6 +1674,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1687
1674
  get strict() {
1688
1675
  return serverOptions.strict;
1689
1676
  },
1677
+ get policy() {
1678
+ return serverOptions.policy;
1679
+ },
1690
1680
  get strictToolArguments() {
1691
1681
  return serverOptions.strictToolArguments;
1692
1682
  },
@@ -1799,10 +1789,12 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1799
1789
  return status >= 400 && status < 500 ? "invalid_request_error" : "server_error";
1800
1790
  }
1801
1791
  /**
1802
- * Envelope for a fixture tool-call error (see isFixtureToolCallError: invalid
1803
- * JSON arguments on an object wire, a custom tool call on a non-Responses
1804
- * wire, or a malformed fixture tool call). It is the provider envelope aimock
1805
- * emits for that route, with the aimock `code` in it: Google's
1792
+ * Envelope for a fixture error (see isFixtureToolCallError: invalid JSON
1793
+ * arguments on an object wire, a custom tool call on a non-Responses wire, a
1794
+ * malformed fixture tool call, generated `files` on a wire that cannot carry
1795
+ * them, or a malformed fixture `citations` or `files` value). It is the
1796
+ * provider envelope aimock emits for that route, with the aimock `code` in
1797
+ * it: Google's
1806
1798
  * `google.rpc.ErrorInfo` detail on Gemini and Vertex AI, a string
1807
1799
  * `error.code` on Gemini Interactions and Anthropic Messages, a `reason`
1808
1800
  * member on Bedrock (a `code` member would replace `__type` as the AWS SDK's
@@ -1812,70 +1804,63 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1812
1804
  * `toolCallEnvelope` instead of using this one.
1813
1805
  */
1814
1806
  function invalidToolArgumentsEnvelope(pathname, message, code) {
1815
- if (pathname === OLLAMA_CHAT_PATH) return JSON.stringify({
1816
- error: message,
1807
+ if (pathname === OLLAMA_CHAT_PATH) return renderError("ollama", "tool-argument", {
1808
+ message,
1817
1809
  code
1818
- });
1819
- if (pathname === GEMINI_INTERACTIONS_PATH) return JSON.stringify({ error: {
1810
+ }).body;
1811
+ if (pathname === GEMINI_INTERACTIONS_PATH) return renderError("interactions", "tool-argument", {
1812
+ message,
1813
+ code
1814
+ }).body;
1815
+ if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return renderError("gemini", "tool-argument", {
1816
+ message,
1820
1817
  code,
1821
- message
1822
- } });
1823
- if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return JSON.stringify({ error: {
1824
- code: 500,
1818
+ googleDetails: googleFixtureToolCallErrorDetails(code)
1819
+ }).body;
1820
+ if (pathname === MESSAGES_PATH) return renderError("messages", "tool-argument", {
1825
1821
  message,
1826
- status: "INTERNAL",
1827
- details: googleFixtureToolCallErrorDetails(code)
1828
- } });
1829
- if (pathname === MESSAGES_PATH) return JSON.stringify({
1830
- type: "error",
1831
- error: {
1832
- type: "api_error",
1833
- code,
1834
- message
1835
- }
1836
- });
1837
- if (BEDROCK_INVOKE_RE.test(pathname) || BEDROCK_STREAM_RE.test(pathname) || BEDROCK_CONVERSE_RE.test(pathname) || BEDROCK_CONVERSE_STREAM_RE.test(pathname)) return JSON.stringify({
1838
- __type: "InternalServerException",
1822
+ code
1823
+ }).body;
1824
+ if (BEDROCK_INVOKE_RE.test(pathname) || BEDROCK_STREAM_RE.test(pathname) || BEDROCK_CONVERSE_RE.test(pathname) || BEDROCK_CONVERSE_STREAM_RE.test(pathname)) return renderError("bedrock", "tool-argument", {
1839
1825
  message,
1840
- reason: code
1841
- });
1842
- return JSON.stringify({ error: {
1826
+ code
1827
+ }).body;
1828
+ return renderError("openai", "tool-argument", {
1843
1829
  message,
1844
- type: "server_error",
1845
1830
  code
1846
- } });
1831
+ }).body;
1847
1832
  }
1848
1833
  function invalidMisbehaviorHeaderEnvelope(pathname, detail) {
1849
1834
  const code = "aimock_misbehavior_invalid";
1850
1835
  const message = `${code}: ${detail}`;
1851
- if (pathname === OLLAMA_CHAT_PATH || pathname === OLLAMA_GENERATE_PATH) return JSON.stringify({ error: message });
1852
- if (pathname === COHERE_CHAT_PATH) return JSON.stringify({ message });
1853
- if (pathname === GEMINI_INTERACTIONS_PATH) return JSON.stringify({ error: {
1854
- code,
1855
- message
1856
- } });
1857
- if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return JSON.stringify({ error: {
1858
- code: 400,
1836
+ if (pathname === OLLAMA_CHAT_PATH || pathname === OLLAMA_GENERATE_PATH) return renderError("ollama", "invalid-misbehavior-header", {
1859
1837
  message,
1860
- status: "INVALID_ARGUMENT"
1861
- } });
1862
- if (pathname === MESSAGES_PATH) return JSON.stringify({
1863
- type: "error",
1864
- error: {
1865
- type: "invalid_request_error",
1866
- code,
1867
- message
1868
- }
1869
- });
1870
- if (BEDROCK_INVOKE_RE.test(pathname) || BEDROCK_STREAM_RE.test(pathname) || BEDROCK_CONVERSE_RE.test(pathname) || BEDROCK_CONVERSE_STREAM_RE.test(pathname)) return JSON.stringify({
1871
- __type: "ValidationException",
1872
- message
1873
- });
1874
- return JSON.stringify({ error: {
1838
+ code
1839
+ }).body;
1840
+ if (pathname === COHERE_CHAT_PATH) return renderError("cohere", "invalid-misbehavior-header", {
1841
+ message,
1842
+ code
1843
+ }).body;
1844
+ if (pathname === GEMINI_INTERACTIONS_PATH) return renderError("interactions", "invalid-misbehavior-header", {
1845
+ message,
1846
+ code
1847
+ }).body;
1848
+ if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return renderError("gemini", "invalid-misbehavior-header", {
1875
1849
  message,
1876
- type: "invalid_request_error",
1877
1850
  code
1878
- } });
1851
+ }).body;
1852
+ if (pathname === MESSAGES_PATH) return renderError("messages", "invalid-misbehavior-header", {
1853
+ message,
1854
+ code
1855
+ }).body;
1856
+ if (BEDROCK_INVOKE_RE.test(pathname) || BEDROCK_STREAM_RE.test(pathname) || BEDROCK_CONVERSE_RE.test(pathname) || BEDROCK_CONVERSE_STREAM_RE.test(pathname)) return renderError("bedrock", "invalid-misbehavior-header", {
1857
+ message,
1858
+ code
1859
+ }).body;
1860
+ return renderError("openai", "invalid-misbehavior-header", {
1861
+ message,
1862
+ code
1863
+ }).body;
1879
1864
  }
1880
1865
  /**
1881
1866
  * The newest journal entry this request already produced, or null.
@@ -1939,7 +1924,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1939
1924
  const existing = lastJournalEntryFor(req);
1940
1925
  const bodyLimitDisconnect = clientFault && (req.destroyed || res.destroyed);
1941
1926
  if ((clientAbort || bodyLimitDisconnect) && !res.headersSent) {
1942
- const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : "client aborted";
1927
+ const interruptReason = bodyLimitDisconnect ? "request body exceeded size limit" : CLIENT_ABORTED;
1943
1928
  if (existing) {
1944
1929
  if (!existing.response.interrupted) {
1945
1930
  existing.response.status = 0;
@@ -1990,12 +1975,12 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1990
1975
  logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1991
1976
  }
1992
1977
  setCorsHeaders(res);
1993
- writeErrorResponse(res, status, invalidToolArguments ? (opts?.toolCallEnvelope ?? ((m, c) => invalidToolArgumentsEnvelope(pathname, m, c)))(msg, fixtureToolCallErrorCode(err)) : opts?.envelope ? opts.envelope(msg, status) : JSON.stringify({ error: {
1978
+ writeErrorResponse(res, status, invalidToolArguments ? (opts?.toolCallEnvelope ?? ((m, c) => invalidToolArgumentsEnvelope(pathname, m, c)))(msg, fixtureToolCallErrorCode(err)) : opts?.envelope ? opts.envelope(msg, status) : renderError("openai", "route-failure", {
1994
1979
  message: msg,
1995
1980
  type: errorTypeForStatus(status)
1996
- } }));
1981
+ }).body);
1997
1982
  } else {
1998
- const midStreamReason = clientAbort ? "client aborted" : "handler crashed mid-stream";
1983
+ const midStreamReason = clientAbort ? CLIENT_ABORTED : "handler crashed mid-stream";
1999
1984
  if (existing) {
2000
1985
  if (res.writableEnded) {
2001
1986
  if (existing.response.error === void 0) existing.response.error = msg;
@@ -2110,10 +2095,10 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2110
2095
  logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
2111
2096
  }
2112
2097
  setCorsHeaders(res);
2113
- writeErrorResponse(res, 400, JSON.stringify({ error: {
2098
+ writeErrorResponse(res, 400, renderError("openai", "route-failure", {
2114
2099
  message: "Invalid request target",
2115
2100
  type: "invalid_request_error"
2116
- } }));
2101
+ }).body);
2117
2102
  return;
2118
2103
  }
2119
2104
  pathname = parsedUrl.pathname;
@@ -2126,7 +2111,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2126
2111
  }
2127
2112
  if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname === METRICS_PATH))) {
2128
2113
  if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
2129
- writeApiKeyHttpRejection(res, setCorsHeaders);
2114
+ writeApiKeyHttpRejection(res, setCorsHeaders, pathname, resolvePolicy(defaults, req.headers).authErrors);
2130
2115
  return;
2131
2116
  }
2132
2117
  markAuthenticatedRequest(req, resolvedAuth.policy);
@@ -2193,18 +2178,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2193
2178
  }
2194
2179
  if (pathname === OLLAMA_TAGS_PATH && req.method === "GET") {
2195
2180
  setCorsHeaders(res);
2196
- const modelIds = /* @__PURE__ */ new Set();
2197
- for (const f of fixtures) if (f.match.model && typeof f.match.model === "string") modelIds.add(f.match.model);
2198
- const models = (modelIds.size > 0 ? [...modelIds] : DEFAULT_MODELS).map((name) => ({
2199
- name,
2200
- model: name,
2201
- modified_at: (/* @__PURE__ */ new Date()).toISOString(),
2202
- size: 0,
2203
- digest: "",
2204
- details: {}
2205
- }));
2181
+ const body = renderOllamaTags(collect(fixtures, "all").ids, () => (/* @__PURE__ */ new Date()).toISOString());
2206
2182
  res.writeHead(200, { "Content-Type": "application/json" });
2207
- res.end(JSON.stringify({ models }));
2183
+ res.end(JSON.stringify(body));
2208
2184
  return;
2209
2185
  }
2210
2186
  const openRouterVideoContentMatch = pathname.match(OPENROUTER_VIDEO_CONTENT_RE);
@@ -2325,21 +2301,70 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2325
2301
  res.end(registry.serialize());
2326
2302
  return;
2327
2303
  }
2328
- if (pathname === MODELS_PATH && req.method === "GET") {
2304
+ const modelDetailMatch = pathname.match(MODELS_DETAIL_RE);
2305
+ const geminiModelDetailMatch = pathname.match(GEMINI_MODEL_DETAIL_RE);
2306
+ if (req.method === "GET" && (pathname === MODELS_PATH || pathname === GEMINI_MODELS_PATH || modelDetailMatch || geminiModelDetailMatch)) {
2329
2307
  setCorsHeaders(res);
2330
- const modelIds = /* @__PURE__ */ new Set();
2331
- for (const f of fixtures) if (f.match.model && typeof f.match.model === "string") modelIds.add(f.match.model);
2332
- const data = (modelIds.size > 0 ? [...modelIds] : DEFAULT_MODELS).map((id) => ({
2333
- id,
2334
- object: "model",
2335
- created: 1686935002,
2336
- owned_by: "aimock"
2337
- }));
2308
+ const google = pathname === GEMINI_MODELS_PATH || geminiModelDetailMatch !== null;
2309
+ const policy = resolvePolicy(defaults, req.headers);
2310
+ const anthropic = !google && (modelDetailMatch !== null || policy.modelCatalog === "native") && isAnthropicModelRequest(req.headers);
2311
+ const missingHeader = anthropic && policy.requiredHeaders === "enforce" ? missingRequiredHeader("messages", req.headers) : void 0;
2312
+ if (missingHeader) {
2313
+ writeErrorResponse(res, 400, renderError("messages", "thinking", {
2314
+ message: `${missingHeader}: header is required`,
2315
+ requestId
2316
+ }).body);
2317
+ return;
2318
+ }
2319
+ const ids = coreModelIds(collect(fixtures, "all").ids);
2320
+ const visible = google ? idsFor(ids, "google") : anthropic ? idsFor(ids, "anthropic") : ids;
2321
+ const catalogError = (status, message) => {
2322
+ const rendered = google ? renderError("gemini", "fixture", {
2323
+ bodyCode: status,
2324
+ status: status === 404 ? "NOT_FOUND" : "INVALID_ARGUMENT",
2325
+ message
2326
+ }) : anthropic ? renderError("messages", "thinking", {
2327
+ type: status === 404 ? "not_found_error" : "invalid_request_error",
2328
+ message
2329
+ }) : renderError("openai", "validation", { message });
2330
+ res.writeHead(status, {
2331
+ "Content-Type": "application/json",
2332
+ ...rendered.headers
2333
+ });
2334
+ res.end(rendered.body);
2335
+ };
2336
+ let body;
2337
+ const detail = modelDetailMatch ?? geminiModelDetailMatch;
2338
+ if (detail) {
2339
+ let id;
2340
+ try {
2341
+ id = decodeURIComponent(detail[1]);
2342
+ } catch {
2343
+ catalogError(404, "Model not found");
2344
+ return;
2345
+ }
2346
+ if (!visible.includes(id)) {
2347
+ catalogError(404, `Model not found: ${id}`);
2348
+ return;
2349
+ }
2350
+ body = google ? renderGeminiModel(id) : anthropic ? renderAnthropicModel(id) : renderOpenAIModel(id);
2351
+ } else if (google) {
2352
+ const result = geminiPage(visible, parsedUrl.searchParams);
2353
+ if (result.error !== void 0) {
2354
+ catalogError(400, result.error);
2355
+ return;
2356
+ }
2357
+ body = renderGeminiModels(result.page);
2358
+ } else if (anthropic) {
2359
+ const result = anthropicPage(visible, parsedUrl.searchParams);
2360
+ if (result.error !== void 0) {
2361
+ catalogError(400, result.error);
2362
+ return;
2363
+ }
2364
+ body = renderAnthropicModels(result.page);
2365
+ } else body = renderOpenAIModels(ids);
2338
2366
  res.writeHead(200, { "Content-Type": "application/json" });
2339
- res.end(JSON.stringify({
2340
- object: "list",
2341
- data
2342
- }));
2367
+ res.end(JSON.stringify(body));
2343
2368
  return;
2344
2369
  }
2345
2370
  const batchesCancelMatch = pathname.match(BATCHES_CANCEL_RE);
@@ -2597,10 +2622,10 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2597
2622
  if (limitParam) {
2598
2623
  const limit = Number(limitParam);
2599
2624
  if (!/^\+?\d+$/.test(limitParam.trim()) || !Number.isSafeInteger(limit) || limit <= 0) {
2600
- writeErrorResponse(res, 400, JSON.stringify({ error: {
2625
+ writeErrorResponse(res, 400, renderError("openai", "validation", {
2601
2626
  message: `Invalid limit parameter: "${limitParam}"`,
2602
2627
  type: "invalid_request_error"
2603
- } }));
2628
+ }).body);
2604
2629
  return;
2605
2630
  }
2606
2631
  opts = { limit };
@@ -2625,7 +2650,27 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2625
2650
  ensureRawFixturePositions();
2626
2651
  await handleResponses(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2627
2652
  } catch (err) {
2628
- routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
2653
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2654
+ }
2655
+ return;
2656
+ }
2657
+ const geminiCountMatch = pathname.match(GEMINI_COUNT_RE);
2658
+ if (req.method === "POST" && (pathname === MESSAGES_COUNT_PATH || geminiCountMatch)) {
2659
+ const google = geminiCountMatch !== null;
2660
+ try {
2661
+ handleCountTokens(req, res, await readBody(req), google, geminiCountMatch?.[1] ?? "", journal, defaults, setCorsHeaders);
2662
+ } catch (err) {
2663
+ routeError(req, res, err, pathname, {
2664
+ service: google ? "gemini" : "anthropic",
2665
+ envelope: (message, status) => google ? renderError("gemini", "fixture", {
2666
+ message,
2667
+ bodyCode: status,
2668
+ status: status === 400 ? "INVALID_ARGUMENT" : "INTERNAL"
2669
+ }).body : renderError("messages", "thinking", {
2670
+ message,
2671
+ type: status === 400 ? "invalid_request_error" : "api_error"
2672
+ }).body
2673
+ });
2629
2674
  }
2630
2675
  return;
2631
2676
  }
@@ -2635,7 +2680,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2635
2680
  ensureRawFixturePositions();
2636
2681
  await handleMessages(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2637
2682
  } catch (err) {
2638
- routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
2683
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2639
2684
  }
2640
2685
  return;
2641
2686
  }
@@ -2645,7 +2690,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2645
2690
  ensureRawFixturePositions();
2646
2691
  await handleCohere(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2647
2692
  } catch (err) {
2648
- routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
2693
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2649
2694
  }
2650
2695
  return;
2651
2696
  }
@@ -2811,7 +2856,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2811
2856
  ensureRawFixturePositions();
2812
2857
  await handleGeminiInteractions(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2813
2858
  } catch (err) {
2814
- routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
2859
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2815
2860
  }
2816
2861
  return;
2817
2862
  }
@@ -2836,7 +2881,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2836
2881
  ensureRawFixturePositions();
2837
2882
  await handleGemini(req, res, raw, geminiModel, streaming, fixtures, journal, defaults, setCorsHeaders);
2838
2883
  } catch (err) {
2839
- routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
2884
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2840
2885
  }
2841
2886
  return;
2842
2887
  }
@@ -2849,7 +2894,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2849
2894
  ensureRawFixturePositions();
2850
2895
  await handleGemini(req, res, raw, vertexModel, streaming, fixtures, journal, defaults, setCorsHeaders, "vertexai");
2851
2896
  } catch (err) {
2852
- routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
2897
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2853
2898
  }
2854
2899
  return;
2855
2900
  }
@@ -2965,10 +3010,10 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2965
3010
  try {
2966
3011
  voiceId = decodeURIComponent(elevenLabsVoiceMatch[1]);
2967
3012
  } catch {
2968
- writeErrorResponse(res, 400, JSON.stringify({ error: {
3013
+ writeErrorResponse(res, 400, renderError("openai", "validation", {
2969
3014
  message: `Invalid voice id '${elevenLabsVoiceMatch[1]}': malformed percent-encoding`,
2970
3015
  type: "invalid_request_error"
2971
- } }));
3016
+ }).body);
2972
3017
  journal.add({
2973
3018
  method: req.method ?? "GET",
2974
3019
  path: req.url ?? pathname,
@@ -3118,15 +3163,15 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
3118
3163
  await handleCompletions(req, res, fixtures, journal, defaults, () => lastJournalEntryFor(req), azureDeploymentId, completionsProvider, isOpenRouter, ensureRawFixturePositions, originalPathname.startsWith("/api/v3/"));
3119
3164
  } catch (err) {
3120
3165
  routeError(req, res, err, pathname, {
3121
- envelope: (m, status) => isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
3166
+ envelope: (m, status) => isOpenRouter ? serializeOpenRouterError(status, m) : renderError("openai", "route-failure", {
3122
3167
  message: m,
3123
3168
  type: errorTypeForStatus(status)
3124
- } }),
3169
+ }).body,
3125
3170
  ...isOpenRouter ? { toolCallEnvelope: (m, code) => serializeOpenRouterError(500, m, { reason: code }) } : {},
3126
- streamEvent: (m, status) => `data: ${isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
3171
+ streamEvent: (m, status) => `data: ${isOpenRouter ? serializeOpenRouterError(status, m) : renderError("openai", "route-failure", {
3127
3172
  message: m,
3128
3173
  type: errorTypeForStatus(status)
3129
- } })}\n\n`
3174
+ }).body}\n\n`
3130
3175
  });
3131
3176
  }
3132
3177
  }