@copilotkit/aimock 1.45.0 → 1.46.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 (426) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +24 -0
  4. package/README.md +7 -1
  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.ts.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/batches.cjs +2 -4
  25. package/dist/batches.cjs.map +1 -1
  26. package/dist/batches.d.cts.map +1 -1
  27. package/dist/batches.d.ts.map +1 -1
  28. package/dist/batches.js +2 -4
  29. package/dist/batches.js.map +1 -1
  30. package/dist/bedrock-converse.cjs +61 -80
  31. package/dist/bedrock-converse.cjs.map +1 -1
  32. package/dist/bedrock-converse.d.cts.map +1 -1
  33. package/dist/bedrock-converse.d.ts.map +1 -1
  34. package/dist/bedrock-converse.js +58 -77
  35. package/dist/bedrock-converse.js.map +1 -1
  36. package/dist/bedrock.cjs +65 -97
  37. package/dist/bedrock.cjs.map +1 -1
  38. package/dist/bedrock.d.cts.map +1 -1
  39. package/dist/bedrock.d.ts.map +1 -1
  40. package/dist/bedrock.js +62 -94
  41. package/dist/bedrock.js.map +1 -1
  42. package/dist/byteplus-video.cjs +11 -10
  43. package/dist/byteplus-video.cjs.map +1 -1
  44. package/dist/byteplus-video.d.cts.map +1 -1
  45. package/dist/byteplus-video.d.ts.map +1 -1
  46. package/dist/byteplus-video.js +5 -4
  47. package/dist/byteplus-video.js.map +1 -1
  48. package/dist/chaos.cjs +5 -4
  49. package/dist/chaos.cjs.map +1 -1
  50. package/dist/chaos.d.cts.map +1 -1
  51. package/dist/chaos.d.ts.map +1 -1
  52. package/dist/chaos.js +5 -4
  53. package/dist/chaos.js.map +1 -1
  54. package/dist/cli.cjs +15 -0
  55. package/dist/cli.cjs.map +1 -1
  56. package/dist/cli.js +15 -0
  57. package/dist/cli.js.map +1 -1
  58. package/dist/cohere.cjs +49 -74
  59. package/dist/cohere.cjs.map +1 -1
  60. package/dist/cohere.d.cts.map +1 -1
  61. package/dist/cohere.d.ts.map +1 -1
  62. package/dist/cohere.js +47 -72
  63. package/dist/cohere.js.map +1 -1
  64. package/dist/config-loader.cjs +29 -1
  65. package/dist/config-loader.cjs.map +1 -1
  66. package/dist/config-loader.d.cts +7 -1
  67. package/dist/config-loader.d.cts.map +1 -1
  68. package/dist/config-loader.d.ts +7 -1
  69. package/dist/config-loader.d.ts.map +1 -1
  70. package/dist/config-loader.js +29 -2
  71. package/dist/config-loader.js.map +1 -1
  72. package/dist/count-tokens.cjs +146 -0
  73. package/dist/count-tokens.cjs.map +1 -0
  74. package/dist/count-tokens.js +146 -0
  75. package/dist/count-tokens.js.map +1 -0
  76. package/dist/elevenlabs-audio.cjs +36 -34
  77. package/dist/elevenlabs-audio.cjs.map +1 -1
  78. package/dist/elevenlabs-audio.d.cts.map +1 -1
  79. package/dist/elevenlabs-audio.d.ts.map +1 -1
  80. package/dist/elevenlabs-audio.js +33 -31
  81. package/dist/elevenlabs-audio.js.map +1 -1
  82. package/dist/elevenlabs-voice.cjs +38 -37
  83. package/dist/elevenlabs-voice.cjs.map +1 -1
  84. package/dist/elevenlabs-voice.d.cts.map +1 -1
  85. package/dist/elevenlabs-voice.d.ts.map +1 -1
  86. package/dist/elevenlabs-voice.js +33 -32
  87. package/dist/elevenlabs-voice.js.map +1 -1
  88. package/dist/embedding-request.cjs +16 -0
  89. package/dist/embedding-request.cjs.map +1 -0
  90. package/dist/embedding-request.js +14 -0
  91. package/dist/embedding-request.js.map +1 -0
  92. package/dist/embeddings.cjs +30 -20
  93. package/dist/embeddings.cjs.map +1 -1
  94. package/dist/embeddings.d.cts.map +1 -1
  95. package/dist/embeddings.d.ts.map +1 -1
  96. package/dist/embeddings.js +30 -20
  97. package/dist/embeddings.js.map +1 -1
  98. package/dist/fal-audio.cjs +45 -43
  99. package/dist/fal-audio.cjs.map +1 -1
  100. package/dist/fal-audio.d.cts.map +1 -1
  101. package/dist/fal-audio.d.ts.map +1 -1
  102. package/dist/fal-audio.js +42 -40
  103. package/dist/fal-audio.js.map +1 -1
  104. package/dist/fal.cjs +17 -15
  105. package/dist/fal.cjs.map +1 -1
  106. package/dist/fal.js +16 -14
  107. package/dist/fal.js.map +1 -1
  108. package/dist/files.cjs +2 -4
  109. package/dist/files.cjs.map +1 -1
  110. package/dist/files.d.cts.map +1 -1
  111. package/dist/files.d.ts.map +1 -1
  112. package/dist/files.js +2 -4
  113. package/dist/files.js.map +1 -1
  114. package/dist/fine-tuning.cjs +2 -4
  115. package/dist/fine-tuning.cjs.map +1 -1
  116. package/dist/fine-tuning.d.cts.map +1 -1
  117. package/dist/fine-tuning.d.ts.map +1 -1
  118. package/dist/fine-tuning.js +2 -4
  119. package/dist/fine-tuning.js.map +1 -1
  120. package/dist/fixture-loader.cjs +37 -42
  121. package/dist/fixture-loader.cjs.map +1 -1
  122. package/dist/fixture-loader.d.cts.map +1 -1
  123. package/dist/fixture-loader.d.ts.map +1 -1
  124. package/dist/fixture-loader.js +37 -42
  125. package/dist/fixture-loader.js.map +1 -1
  126. package/dist/gemini-embeddings.cjs +26 -24
  127. package/dist/gemini-embeddings.cjs.map +1 -1
  128. package/dist/gemini-embeddings.js +26 -24
  129. package/dist/gemini-embeddings.js.map +1 -1
  130. package/dist/gemini-interactions.cjs +50 -35
  131. package/dist/gemini-interactions.cjs.map +1 -1
  132. package/dist/gemini-interactions.d.cts +0 -1
  133. package/dist/gemini-interactions.d.cts.map +1 -1
  134. package/dist/gemini-interactions.d.ts +0 -1
  135. package/dist/gemini-interactions.d.ts.map +1 -1
  136. package/dist/gemini-interactions.js +49 -34
  137. package/dist/gemini-interactions.js.map +1 -1
  138. package/dist/gemini.cjs +57 -67
  139. package/dist/gemini.cjs.map +1 -1
  140. package/dist/gemini.d.cts.map +1 -1
  141. package/dist/gemini.d.ts.map +1 -1
  142. package/dist/gemini.js +56 -67
  143. package/dist/gemini.js.map +1 -1
  144. package/dist/grok-video.cjs +14 -12
  145. package/dist/grok-video.cjs.map +1 -1
  146. package/dist/grok-video.d.cts.map +1 -1
  147. package/dist/grok-video.d.ts.map +1 -1
  148. package/dist/grok-video.js +8 -6
  149. package/dist/grok-video.js.map +1 -1
  150. package/dist/helpers.cjs +11 -68
  151. package/dist/helpers.cjs.map +1 -1
  152. package/dist/helpers.d.cts.map +1 -1
  153. package/dist/helpers.d.ts.map +1 -1
  154. package/dist/helpers.js +12 -67
  155. package/dist/helpers.js.map +1 -1
  156. package/dist/images.cjs +30 -28
  157. package/dist/images.cjs.map +1 -1
  158. package/dist/images.d.cts.map +1 -1
  159. package/dist/images.d.ts.map +1 -1
  160. package/dist/images.js +27 -25
  161. package/dist/images.js.map +1 -1
  162. package/dist/index.d.cts +2 -2
  163. package/dist/index.d.ts +2 -2
  164. package/dist/journal.cjs +3 -1
  165. package/dist/journal.cjs.map +1 -1
  166. package/dist/journal.d.cts.map +1 -1
  167. package/dist/journal.d.ts.map +1 -1
  168. package/dist/journal.js +2 -1
  169. package/dist/journal.js.map +1 -1
  170. package/dist/llmock.cjs +3 -0
  171. package/dist/llmock.cjs.map +1 -1
  172. package/dist/llmock.d.cts.map +1 -1
  173. package/dist/llmock.d.ts.map +1 -1
  174. package/dist/llmock.js +3 -0
  175. package/dist/llmock.js.map +1 -1
  176. package/dist/mcp-fakes.d.cts.map +1 -1
  177. package/dist/mcp-handler.cjs +14 -4
  178. package/dist/mcp-handler.cjs.map +1 -1
  179. package/dist/mcp-handler.js +14 -4
  180. package/dist/mcp-handler.js.map +1 -1
  181. package/dist/mcp-recorder.cjs +2 -1
  182. package/dist/mcp-recorder.cjs.map +1 -1
  183. package/dist/mcp-recorder.js +3 -2
  184. package/dist/mcp-recorder.js.map +1 -1
  185. package/dist/messages.cjs +111 -96
  186. package/dist/messages.cjs.map +1 -1
  187. package/dist/messages.d.cts.map +1 -1
  188. package/dist/messages.d.ts.map +1 -1
  189. package/dist/messages.js +109 -95
  190. package/dist/messages.js.map +1 -1
  191. package/dist/metrics.cjs +5 -2
  192. package/dist/metrics.cjs.map +1 -1
  193. package/dist/metrics.d.cts.map +1 -1
  194. package/dist/metrics.d.ts.map +1 -1
  195. package/dist/metrics.js +6 -3
  196. package/dist/metrics.js.map +1 -1
  197. package/dist/model-utils.cjs +1 -0
  198. package/dist/model-utils.cjs.map +1 -1
  199. package/dist/model-utils.js +1 -1
  200. package/dist/model-utils.js.map +1 -1
  201. package/dist/moderation.cjs +15 -12
  202. package/dist/moderation.cjs.map +1 -1
  203. package/dist/moderation.js +14 -11
  204. package/dist/moderation.js.map +1 -1
  205. package/dist/ollama.cjs +128 -68
  206. package/dist/ollama.cjs.map +1 -1
  207. package/dist/ollama.d.cts.map +1 -1
  208. package/dist/ollama.d.ts.map +1 -1
  209. package/dist/ollama.js +124 -64
  210. package/dist/ollama.js.map +1 -1
  211. package/dist/openapi.cjs +323 -3
  212. package/dist/openapi.cjs.map +1 -1
  213. package/dist/openapi.d.cts.map +1 -1
  214. package/dist/openapi.d.ts.map +1 -1
  215. package/dist/openapi.js +324 -4
  216. package/dist/openapi.js.map +1 -1
  217. package/dist/openrouter-chat.cjs +11 -155
  218. package/dist/openrouter-chat.cjs.map +1 -1
  219. package/dist/openrouter-chat.js +11 -155
  220. package/dist/openrouter-chat.js.map +1 -1
  221. package/dist/openrouter-video.cjs +76 -71
  222. package/dist/openrouter-video.cjs.map +1 -1
  223. package/dist/openrouter-video.d.cts.map +1 -1
  224. package/dist/openrouter-video.d.ts.map +1 -1
  225. package/dist/openrouter-video.js +63 -58
  226. package/dist/openrouter-video.js.map +1 -1
  227. package/dist/recorder.cjs +176 -95
  228. package/dist/recorder.cjs.map +1 -1
  229. package/dist/recorder.d.cts.map +1 -1
  230. package/dist/recorder.d.ts.map +1 -1
  231. package/dist/recorder.js +176 -95
  232. package/dist/recorder.js.map +1 -1
  233. package/dist/rerank.cjs +13 -11
  234. package/dist/rerank.cjs.map +1 -1
  235. package/dist/rerank.js +12 -10
  236. package/dist/rerank.js.map +1 -1
  237. package/dist/response/catalog/anthropic-page.cjs +39 -0
  238. package/dist/response/catalog/anthropic-page.cjs.map +1 -0
  239. package/dist/response/catalog/anthropic-page.js +38 -0
  240. package/dist/response/catalog/anthropic-page.js.map +1 -0
  241. package/dist/response/catalog/collect.cjs +61 -0
  242. package/dist/response/catalog/collect.cjs.map +1 -0
  243. package/dist/response/catalog/collect.js +59 -0
  244. package/dist/response/catalog/collect.js.map +1 -0
  245. package/dist/response/catalog/gemini-page.cjs +25 -0
  246. package/dist/response/catalog/gemini-page.cjs.map +1 -0
  247. package/dist/response/catalog/gemini-page.js +24 -0
  248. package/dist/response/catalog/gemini-page.js.map +1 -0
  249. package/dist/response/catalog/render.cjs +182 -0
  250. package/dist/response/catalog/render.cjs.map +1 -0
  251. package/dist/response/catalog/render.js +171 -0
  252. package/dist/response/catalog/render.js.map +1 -0
  253. package/dist/response/errors.cjs +225 -0
  254. package/dist/response/errors.cjs.map +1 -0
  255. package/dist/response/errors.js +224 -0
  256. package/dist/response/errors.js.map +1 -0
  257. package/dist/response/finish.cjs +39 -0
  258. package/dist/response/finish.cjs.map +1 -0
  259. package/dist/response/finish.js +35 -0
  260. package/dist/response/finish.js.map +1 -0
  261. package/dist/response/policy.cjs +126 -0
  262. package/dist/response/policy.cjs.map +1 -0
  263. package/dist/response/policy.js +120 -0
  264. package/dist/response/policy.js.map +1 -0
  265. package/dist/response/profiles.cjs +69 -0
  266. package/dist/response/profiles.cjs.map +1 -0
  267. package/dist/response/profiles.js +65 -0
  268. package/dist/response/profiles.js.map +1 -0
  269. package/dist/response/usage/codecs/anthropic.cjs +52 -0
  270. package/dist/response/usage/codecs/anthropic.cjs.map +1 -0
  271. package/dist/response/usage/codecs/anthropic.js +50 -0
  272. package/dist/response/usage/codecs/anthropic.js.map +1 -0
  273. package/dist/response/usage/codecs/cohere.cjs +36 -0
  274. package/dist/response/usage/codecs/cohere.cjs.map +1 -0
  275. package/dist/response/usage/codecs/cohere.js +35 -0
  276. package/dist/response/usage/codecs/cohere.js.map +1 -0
  277. package/dist/response/usage/codecs/converse.cjs +16 -0
  278. package/dist/response/usage/codecs/converse.cjs.map +1 -0
  279. package/dist/response/usage/codecs/converse.js +15 -0
  280. package/dist/response/usage/codecs/converse.js.map +1 -0
  281. package/dist/response/usage/codecs/gemini.cjs +31 -0
  282. package/dist/response/usage/codecs/gemini.cjs.map +1 -0
  283. package/dist/response/usage/codecs/gemini.js +31 -0
  284. package/dist/response/usage/codecs/gemini.js.map +1 -0
  285. package/dist/response/usage/codecs/interactions.cjs +22 -0
  286. package/dist/response/usage/codecs/interactions.cjs.map +1 -0
  287. package/dist/response/usage/codecs/interactions.js +21 -0
  288. package/dist/response/usage/codecs/interactions.js.map +1 -0
  289. package/dist/response/usage/codecs/ollama.cjs +17 -0
  290. package/dist/response/usage/codecs/ollama.cjs.map +1 -0
  291. package/dist/response/usage/codecs/ollama.js +17 -0
  292. package/dist/response/usage/codecs/ollama.js.map +1 -0
  293. package/dist/response/usage/codecs/openai.cjs +52 -0
  294. package/dist/response/usage/codecs/openai.cjs.map +1 -0
  295. package/dist/response/usage/codecs/openai.js +50 -0
  296. package/dist/response/usage/codecs/openai.js.map +1 -0
  297. package/dist/response/usage/codecs/openrouter.cjs +68 -0
  298. package/dist/response/usage/codecs/openrouter.cjs.map +1 -0
  299. package/dist/response/usage/codecs/openrouter.js +67 -0
  300. package/dist/response/usage/codecs/openrouter.js.map +1 -0
  301. package/dist/response/usage/codecs/realtime.cjs +19 -0
  302. package/dist/response/usage/codecs/realtime.cjs.map +1 -0
  303. package/dist/response/usage/codecs/realtime.js +18 -0
  304. package/dist/response/usage/codecs/realtime.js.map +1 -0
  305. package/dist/response/usage/codecs/responses.cjs +21 -0
  306. package/dist/response/usage/codecs/responses.cjs.map +1 -0
  307. package/dist/response/usage/codecs/responses.js +20 -0
  308. package/dist/response/usage/codecs/responses.js.map +1 -0
  309. package/dist/response/usage/estimate.cjs +23 -0
  310. package/dist/response/usage/estimate.cjs.map +1 -0
  311. package/dist/response/usage/estimate.js +21 -0
  312. package/dist/response/usage/estimate.js.map +1 -0
  313. package/dist/response/usage/fields.cjs +72 -0
  314. package/dist/response/usage/fields.cjs.map +1 -0
  315. package/dist/response/usage/fields.js +70 -0
  316. package/dist/response/usage/fields.js.map +1 -0
  317. package/dist/response/usage/recording.cjs +52 -0
  318. package/dist/response/usage/recording.cjs.map +1 -0
  319. package/dist/response/usage/recording.js +52 -0
  320. package/dist/response/usage/recording.js.map +1 -0
  321. package/dist/responses.cjs +45 -58
  322. package/dist/responses.cjs.map +1 -1
  323. package/dist/responses.d.cts.map +1 -1
  324. package/dist/responses.d.ts.map +1 -1
  325. package/dist/responses.js +44 -57
  326. package/dist/responses.js.map +1 -1
  327. package/dist/route-registry.cjs +45 -0
  328. package/dist/route-registry.cjs.map +1 -1
  329. package/dist/route-registry.d.cts.map +1 -1
  330. package/dist/route-registry.d.ts.map +1 -1
  331. package/dist/route-registry.js +41 -1
  332. package/dist/route-registry.js.map +1 -1
  333. package/dist/router.cjs +4 -0
  334. package/dist/router.cjs.map +1 -1
  335. package/dist/router.d.cts.map +1 -1
  336. package/dist/router.d.ts.map +1 -1
  337. package/dist/router.js +3 -0
  338. package/dist/router.js.map +1 -1
  339. package/dist/search.cjs +13 -11
  340. package/dist/search.cjs.map +1 -1
  341. package/dist/search.js +12 -10
  342. package/dist/search.js.map +1 -1
  343. package/dist/server.cjs +222 -146
  344. package/dist/server.cjs.map +1 -1
  345. package/dist/server.d.cts.map +1 -1
  346. package/dist/server.d.ts.map +1 -1
  347. package/dist/server.js +223 -147
  348. package/dist/server.js.map +1 -1
  349. package/dist/speech.cjs +20 -18
  350. package/dist/speech.cjs.map +1 -1
  351. package/dist/speech.d.cts.map +1 -1
  352. package/dist/speech.d.ts.map +1 -1
  353. package/dist/speech.js +19 -17
  354. package/dist/speech.js.map +1 -1
  355. package/dist/sse-writer.cjs +7 -0
  356. package/dist/sse-writer.cjs.map +1 -1
  357. package/dist/sse-writer.d.cts.map +1 -1
  358. package/dist/sse-writer.d.ts.map +1 -1
  359. package/dist/sse-writer.js +7 -1
  360. package/dist/sse-writer.js.map +1 -1
  361. package/dist/stream-collapse.cjs +118 -11
  362. package/dist/stream-collapse.cjs.map +1 -1
  363. package/dist/stream-collapse.d.cts +10 -9
  364. package/dist/stream-collapse.d.cts.map +1 -1
  365. package/dist/stream-collapse.d.ts +10 -9
  366. package/dist/stream-collapse.d.ts.map +1 -1
  367. package/dist/stream-collapse.js +118 -11
  368. package/dist/stream-collapse.js.map +1 -1
  369. package/dist/transcription.cjs +12 -10
  370. package/dist/transcription.cjs.map +1 -1
  371. package/dist/transcription.d.cts.map +1 -1
  372. package/dist/transcription.d.ts.map +1 -1
  373. package/dist/transcription.js +11 -9
  374. package/dist/transcription.js.map +1 -1
  375. package/dist/types.cjs.map +1 -1
  376. package/dist/types.d.cts +62 -3
  377. package/dist/types.d.cts.map +1 -1
  378. package/dist/types.d.ts +62 -3
  379. package/dist/types.d.ts.map +1 -1
  380. package/dist/types.js.map +1 -1
  381. package/dist/vector-mock.cjs +3 -8
  382. package/dist/vector-mock.cjs.map +1 -1
  383. package/dist/vector-mock.d.cts.map +1 -1
  384. package/dist/vector-mock.d.ts.map +1 -1
  385. package/dist/vector-mock.js +3 -8
  386. package/dist/vector-mock.js.map +1 -1
  387. package/dist/vector-stores.cjs +2 -4
  388. package/dist/vector-stores.cjs.map +1 -1
  389. package/dist/vector-stores.d.cts.map +1 -1
  390. package/dist/vector-stores.d.ts.map +1 -1
  391. package/dist/vector-stores.js +2 -4
  392. package/dist/vector-stores.js.map +1 -1
  393. package/dist/veo-video.cjs +16 -14
  394. package/dist/veo-video.cjs.map +1 -1
  395. package/dist/veo-video.js +10 -8
  396. package/dist/veo-video.js.map +1 -1
  397. package/dist/video.cjs +21 -19
  398. package/dist/video.cjs.map +1 -1
  399. package/dist/video.d.cts.map +1 -1
  400. package/dist/video.d.ts.map +1 -1
  401. package/dist/video.js +20 -18
  402. package/dist/video.js.map +1 -1
  403. package/dist/ws-gemini-live.cjs +11 -3
  404. package/dist/ws-gemini-live.cjs.map +1 -1
  405. package/dist/ws-gemini-live.d.cts +0 -1
  406. package/dist/ws-gemini-live.d.cts.map +1 -1
  407. package/dist/ws-gemini-live.d.ts +0 -1
  408. package/dist/ws-gemini-live.d.ts.map +1 -1
  409. package/dist/ws-gemini-live.js +10 -2
  410. package/dist/ws-gemini-live.js.map +1 -1
  411. package/dist/ws-live.cjs +6 -5
  412. package/dist/ws-live.cjs.map +1 -1
  413. package/dist/ws-live.js +5 -4
  414. package/dist/ws-live.js.map +1 -1
  415. package/dist/ws-realtime.cjs +28 -54
  416. package/dist/ws-realtime.cjs.map +1 -1
  417. package/dist/ws-realtime.d.cts.map +1 -1
  418. package/dist/ws-realtime.d.ts.map +1 -1
  419. package/dist/ws-realtime.js +25 -51
  420. package/dist/ws-realtime.js.map +1 -1
  421. package/dist/ws-responses.cjs +8 -6
  422. package/dist/ws-responses.cjs.map +1 -1
  423. package/dist/ws-responses.js +7 -5
  424. package/dist/ws-responses.js.map +1 -1
  425. package/package.json +4 -1
  426. package/skills/write-fixtures/SKILL.md +25 -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, isResponseFactory, isServerOwnedFixtures, isTextResponse, isToolCallResponse, isTranscriptionResponse, isVideoResponse, markMintedRequestId, prepareOpenAIChatMisbehavior, readBody, readBodyBufferBounded, 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 { calculateTotalDelay, delay, writeErrorResponse, writeSSEStream } 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";
@@ -12,10 +15,15 @@ import { proxyAndRecord } from "./recorder.js";
12
15
  import { Logger } from "./logger.js";
13
16
  import { Journal } from "./journal.js";
14
17
  import { createInterruptionSignal } from "./interruption.js";
18
+ import { collect, idsFor } from "./response/catalog/collect.js";
19
+ import { anthropicPage } from "./response/catalog/anthropic-page.js";
20
+ import { geminiPage } from "./response/catalog/gemini-page.js";
21
+ import { coreModelIds, renderAnthropicModel, renderAnthropicModels, renderGeminiModel, renderGeminiModels, renderOllamaTags, renderOpenAIModel, renderOpenAIModels } from "./response/catalog/render.js";
15
22
  import { buildOpenRouterCandidates, handleOpenRouterCredits, handleOpenRouterKey, handleOpenRouterModels, isOpenRouterPath, resolveOpenRouterShaping, serializeOpenRouterError, shapeOpenRouterChunks, shapeOpenRouterCompletion } from "./openrouter-chat.js";
16
23
  import { handleResponses } from "./responses.js";
17
24
  import { handleMessages } from "./messages.js";
18
25
  import { handleGemini } from "./gemini.js";
26
+ import { handleCountTokens } from "./count-tokens.js";
19
27
  import { handleGeminiEmbedContent } from "./gemini-embeddings.js";
20
28
  import { handleBedrock, handleBedrockStream } from "./bedrock.js";
21
29
  import { handleConverse, handleConverseStream } from "./bedrock-converse.js";
@@ -47,7 +55,7 @@ import { handleLiveSession } from "./ws-live.js";
47
55
  import { handleWebSocketResponses } from "./ws-responses.js";
48
56
  import { handleWebSocketRealtime } from "./ws-realtime.js";
49
57
  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";
58
+ 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
59
  import { CATALOG_ROUTES, buildOpenApiDocument } from "./openapi.js";
52
60
  import { DESTROYED_STATUS_LABEL, createMetricsRegistry, normalizePathLabel } from "./metrics.js";
53
61
  import { MCP_FAKES_DEFAULT_MOUNT, McpFakeStore, McpFakesAddError, blockIdOf } from "./mcp-fakes.js";
@@ -87,13 +95,6 @@ function normalizeCompatPath(pathname, logger) {
87
95
  }
88
96
  return pathname;
89
97
  }
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
98
  const CORS_HEADERS = {
98
99
  "Access-Control-Allow-Origin": "*",
99
100
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
@@ -110,10 +111,10 @@ function handleOptions(res) {
110
111
  }
111
112
  function handleNotFound(res, message) {
112
113
  setCorsHeaders(res);
113
- writeErrorResponse(res, 404, JSON.stringify({ error: {
114
+ writeErrorResponse(res, 404, renderError("openai", "route-failure", {
114
115
  message,
115
116
  type: "not_found"
116
- } }));
117
+ }).body);
117
118
  }
118
119
  /** The complete `GET /__aimock/fixtures` query-param vocabulary; anything else 400s. */
119
120
  const FIXTURES_PARAMS = new Set(["include"]);
@@ -924,10 +925,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
924
925
  fixture: null
925
926
  }
926
927
  });
927
- writeErrorResponse(res, status, openRouter ? serializeOpenRouterError(status, `Request body read failed: ${msg}`) : JSON.stringify({ error: {
928
+ writeErrorResponse(res, status, openRouter ? serializeOpenRouterError(status, `Request body read failed: ${msg}`) : renderError("openai", "transport-failure", {
928
929
  message: `Request body read failed: ${msg}`,
929
930
  type: clientFault ? "invalid_request_error" : "server_error"
930
- } }));
931
+ }).body);
931
932
  return;
932
933
  }
933
934
  let body;
@@ -945,12 +946,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
945
946
  fixture: null
946
947
  }
947
948
  });
948
- writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, `Malformed JSON: ${detail}`) : JSON.stringify({ error: {
949
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, `Malformed JSON: ${detail}`) : renderError("openai", "validation", {
949
950
  message: `Malformed JSON: ${detail}`,
950
951
  type: "invalid_request_error",
951
952
  param: null,
952
- code: "invalid_json"
953
- } }));
953
+ code: "invalid_json",
954
+ includeParam: true
955
+ }).body);
954
956
  return;
955
957
  }
956
958
  if (!isJsonObject(body)) {
@@ -964,10 +966,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
964
966
  fixture: null
965
967
  }
966
968
  });
967
- writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Request body must be a JSON object") : JSON.stringify({ error: {
969
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Request body must be a JSON object") : renderError("openai", "validation", {
968
970
  message: "Request body must be a JSON object",
969
971
  type: "invalid_request_error"
970
- } }));
972
+ }).body);
971
973
  return;
972
974
  }
973
975
  if (modelFallback && !body.model) body.model = modelFallback;
@@ -982,12 +984,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
982
984
  fixture: null
983
985
  }
984
986
  });
985
- writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Missing required parameter: 'messages'") : JSON.stringify({ error: {
987
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Missing required parameter: 'messages'") : renderError("openai", "validation", {
986
988
  message: "Missing required parameter: 'messages'",
987
989
  type: "invalid_request_error",
988
990
  param: null,
989
- code: null
990
- } }));
991
+ code: null,
992
+ includeParam: true
993
+ }).body);
991
994
  return;
992
995
  }
993
996
  const nullMessageIndex = body.messages.findIndex((message) => message === null);
@@ -1003,12 +1006,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1003
1006
  fixture: null
1004
1007
  }
1005
1008
  });
1006
- writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, message) : JSON.stringify({ error: {
1009
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, message) : renderError("openai", "validation", {
1007
1010
  message,
1008
1011
  type: "invalid_request_error",
1009
1012
  param: null,
1010
- code: null
1011
- } }));
1013
+ code: null,
1014
+ includeParam: true
1015
+ }).body);
1012
1016
  return;
1013
1017
  }
1014
1018
  if (new URL(req.url ?? COMPLETIONS_PATH, "http://localhost").pathname === COMPLETIONS_PATH) {
@@ -1028,10 +1032,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1028
1032
  fixture: null
1029
1033
  }
1030
1034
  });
1031
- writeErrorResponse(res, 400, JSON.stringify({ error: {
1035
+ writeErrorResponse(res, 400, renderError("openai", "validation", {
1032
1036
  message: toolsError,
1033
1037
  type: "invalid_request_error"
1034
- } }));
1038
+ }).body);
1035
1039
  return;
1036
1040
  }
1037
1041
  }
@@ -1131,8 +1135,9 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1131
1135
  headers: flatHeaders,
1132
1136
  body
1133
1137
  };
1134
- const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1135
- const missWouldProxy = wouldProxyMiss(effectiveStrict, defaults.record, providerKey);
1138
+ const missDecisions = resolveRequestDecisions(defaults.strict, req.headers);
1139
+ const effectiveStrict = missDecisions.fixtureMiss;
1140
+ const missWouldProxy = wouldProxyMiss(!missDecisions.upstream, defaults.record, providerKey);
1136
1141
  const noFixtureSource = missWouldProxy ? "proxy" : "internal";
1137
1142
  const recordBypass = (reason) => {
1138
1143
  const entry = getRequestEntry();
@@ -1179,12 +1184,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1179
1184
  ...strictOverrideField(defaults.strict, req.headers)
1180
1185
  }
1181
1186
  });
1182
- writeErrorResponse(res, strictStatus, openRouter ? serializeOpenRouterError(strictStatus, strictMessage) : JSON.stringify({ error: {
1187
+ writeErrorResponse(res, strictStatus, openRouter ? serializeOpenRouterError(strictStatus, strictMessage) : renderError("openai", "strict-miss", {
1183
1188
  message: strictMessage,
1184
1189
  type: "invalid_request_error",
1185
1190
  param: null,
1186
- code: "no_fixture_match"
1187
- } }));
1191
+ code: "no_fixture_match",
1192
+ includeParam: true
1193
+ }).body);
1188
1194
  return;
1189
1195
  }
1190
1196
  if (defaults.record && providerKey) {
@@ -1254,12 +1260,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1254
1260
  ...strictOverrideField(defaults.strict, req.headers)
1255
1261
  }
1256
1262
  });
1257
- writeErrorResponse(res, 404, openRouter ? serializeOpenRouterError(404, "No fixture matched") : JSON.stringify({ error: {
1263
+ writeErrorResponse(res, 404, openRouter ? serializeOpenRouterError(404, "No fixture matched") : renderError("openai", "fixture-miss", {
1258
1264
  message: "No fixture matched",
1259
1265
  type: "invalid_request_error",
1260
1266
  param: null,
1261
- code: "no_fixture_match"
1262
- } }));
1267
+ code: "no_fixture_match",
1268
+ includeParam: true
1269
+ }).body);
1263
1270
  return;
1264
1271
  }
1265
1272
  const resolvedResponse = preResolvedResponse ?? await resolveResponse(fixture, body);
@@ -1290,12 +1297,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1290
1297
  testId
1291
1298
  });
1292
1299
  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: {
1300
+ writeErrorResponse(res, evaluation.status, openRouter ? serializeOpenRouterError(evaluation.status, `${evaluation.code}: ${evaluation.message}`) : renderError("openai", "misbehavior", {
1294
1301
  message: evaluation.message,
1295
1302
  type: "invalid_request_error",
1296
1303
  param: null,
1297
- code: evaluation.code
1298
- } }));
1304
+ code: evaluation.code,
1305
+ includeParam: true
1306
+ }).body);
1299
1307
  return;
1300
1308
  }
1301
1309
  const appliedPlan = evaluation.kind === "applied" ? prepareOpenAIChatMisbehavior(evaluation) : void 0;
@@ -1309,22 +1317,28 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1309
1317
  testId
1310
1318
  });
1311
1319
  };
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;
1320
+ 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
1321
  const appliedUsage = appliedPlan ? resolveOpenAIChatMisbehaviorUsage(appliedPlan, body, appliedPlan.reasoning === void 0 ? appliedReasoning !== void 0 : openRouter) : void 0;
1314
1322
  const latency = fixture.latency ?? defaults.latency;
1315
1323
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
1316
1324
  const emitStreamingUsage = body.stream === true && body.stream_options?.include_usage === true || openRouter;
1317
1325
  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
1326
  const resolveStreamingUsageTokens = (overrides, completionText) => appliedUsage ?? resolveUsage(overrides, streamingPromptText, completionText);
1319
- const resolveRecordedTimings = (chunks) => {
1327
+ const resolveRecordedTimings = (chunkCount) => {
1320
1328
  const timings = fixture.recordedTimings;
1321
1329
  if (!appliedPlan?.duplicateId || !timings) return timings;
1322
1330
  const gaps = timings.interChunkDelaysMs;
1323
1331
  return {
1324
1332
  ...timings,
1325
- interChunkDelaysMs: Array.from({ length: Math.max(gaps.length, chunks.length - 1) }, (_, index) => gaps[index] ?? gaps.at(-1) ?? 0)
1333
+ interChunkDelaysMs: Array.from({ length: Math.max(gaps.length, chunkCount - 1) }, (_, index) => gaps[index] ?? gaps.at(-1) ?? 0)
1326
1334
  };
1327
1335
  };
1336
+ const awaitNonStreamingTiming = (contentLength) => {
1337
+ if (fixture.nonStreamingTiming !== true) return Promise.resolve();
1338
+ if (!fixture.streamingProfile && !fixture.recordedTimings) return Promise.resolve();
1339
+ const chunkCount = Math.max(1, Math.ceil(contentLength / chunkSize));
1340
+ return delay(calculateTotalDelay(contentLength, chunkSize, fixture.streamingProfile, resolveRecordedTimings(chunkCount), fixture.replaySpeed ?? defaults.replaySpeed));
1341
+ };
1328
1342
  const shapeORCompletion = (completion, overrides) => {
1329
1343
  if (appliedUsage) completion.usage = appliedUsage;
1330
1344
  if (!openRouter) return completion;
@@ -1363,10 +1377,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1363
1377
  fixture
1364
1378
  }
1365
1379
  }));
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: {
1380
+ 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
1381
  message: "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.",
1368
1382
  type: "invalid_request_error"
1369
- } }));
1383
+ }).body);
1370
1384
  return;
1371
1385
  }
1372
1386
  if (appliedPlan && isTextResponse(response) && (appliedPlan.stop === "refusal" || appliedPlan.stop === "content_filter" || appliedPlan.reasoning !== void 0)) {
@@ -1384,6 +1398,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1384
1398
  recordOutcome(entry);
1385
1399
  if (body.stream !== true) {
1386
1400
  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);
1401
+ await awaitNonStreamingTiming((appliedPlan.stop === "refusal" ? appliedPlan.refusal ?? "" : appliedPlan.stop === "content_filter" ? "" : appliedPlan.reasoning ?? "").length);
1387
1402
  res.writeHead(200, { "Content-Type": "application/json" });
1388
1403
  res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1389
1404
  } else {
@@ -1397,7 +1412,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1397
1412
  signal: interruption?.signal,
1398
1413
  onChunkSent: interruption?.tick,
1399
1414
  usageChunk,
1400
- recordedTimings: resolveRecordedTimings(chunks),
1415
+ recordedTimings: resolveRecordedTimings(chunks.length),
1401
1416
  replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1402
1417
  openRouterProcessing
1403
1418
  })) {
@@ -1412,7 +1427,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
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;
@@ -1428,7 +1443,9 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1428
1443
  });
1429
1444
  recordOutcome(journalEntry);
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) {
1448
+ await awaitNonStreamingTiming((effReasoning?.length ?? 0) + completionText.length);
1432
1449
  const completion = buildContentWithToolCallsCompletion(blockOutcome?.content ?? response.content ?? "", blockOutcome?.toolCalls ?? toolCalls, responseModel, effReasoning, blockOutcome ? {
1433
1450
  ...overrides,
1434
1451
  finishReason: overrides?.finishReason ?? (blockOutcome.hasToolCalls ? "tool_calls" : "stop")
@@ -1437,7 +1454,6 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1437
1454
  res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
1438
1455
  } else {
1439
1456
  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
1457
  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
1458
  shapeORChunks(chunks, usageChunk, overrides);
1443
1459
  const interruption = createInterruptionSignal(fixture);
@@ -1447,7 +1463,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1447
1463
  signal: interruption?.signal,
1448
1464
  onChunkSent: interruption?.tick,
1449
1465
  usageChunk,
1450
- recordedTimings: resolveRecordedTimings(chunks),
1466
+ recordedTimings: resolveRecordedTimings(chunks.length),
1451
1467
  replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1452
1468
  openRouterProcessing
1453
1469
  })) {
@@ -1462,7 +1478,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1462
1478
  if (isTextResponse(response)) {
1463
1479
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
1464
1480
  const overrides = extractOverrides(response);
1465
- const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1481
+ const effectiveStrict = resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression;
1466
1482
  const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1467
1483
  const journalEntry = journal.add({
1468
1484
  method: req.method ?? "POST",
@@ -1476,6 +1492,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1476
1492
  });
1477
1493
  recordOutcome(journalEntry);
1478
1494
  if (body.stream !== true) {
1495
+ await awaitNonStreamingTiming((effReasoning?.length ?? 0) + response.content.length);
1479
1496
  const completion = buildTextCompletion(response.content, responseModel, effReasoning, overrides, body.messages);
1480
1497
  res.writeHead(200, { "Content-Type": "application/json" });
1481
1498
  res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
@@ -1490,7 +1507,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1490
1507
  signal: interruption?.signal,
1491
1508
  onChunkSent: interruption?.tick,
1492
1509
  usageChunk,
1493
- recordedTimings: resolveRecordedTimings(chunks),
1510
+ recordedTimings: resolveRecordedTimings(chunks.length),
1494
1511
  replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1495
1512
  openRouterProcessing
1496
1513
  })) {
@@ -1505,7 +1522,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1505
1522
  if (isToolCallResponse(response)) {
1506
1523
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
1507
1524
  const overrides = extractOverrides(response);
1508
- const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
1525
+ const effectiveStrict = resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression;
1509
1526
  const effReasoning = appliedPlan ? appliedReasoning : resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
1510
1527
  const journalEntry = journal.add({
1511
1528
  method: req.method ?? "POST",
@@ -1520,6 +1537,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1520
1537
  recordOutcome(journalEntry);
1521
1538
  const toolCalls = requireServedFunctionToolCalls(response, wire);
1522
1539
  if (body.stream !== true) {
1540
+ const toolCallText = toolCalls.map((tc) => tc.name + tc.arguments).join("");
1541
+ await awaitNonStreamingTiming((effReasoning?.length ?? 0) + toolCallText.length);
1523
1542
  const completion = buildToolCallCompletion(toolCalls, responseModel, effReasoning, overrides, body.messages);
1524
1543
  res.writeHead(200, { "Content-Type": "application/json" });
1525
1544
  res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
@@ -1535,7 +1554,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1535
1554
  signal: interruption?.signal,
1536
1555
  onChunkSent: interruption?.tick,
1537
1556
  usageChunk,
1538
- recordedTimings: resolveRecordedTimings(chunks),
1557
+ recordedTimings: resolveRecordedTimings(chunks.length),
1539
1558
  replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1540
1559
  openRouterProcessing
1541
1560
  })) {
@@ -1557,10 +1576,10 @@ async function handleCompletions(req, res, fixtures, journal, defaults, getReque
1557
1576
  fixture
1558
1577
  }
1559
1578
  }));
1560
- writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, "Fixture response did not match any known type") : JSON.stringify({ error: {
1579
+ writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, "Fixture response did not match any known type") : renderError("openai", "unknown-response", {
1561
1580
  message: "Fixture response did not match any known type",
1562
1581
  type: "server_error"
1563
- } }));
1582
+ }).body);
1564
1583
  }
1565
1584
  async function createServer(fixtures, options, mounts, serviceFixtures) {
1566
1585
  return createServerWithResolvedAuth(fixtures, options, resolveInboundAuth({
@@ -1629,6 +1648,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1629
1648
  const port = options?.port ?? 0;
1630
1649
  const registry = options?.metrics ? createMetricsRegistry() : void 0;
1631
1650
  const serverOptions = options ?? {};
1651
+ parsePolicy(serverOptions.policy, (message) => new Logger("warn").warn(message));
1632
1652
  const validResponsesTools = (mode) => mode === "legacy" || mode === "extended";
1633
1653
  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
1654
  fixtures = createServerFixtureList(callerFixtures, () => serverOptions.responsesTools, responsesToolsExtendedView);
@@ -1687,6 +1707,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1687
1707
  get strict() {
1688
1708
  return serverOptions.strict;
1689
1709
  },
1710
+ get policy() {
1711
+ return serverOptions.policy;
1712
+ },
1690
1713
  get strictToolArguments() {
1691
1714
  return serverOptions.strictToolArguments;
1692
1715
  },
@@ -1812,70 +1835,63 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1812
1835
  * `toolCallEnvelope` instead of using this one.
1813
1836
  */
1814
1837
  function invalidToolArgumentsEnvelope(pathname, message, code) {
1815
- if (pathname === OLLAMA_CHAT_PATH) return JSON.stringify({
1816
- error: message,
1838
+ if (pathname === OLLAMA_CHAT_PATH) return renderError("ollama", "tool-argument", {
1839
+ message,
1817
1840
  code
1818
- });
1819
- if (pathname === GEMINI_INTERACTIONS_PATH) return JSON.stringify({ error: {
1841
+ }).body;
1842
+ if (pathname === GEMINI_INTERACTIONS_PATH) return renderError("interactions", "tool-argument", {
1843
+ message,
1844
+ code
1845
+ }).body;
1846
+ if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return renderError("gemini", "tool-argument", {
1847
+ message,
1820
1848
  code,
1821
- message
1822
- } });
1823
- if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return JSON.stringify({ error: {
1824
- code: 500,
1849
+ googleDetails: googleFixtureToolCallErrorDetails(code)
1850
+ }).body;
1851
+ if (pathname === MESSAGES_PATH) return renderError("messages", "tool-argument", {
1825
1852
  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",
1853
+ code
1854
+ }).body;
1855
+ 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
1856
  message,
1840
- reason: code
1841
- });
1842
- return JSON.stringify({ error: {
1857
+ code
1858
+ }).body;
1859
+ return renderError("openai", "tool-argument", {
1843
1860
  message,
1844
- type: "server_error",
1845
1861
  code
1846
- } });
1862
+ }).body;
1847
1863
  }
1848
1864
  function invalidMisbehaviorHeaderEnvelope(pathname, detail) {
1849
1865
  const code = "aimock_misbehavior_invalid";
1850
1866
  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,
1867
+ if (pathname === OLLAMA_CHAT_PATH || pathname === OLLAMA_GENERATE_PATH) return renderError("ollama", "invalid-misbehavior-header", {
1859
1868
  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: {
1869
+ code
1870
+ }).body;
1871
+ if (pathname === COHERE_CHAT_PATH) return renderError("cohere", "invalid-misbehavior-header", {
1872
+ message,
1873
+ code
1874
+ }).body;
1875
+ if (pathname === GEMINI_INTERACTIONS_PATH) return renderError("interactions", "invalid-misbehavior-header", {
1876
+ message,
1877
+ code
1878
+ }).body;
1879
+ if (GEMINI_PATH_RE.test(pathname) || VERTEX_AI_RE.test(pathname)) return renderError("gemini", "invalid-misbehavior-header", {
1880
+ message,
1881
+ code
1882
+ }).body;
1883
+ if (pathname === MESSAGES_PATH) return renderError("messages", "invalid-misbehavior-header", {
1875
1884
  message,
1876
- type: "invalid_request_error",
1877
1885
  code
1878
- } });
1886
+ }).body;
1887
+ 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", {
1888
+ message,
1889
+ code
1890
+ }).body;
1891
+ return renderError("openai", "invalid-misbehavior-header", {
1892
+ message,
1893
+ code
1894
+ }).body;
1879
1895
  }
1880
1896
  /**
1881
1897
  * The newest journal entry this request already produced, or null.
@@ -1990,10 +2006,10 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
1990
2006
  logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
1991
2007
  }
1992
2008
  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: {
2009
+ 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
2010
  message: msg,
1995
2011
  type: errorTypeForStatus(status)
1996
- } }));
2012
+ }).body);
1997
2013
  } else {
1998
2014
  const midStreamReason = clientAbort ? "client aborted" : "handler crashed mid-stream";
1999
2015
  if (existing) {
@@ -2110,10 +2126,10 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2110
2126
  logger.warn(`${route}: journal write failed after handler error: ${jErr instanceof Error ? jErr.message : String(jErr)}`);
2111
2127
  }
2112
2128
  setCorsHeaders(res);
2113
- writeErrorResponse(res, 400, JSON.stringify({ error: {
2129
+ writeErrorResponse(res, 400, renderError("openai", "route-failure", {
2114
2130
  message: "Invalid request target",
2115
2131
  type: "invalid_request_error"
2116
- } }));
2132
+ }).body);
2117
2133
  return;
2118
2134
  }
2119
2135
  pathname = parsedUrl.pathname;
@@ -2126,7 +2142,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2126
2142
  }
2127
2143
  if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname === METRICS_PATH))) {
2128
2144
  if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
2129
- writeApiKeyHttpRejection(res, setCorsHeaders);
2145
+ writeApiKeyHttpRejection(res, setCorsHeaders, pathname, resolvePolicy(defaults, req.headers).authErrors);
2130
2146
  return;
2131
2147
  }
2132
2148
  markAuthenticatedRequest(req, resolvedAuth.policy);
@@ -2193,18 +2209,9 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2193
2209
  }
2194
2210
  if (pathname === OLLAMA_TAGS_PATH && req.method === "GET") {
2195
2211
  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
- }));
2212
+ const body = renderOllamaTags(collect(fixtures, "all").ids, () => (/* @__PURE__ */ new Date()).toISOString());
2206
2213
  res.writeHead(200, { "Content-Type": "application/json" });
2207
- res.end(JSON.stringify({ models }));
2214
+ res.end(JSON.stringify(body));
2208
2215
  return;
2209
2216
  }
2210
2217
  const openRouterVideoContentMatch = pathname.match(OPENROUTER_VIDEO_CONTENT_RE);
@@ -2325,21 +2332,70 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2325
2332
  res.end(registry.serialize());
2326
2333
  return;
2327
2334
  }
2328
- if (pathname === MODELS_PATH && req.method === "GET") {
2335
+ const modelDetailMatch = pathname.match(MODELS_DETAIL_RE);
2336
+ const geminiModelDetailMatch = pathname.match(GEMINI_MODEL_DETAIL_RE);
2337
+ if (req.method === "GET" && (pathname === MODELS_PATH || pathname === GEMINI_MODELS_PATH || modelDetailMatch || geminiModelDetailMatch)) {
2329
2338
  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
- }));
2339
+ const google = pathname === GEMINI_MODELS_PATH || geminiModelDetailMatch !== null;
2340
+ const policy = resolvePolicy(defaults, req.headers);
2341
+ const anthropic = !google && (modelDetailMatch !== null || policy.modelCatalog === "native") && isAnthropicModelRequest(req.headers);
2342
+ const missingHeader = anthropic && policy.requiredHeaders === "enforce" ? missingRequiredHeader("messages", req.headers) : void 0;
2343
+ if (missingHeader) {
2344
+ writeErrorResponse(res, 400, renderError("messages", "thinking", {
2345
+ message: `${missingHeader}: header is required`,
2346
+ requestId
2347
+ }).body);
2348
+ return;
2349
+ }
2350
+ const ids = coreModelIds(collect(fixtures, "all").ids);
2351
+ const visible = google ? idsFor(ids, "google") : anthropic ? idsFor(ids, "anthropic") : ids;
2352
+ const catalogError = (status, message) => {
2353
+ const rendered = google ? renderError("gemini", "fixture", {
2354
+ bodyCode: status,
2355
+ status: status === 404 ? "NOT_FOUND" : "INVALID_ARGUMENT",
2356
+ message
2357
+ }) : anthropic ? renderError("messages", "thinking", {
2358
+ type: status === 404 ? "not_found_error" : "invalid_request_error",
2359
+ message
2360
+ }) : renderError("openai", "validation", { message });
2361
+ res.writeHead(status, {
2362
+ "Content-Type": "application/json",
2363
+ ...rendered.headers
2364
+ });
2365
+ res.end(rendered.body);
2366
+ };
2367
+ let body;
2368
+ const detail = modelDetailMatch ?? geminiModelDetailMatch;
2369
+ if (detail) {
2370
+ let id;
2371
+ try {
2372
+ id = decodeURIComponent(detail[1]);
2373
+ } catch {
2374
+ catalogError(404, "Model not found");
2375
+ return;
2376
+ }
2377
+ if (!visible.includes(id)) {
2378
+ catalogError(404, `Model not found: ${id}`);
2379
+ return;
2380
+ }
2381
+ body = google ? renderGeminiModel(id) : anthropic ? renderAnthropicModel(id) : renderOpenAIModel(id);
2382
+ } else if (google) {
2383
+ const result = geminiPage(visible, parsedUrl.searchParams);
2384
+ if (result.error !== void 0) {
2385
+ catalogError(400, result.error);
2386
+ return;
2387
+ }
2388
+ body = renderGeminiModels(result.page);
2389
+ } else if (anthropic) {
2390
+ const result = anthropicPage(visible, parsedUrl.searchParams);
2391
+ if (result.error !== void 0) {
2392
+ catalogError(400, result.error);
2393
+ return;
2394
+ }
2395
+ body = renderAnthropicModels(result.page);
2396
+ } else body = renderOpenAIModels(ids);
2338
2397
  res.writeHead(200, { "Content-Type": "application/json" });
2339
- res.end(JSON.stringify({
2340
- object: "list",
2341
- data
2342
- }));
2398
+ res.end(JSON.stringify(body));
2343
2399
  return;
2344
2400
  }
2345
2401
  const batchesCancelMatch = pathname.match(BATCHES_CANCEL_RE);
@@ -2597,10 +2653,10 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2597
2653
  if (limitParam) {
2598
2654
  const limit = Number(limitParam);
2599
2655
  if (!/^\+?\d+$/.test(limitParam.trim()) || !Number.isSafeInteger(limit) || limit <= 0) {
2600
- writeErrorResponse(res, 400, JSON.stringify({ error: {
2656
+ writeErrorResponse(res, 400, renderError("openai", "validation", {
2601
2657
  message: `Invalid limit parameter: "${limitParam}"`,
2602
2658
  type: "invalid_request_error"
2603
- } }));
2659
+ }).body);
2604
2660
  return;
2605
2661
  }
2606
2662
  opts = { limit };
@@ -2625,7 +2681,27 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2625
2681
  ensureRawFixturePositions();
2626
2682
  await handleResponses(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2627
2683
  } catch (err) {
2628
- routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
2684
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2685
+ }
2686
+ return;
2687
+ }
2688
+ const geminiCountMatch = pathname.match(GEMINI_COUNT_RE);
2689
+ if (req.method === "POST" && (pathname === MESSAGES_COUNT_PATH || geminiCountMatch)) {
2690
+ const google = geminiCountMatch !== null;
2691
+ try {
2692
+ handleCountTokens(req, res, await readBody(req), google, geminiCountMatch?.[1] ?? "", journal, defaults, setCorsHeaders);
2693
+ } catch (err) {
2694
+ routeError(req, res, err, pathname, {
2695
+ service: google ? "gemini" : "anthropic",
2696
+ envelope: (message, status) => google ? renderError("gemini", "fixture", {
2697
+ message,
2698
+ bodyCode: status,
2699
+ status: status === 400 ? "INVALID_ARGUMENT" : "INTERNAL"
2700
+ }).body : renderError("messages", "thinking", {
2701
+ message,
2702
+ type: status === 400 ? "invalid_request_error" : "api_error"
2703
+ }).body
2704
+ });
2629
2705
  }
2630
2706
  return;
2631
2707
  }
@@ -2635,7 +2711,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2635
2711
  ensureRawFixturePositions();
2636
2712
  await handleMessages(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2637
2713
  } catch (err) {
2638
- routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
2714
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2639
2715
  }
2640
2716
  return;
2641
2717
  }
@@ -2645,7 +2721,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2645
2721
  ensureRawFixturePositions();
2646
2722
  await handleCohere(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2647
2723
  } catch (err) {
2648
- routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${JSON.stringify({ error: { message: m } })}\n\n` });
2724
+ routeError(req, res, err, pathname, { streamEvent: (m) => `event: error\ndata: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2649
2725
  }
2650
2726
  return;
2651
2727
  }
@@ -2811,7 +2887,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2811
2887
  ensureRawFixturePositions();
2812
2888
  await handleGeminiInteractions(req, res, raw, fixtures, journal, defaults, setCorsHeaders);
2813
2889
  } catch (err) {
2814
- routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
2890
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2815
2891
  }
2816
2892
  return;
2817
2893
  }
@@ -2836,7 +2912,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2836
2912
  ensureRawFixturePositions();
2837
2913
  await handleGemini(req, res, raw, geminiModel, streaming, fixtures, journal, defaults, setCorsHeaders);
2838
2914
  } catch (err) {
2839
- routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
2915
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2840
2916
  }
2841
2917
  return;
2842
2918
  }
@@ -2849,7 +2925,7 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2849
2925
  ensureRawFixturePositions();
2850
2926
  await handleGemini(req, res, raw, vertexModel, streaming, fixtures, journal, defaults, setCorsHeaders, "vertexai");
2851
2927
  } catch (err) {
2852
- routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${JSON.stringify({ error: { message: m } })}\n\n` });
2928
+ routeError(req, res, err, pathname, { streamEvent: (m) => `data: ${renderError("openai", "stream-failure", { message: m }).body}\n\n` });
2853
2929
  }
2854
2930
  return;
2855
2931
  }
@@ -2965,10 +3041,10 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
2965
3041
  try {
2966
3042
  voiceId = decodeURIComponent(elevenLabsVoiceMatch[1]);
2967
3043
  } catch {
2968
- writeErrorResponse(res, 400, JSON.stringify({ error: {
3044
+ writeErrorResponse(res, 400, renderError("openai", "validation", {
2969
3045
  message: `Invalid voice id '${elevenLabsVoiceMatch[1]}': malformed percent-encoding`,
2970
3046
  type: "invalid_request_error"
2971
- } }));
3047
+ }).body);
2972
3048
  journal.add({
2973
3049
  method: req.method ?? "GET",
2974
3050
  path: req.url ?? pathname,
@@ -3118,15 +3194,15 @@ async function startServer(fixtures, options, resolvedAuth, mountList, logger, s
3118
3194
  await handleCompletions(req, res, fixtures, journal, defaults, () => lastJournalEntryFor(req), azureDeploymentId, completionsProvider, isOpenRouter, ensureRawFixturePositions, originalPathname.startsWith("/api/v3/"));
3119
3195
  } catch (err) {
3120
3196
  routeError(req, res, err, pathname, {
3121
- envelope: (m, status) => isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
3197
+ envelope: (m, status) => isOpenRouter ? serializeOpenRouterError(status, m) : renderError("openai", "route-failure", {
3122
3198
  message: m,
3123
3199
  type: errorTypeForStatus(status)
3124
- } }),
3200
+ }).body,
3125
3201
  ...isOpenRouter ? { toolCallEnvelope: (m, code) => serializeOpenRouterError(500, m, { reason: code }) } : {},
3126
- streamEvent: (m, status) => `data: ${isOpenRouter ? serializeOpenRouterError(status, m) : JSON.stringify({ error: {
3202
+ streamEvent: (m, status) => `data: ${isOpenRouter ? serializeOpenRouterError(status, m) : renderError("openai", "route-failure", {
3127
3203
  message: m,
3128
3204
  type: errorTypeForStatus(status)
3129
- } })}\n\n`
3205
+ }).body}\n\n`
3130
3206
  });
3131
3207
  }
3132
3208
  }