@copilotkit/aimock 1.45.1 → 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 (425) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +13 -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.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/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-handler.cjs +7 -3
  177. package/dist/mcp-handler.cjs.map +1 -1
  178. package/dist/mcp-handler.js +7 -3
  179. package/dist/mcp-handler.js.map +1 -1
  180. package/dist/mcp-recorder.cjs +2 -1
  181. package/dist/mcp-recorder.cjs.map +1 -1
  182. package/dist/mcp-recorder.js +3 -2
  183. package/dist/mcp-recorder.js.map +1 -1
  184. package/dist/messages.cjs +111 -96
  185. package/dist/messages.cjs.map +1 -1
  186. package/dist/messages.d.cts.map +1 -1
  187. package/dist/messages.d.ts.map +1 -1
  188. package/dist/messages.js +109 -95
  189. package/dist/messages.js.map +1 -1
  190. package/dist/metrics.cjs +5 -2
  191. package/dist/metrics.cjs.map +1 -1
  192. package/dist/metrics.d.cts.map +1 -1
  193. package/dist/metrics.d.ts.map +1 -1
  194. package/dist/metrics.js +6 -3
  195. package/dist/metrics.js.map +1 -1
  196. package/dist/model-utils.cjs +1 -0
  197. package/dist/model-utils.cjs.map +1 -1
  198. package/dist/model-utils.js +1 -1
  199. package/dist/model-utils.js.map +1 -1
  200. package/dist/moderation.cjs +15 -12
  201. package/dist/moderation.cjs.map +1 -1
  202. package/dist/moderation.js +14 -11
  203. package/dist/moderation.js.map +1 -1
  204. package/dist/ollama.cjs +128 -68
  205. package/dist/ollama.cjs.map +1 -1
  206. package/dist/ollama.d.cts.map +1 -1
  207. package/dist/ollama.d.ts.map +1 -1
  208. package/dist/ollama.js +124 -64
  209. package/dist/ollama.js.map +1 -1
  210. package/dist/openapi.cjs +323 -3
  211. package/dist/openapi.cjs.map +1 -1
  212. package/dist/openapi.d.cts.map +1 -1
  213. package/dist/openapi.d.ts.map +1 -1
  214. package/dist/openapi.js +324 -4
  215. package/dist/openapi.js.map +1 -1
  216. package/dist/openrouter-chat.cjs +11 -155
  217. package/dist/openrouter-chat.cjs.map +1 -1
  218. package/dist/openrouter-chat.js +11 -155
  219. package/dist/openrouter-chat.js.map +1 -1
  220. package/dist/openrouter-video.cjs +51 -74
  221. package/dist/openrouter-video.cjs.map +1 -1
  222. package/dist/openrouter-video.d.cts.map +1 -1
  223. package/dist/openrouter-video.d.ts.map +1 -1
  224. package/dist/openrouter-video.js +37 -60
  225. package/dist/openrouter-video.js.map +1 -1
  226. package/dist/recorder.cjs +176 -95
  227. package/dist/recorder.cjs.map +1 -1
  228. package/dist/recorder.d.cts.map +1 -1
  229. package/dist/recorder.d.ts.map +1 -1
  230. package/dist/recorder.js +176 -95
  231. package/dist/recorder.js.map +1 -1
  232. package/dist/rerank.cjs +13 -11
  233. package/dist/rerank.cjs.map +1 -1
  234. package/dist/rerank.js +12 -10
  235. package/dist/rerank.js.map +1 -1
  236. package/dist/response/catalog/anthropic-page.cjs +39 -0
  237. package/dist/response/catalog/anthropic-page.cjs.map +1 -0
  238. package/dist/response/catalog/anthropic-page.js +38 -0
  239. package/dist/response/catalog/anthropic-page.js.map +1 -0
  240. package/dist/response/catalog/collect.cjs +61 -0
  241. package/dist/response/catalog/collect.cjs.map +1 -0
  242. package/dist/response/catalog/collect.js +59 -0
  243. package/dist/response/catalog/collect.js.map +1 -0
  244. package/dist/response/catalog/gemini-page.cjs +25 -0
  245. package/dist/response/catalog/gemini-page.cjs.map +1 -0
  246. package/dist/response/catalog/gemini-page.js +24 -0
  247. package/dist/response/catalog/gemini-page.js.map +1 -0
  248. package/dist/response/catalog/render.cjs +182 -0
  249. package/dist/response/catalog/render.cjs.map +1 -0
  250. package/dist/response/catalog/render.js +171 -0
  251. package/dist/response/catalog/render.js.map +1 -0
  252. package/dist/response/errors.cjs +225 -0
  253. package/dist/response/errors.cjs.map +1 -0
  254. package/dist/response/errors.js +224 -0
  255. package/dist/response/errors.js.map +1 -0
  256. package/dist/response/finish.cjs +39 -0
  257. package/dist/response/finish.cjs.map +1 -0
  258. package/dist/response/finish.js +35 -0
  259. package/dist/response/finish.js.map +1 -0
  260. package/dist/response/policy.cjs +126 -0
  261. package/dist/response/policy.cjs.map +1 -0
  262. package/dist/response/policy.js +120 -0
  263. package/dist/response/policy.js.map +1 -0
  264. package/dist/response/profiles.cjs +69 -0
  265. package/dist/response/profiles.cjs.map +1 -0
  266. package/dist/response/profiles.js +65 -0
  267. package/dist/response/profiles.js.map +1 -0
  268. package/dist/response/usage/codecs/anthropic.cjs +52 -0
  269. package/dist/response/usage/codecs/anthropic.cjs.map +1 -0
  270. package/dist/response/usage/codecs/anthropic.js +50 -0
  271. package/dist/response/usage/codecs/anthropic.js.map +1 -0
  272. package/dist/response/usage/codecs/cohere.cjs +36 -0
  273. package/dist/response/usage/codecs/cohere.cjs.map +1 -0
  274. package/dist/response/usage/codecs/cohere.js +35 -0
  275. package/dist/response/usage/codecs/cohere.js.map +1 -0
  276. package/dist/response/usage/codecs/converse.cjs +16 -0
  277. package/dist/response/usage/codecs/converse.cjs.map +1 -0
  278. package/dist/response/usage/codecs/converse.js +15 -0
  279. package/dist/response/usage/codecs/converse.js.map +1 -0
  280. package/dist/response/usage/codecs/gemini.cjs +31 -0
  281. package/dist/response/usage/codecs/gemini.cjs.map +1 -0
  282. package/dist/response/usage/codecs/gemini.js +31 -0
  283. package/dist/response/usage/codecs/gemini.js.map +1 -0
  284. package/dist/response/usage/codecs/interactions.cjs +22 -0
  285. package/dist/response/usage/codecs/interactions.cjs.map +1 -0
  286. package/dist/response/usage/codecs/interactions.js +21 -0
  287. package/dist/response/usage/codecs/interactions.js.map +1 -0
  288. package/dist/response/usage/codecs/ollama.cjs +17 -0
  289. package/dist/response/usage/codecs/ollama.cjs.map +1 -0
  290. package/dist/response/usage/codecs/ollama.js +17 -0
  291. package/dist/response/usage/codecs/ollama.js.map +1 -0
  292. package/dist/response/usage/codecs/openai.cjs +52 -0
  293. package/dist/response/usage/codecs/openai.cjs.map +1 -0
  294. package/dist/response/usage/codecs/openai.js +50 -0
  295. package/dist/response/usage/codecs/openai.js.map +1 -0
  296. package/dist/response/usage/codecs/openrouter.cjs +68 -0
  297. package/dist/response/usage/codecs/openrouter.cjs.map +1 -0
  298. package/dist/response/usage/codecs/openrouter.js +67 -0
  299. package/dist/response/usage/codecs/openrouter.js.map +1 -0
  300. package/dist/response/usage/codecs/realtime.cjs +19 -0
  301. package/dist/response/usage/codecs/realtime.cjs.map +1 -0
  302. package/dist/response/usage/codecs/realtime.js +18 -0
  303. package/dist/response/usage/codecs/realtime.js.map +1 -0
  304. package/dist/response/usage/codecs/responses.cjs +21 -0
  305. package/dist/response/usage/codecs/responses.cjs.map +1 -0
  306. package/dist/response/usage/codecs/responses.js +20 -0
  307. package/dist/response/usage/codecs/responses.js.map +1 -0
  308. package/dist/response/usage/estimate.cjs +23 -0
  309. package/dist/response/usage/estimate.cjs.map +1 -0
  310. package/dist/response/usage/estimate.js +21 -0
  311. package/dist/response/usage/estimate.js.map +1 -0
  312. package/dist/response/usage/fields.cjs +72 -0
  313. package/dist/response/usage/fields.cjs.map +1 -0
  314. package/dist/response/usage/fields.js +70 -0
  315. package/dist/response/usage/fields.js.map +1 -0
  316. package/dist/response/usage/recording.cjs +52 -0
  317. package/dist/response/usage/recording.cjs.map +1 -0
  318. package/dist/response/usage/recording.js +52 -0
  319. package/dist/response/usage/recording.js.map +1 -0
  320. package/dist/responses.cjs +45 -58
  321. package/dist/responses.cjs.map +1 -1
  322. package/dist/responses.d.cts.map +1 -1
  323. package/dist/responses.d.ts.map +1 -1
  324. package/dist/responses.js +44 -57
  325. package/dist/responses.js.map +1 -1
  326. package/dist/route-registry.cjs +45 -0
  327. package/dist/route-registry.cjs.map +1 -1
  328. package/dist/route-registry.d.cts.map +1 -1
  329. package/dist/route-registry.d.ts.map +1 -1
  330. package/dist/route-registry.js +41 -1
  331. package/dist/route-registry.js.map +1 -1
  332. package/dist/router.cjs +4 -0
  333. package/dist/router.cjs.map +1 -1
  334. package/dist/router.d.cts.map +1 -1
  335. package/dist/router.d.ts.map +1 -1
  336. package/dist/router.js +3 -0
  337. package/dist/router.js.map +1 -1
  338. package/dist/search.cjs +13 -11
  339. package/dist/search.cjs.map +1 -1
  340. package/dist/search.js +12 -10
  341. package/dist/search.js.map +1 -1
  342. package/dist/server.cjs +222 -146
  343. package/dist/server.cjs.map +1 -1
  344. package/dist/server.d.cts.map +1 -1
  345. package/dist/server.d.ts.map +1 -1
  346. package/dist/server.js +223 -147
  347. package/dist/server.js.map +1 -1
  348. package/dist/speech.cjs +20 -18
  349. package/dist/speech.cjs.map +1 -1
  350. package/dist/speech.d.cts.map +1 -1
  351. package/dist/speech.d.ts.map +1 -1
  352. package/dist/speech.js +19 -17
  353. package/dist/speech.js.map +1 -1
  354. package/dist/sse-writer.cjs +7 -0
  355. package/dist/sse-writer.cjs.map +1 -1
  356. package/dist/sse-writer.d.cts.map +1 -1
  357. package/dist/sse-writer.d.ts.map +1 -1
  358. package/dist/sse-writer.js +7 -1
  359. package/dist/sse-writer.js.map +1 -1
  360. package/dist/stream-collapse.cjs +118 -11
  361. package/dist/stream-collapse.cjs.map +1 -1
  362. package/dist/stream-collapse.d.cts +10 -9
  363. package/dist/stream-collapse.d.cts.map +1 -1
  364. package/dist/stream-collapse.d.ts +10 -9
  365. package/dist/stream-collapse.d.ts.map +1 -1
  366. package/dist/stream-collapse.js +118 -11
  367. package/dist/stream-collapse.js.map +1 -1
  368. package/dist/transcription.cjs +12 -10
  369. package/dist/transcription.cjs.map +1 -1
  370. package/dist/transcription.d.cts.map +1 -1
  371. package/dist/transcription.d.ts.map +1 -1
  372. package/dist/transcription.js +11 -9
  373. package/dist/transcription.js.map +1 -1
  374. package/dist/types.cjs.map +1 -1
  375. package/dist/types.d.cts +62 -3
  376. package/dist/types.d.cts.map +1 -1
  377. package/dist/types.d.ts +62 -3
  378. package/dist/types.d.ts.map +1 -1
  379. package/dist/types.js.map +1 -1
  380. package/dist/vector-mock.cjs +3 -8
  381. package/dist/vector-mock.cjs.map +1 -1
  382. package/dist/vector-mock.d.cts.map +1 -1
  383. package/dist/vector-mock.d.ts.map +1 -1
  384. package/dist/vector-mock.js +3 -8
  385. package/dist/vector-mock.js.map +1 -1
  386. package/dist/vector-stores.cjs +2 -4
  387. package/dist/vector-stores.cjs.map +1 -1
  388. package/dist/vector-stores.d.cts.map +1 -1
  389. package/dist/vector-stores.d.ts.map +1 -1
  390. package/dist/vector-stores.js +2 -4
  391. package/dist/vector-stores.js.map +1 -1
  392. package/dist/veo-video.cjs +16 -14
  393. package/dist/veo-video.cjs.map +1 -1
  394. package/dist/veo-video.js +10 -8
  395. package/dist/veo-video.js.map +1 -1
  396. package/dist/video.cjs +21 -19
  397. package/dist/video.cjs.map +1 -1
  398. package/dist/video.d.cts.map +1 -1
  399. package/dist/video.d.ts.map +1 -1
  400. package/dist/video.js +20 -18
  401. package/dist/video.js.map +1 -1
  402. package/dist/ws-gemini-live.cjs +11 -3
  403. package/dist/ws-gemini-live.cjs.map +1 -1
  404. package/dist/ws-gemini-live.d.cts +0 -1
  405. package/dist/ws-gemini-live.d.cts.map +1 -1
  406. package/dist/ws-gemini-live.d.ts +0 -1
  407. package/dist/ws-gemini-live.d.ts.map +1 -1
  408. package/dist/ws-gemini-live.js +10 -2
  409. package/dist/ws-gemini-live.js.map +1 -1
  410. package/dist/ws-live.cjs +6 -5
  411. package/dist/ws-live.cjs.map +1 -1
  412. package/dist/ws-live.js +5 -4
  413. package/dist/ws-live.js.map +1 -1
  414. package/dist/ws-realtime.cjs +28 -54
  415. package/dist/ws-realtime.cjs.map +1 -1
  416. package/dist/ws-realtime.d.cts.map +1 -1
  417. package/dist/ws-realtime.d.ts.map +1 -1
  418. package/dist/ws-realtime.js +25 -51
  419. package/dist/ws-realtime.js.map +1 -1
  420. package/dist/ws-responses.cjs +8 -6
  421. package/dist/ws-responses.cjs.map +1 -1
  422. package/dist/ws-responses.js +7 -5
  423. package/dist/ws-responses.js.map +1 -1
  424. package/package.json +4 -1
  425. package/skills/write-fixtures/SKILL.md +25 -16
@@ -1 +1 @@
1
- {"version":3,"file":"batches.js","names":[],"sources":["../src/batches.ts"],"sourcesContent":["/**\n * OpenAI Batches API mock.\n *\n * Covers `POST /v1/batches`, `GET /v1/batches`, `GET /v1/batches/{id}` and\n * `POST /v1/batches/{id}/cancel` with a deterministic poll progression so\n * batch flows can be tested without waiting 24h:\n *\n * validating → in_progress → completed | failed | expired | cancelled\n *\n * The first retrieve after create advances to `in_progress`, the second (and\n * later) to the batch's outcome. Terminal batches are stable. On `completed`\n * the mock registers a REAL file in the Files store (`output_file_id`, purpose\n * `batch_output`, filename `<batch-id>_output.jsonl`) holding one JSONL line\n * per input request, so `GET /v1/files/{id}/content` serves it back and the\n * upload → create → poll → download flow closes. When `input_file_id` names a\n * file in the store, its JSONL lines drive the `custom_id`s and\n * `request_counts.total`; when it does not (the id was never uploaded here),\n * the mock synthesizes ONE request (`custom_id: \"request-1\"`, `total: 1`) so\n * the output file is still downloadable.\n *\n * Outcome control: the real API only ever completes a healthy batch, so a\n * suite that has to exercise its failure handling needs a lever. Send\n * `X-AIMock-Batch-Outcome: completed | failed | expired | cancelled` on\n * `POST /v1/batches` (default `completed`, unknown values 400). `failed`\n * populates a one-entry `errors` list and a real `error_file_id` (one JSONL\n * error line per request) and stamps `failed_at`; `expired` stamps `expired_at`; `cancelled`\n * lands the batch on `cancelled` without a cancel call. Same `X-AIMock-*`\n * family as `X-AIMock-Strict` and the chaos headers.\n *\n * `cancel` on a non-terminal batch answers `cancelling` (stamping\n * `cancelling_at`); the next retrieve shows `cancelled` with `cancelled_at`.\n * Cancelling a terminal batch is 400 `Cannot cancel a batch with status\n * <status>.` (the SDK's `Batch.status` union, openai@4.104.0\n * `resources/batches.d.ts`, is the source for the status set and the `*_at`\n * stamps; the exact 400 message text is UNVERIFIED against the live API).\n *\n * Output-line shape (`{id: \"batch_req_…\", custom_id, response: {status_code,\n * request_id, body}, error}` / error lines with `response: null` and\n * `error: {code, message}`) follows the OpenAI Batch guide's documented\n * result-file format; neither the vendored SDK nor `openai/openai-openapi`\n * types the file's lines, so that shape is UNVERIFIED against a live capture.\n *\n * State is in-memory per process and cleared by the full reset path.\n * Every branch journals with `service: \"batches\"` and runs through the\n * chaos gate so fault-injection suites can target the batch surface.\n */\n\nimport type * as http from \"node:http\";\nimport { randomBytes } from \"node:crypto\";\nimport { flattenHeaders, generateId, isJsonObject } from \"./helpers.js\";\nimport { applyChaosAsync, type ChaosAsyncOutcome } from \"./chaos.js\";\nimport { paginate, readMetadata } from \"./fine-tuning.js\";\nimport { FILES_MAX_BYTES, getStoredFileBytes, storeFile } from \"./files.js\";\nimport { buildTextResponse } from \"./responses.js\";\nimport type { ChaosDefaults } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\n\nexport interface BatchObject {\n id: string;\n object: \"batch\";\n endpoint: string;\n input_file_id: string;\n completion_window: string;\n status: BatchStatus;\n created_at: number;\n metadata?: Record<string, string>;\n in_progress_at?: number;\n completed_at?: number;\n failed_at?: number;\n expired_at?: number;\n cancelling_at?: number;\n cancelled_at?: number;\n output_file_id?: string;\n error_file_id?: string;\n errors?: { object: \"list\"; data: BatchError[] };\n request_counts?: { total: number; completed: number; failed: number };\n}\n\n/** `Batch.status` minus `finalizing`, which this mock never lingers in. */\nexport type BatchStatus =\n | \"validating\"\n | \"in_progress\"\n | \"completed\"\n | \"failed\"\n | \"expired\"\n | \"cancelling\"\n | \"cancelled\";\n\n/** `BatchError` from the SDK (`openai@4.104.0` `resources/batches.d.ts`). */\nexport interface BatchError {\n code?: string;\n line?: number | null;\n message?: string;\n param?: string | null;\n}\n\n/**\n * The statuses a batch never leaves. Single source of truth: `advance()`\n * refuses to move a terminal batch and the cancel handler rejects one, so the\n * two cannot drift apart.\n */\nconst TERMINAL_STATUSES = new Set<BatchStatus>([\"completed\", \"failed\", \"expired\", \"cancelled\"]);\n\nfunction isTerminal(status: BatchStatus): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n\n/** Values accepted on `X-AIMock-Batch-Outcome`; where a batch lands on its 2nd retrieve. */\nconst OUTCOMES = new Set([\"completed\", \"failed\", \"expired\", \"cancelled\"]);\ntype BatchOutcome = \"completed\" | \"failed\" | \"expired\" | \"cancelled\";\nconst OUTCOME_HEADER = \"x-aimock-batch-outcome\";\n\n// The `BatchCreateParams.endpoint` union in the vendored `openai` SDK 4.104.0\n// (`resources/batches.d.ts`): `/v1/responses`, `/v1/chat/completions`,\n// `/v1/embeddings`, `/v1/completions`.\nconst VALID_ENDPOINTS = new Set([\n \"/v1/responses\",\n \"/v1/chat/completions\",\n \"/v1/embeddings\",\n \"/v1/completions\",\n]);\n\nconst batches = new Map<string, BatchObject>();\nconst batchPolls = new Map<string, number>();\nconst batchOutcomes = new Map<string, BatchOutcome>();\n\nexport function clearBatchStore(): void {\n batches.clear();\n batchPolls.clear();\n batchOutcomes.clear();\n}\n\nfunction journalBatches(\n journal: Journal,\n method: string,\n path: string,\n headers: Record<string, string>,\n status: number,\n): void {\n journal.add({\n method,\n path,\n headers,\n body: null,\n service: \"batches\",\n response: { status, fixture: null },\n });\n}\n\nfunction writeJson(\n res: http.ServerResponse,\n status: number,\n payload: unknown,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): void {\n setCorsHeaders(res);\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(payload));\n}\n\nfunction invalid(message: string): { error: { message: string; type: string } } {\n return { error: { message, type: \"invalid_request_error\" } };\n}\n\ntype Defaults = { logger: Logger; chaos?: ChaosDefaults; registry?: MetricsRegistry };\n\n/**\n * Roll the chaos dice for one batches request. Returns true when a fault\n * fired and the response is already written, so the caller returns early.\n *\n * CORS headers go on BEFORE the roll, because a fault writes the response\n * itself and never reaches `writeJson`. `service: \"batches\"` on the journal\n * context keeps faulted requests visible under `?service=batches`. Mirrors\n * the `chaosHit` in `src/fine-tuning.ts`.\n */\nasync function chaosHit(\n req: http.IncomingMessage,\n journal: Journal,\n defaults: Defaults,\n method: string,\n path: string,\n res: http.ServerResponse,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<ChaosAsyncOutcome> {\n setCorsHeaders(res);\n return await applyChaosAsync(\n res,\n null,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"batches\",\n },\n \"internal\",\n defaults.registry,\n defaults.logger,\n );\n}\n\nfunction now(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/**\n * The input file's requests, as `{ custom_id, model }` per JSONL line. Lines\n * that are not JSON objects still count as requests (the real API reports\n * them as errors by line; here they get a positional `custom_id`). An\n * `input_file_id` that was never uploaded to this mock yields one synthesized\n * request — see the module docblock.\n */\nfunction inputRequests(batch: BatchObject): { custom_id: string; model: string }[] {\n const bytes = getStoredFileBytes(batch.input_file_id);\n const lines =\n bytes === undefined\n ? []\n : bytes\n .toString(\"utf8\")\n .split(\"\\n\")\n .filter((l) => l.trim() !== \"\");\n const requests = lines.map((line, i) => {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line) as unknown;\n } catch {\n parsed = undefined;\n }\n const obj = isJsonObject(parsed) ? parsed : {};\n const body = isJsonObject(obj[\"body\"]) ? obj[\"body\"] : {};\n return {\n custom_id: typeof obj[\"custom_id\"] === \"string\" ? obj[\"custom_id\"] : `request-${i + 1}`,\n model: typeof body[\"model\"] === \"string\" ? body[\"model\"] : \"aimock\",\n };\n });\n return requests.length > 0 ? requests : [{ custom_id: \"request-1\", model: \"aimock\" }];\n}\n\n/** A minimal per-endpoint success body for one output line. */\nfunction outputBody(endpoint: string, model: string): Record<string, unknown> {\n const created = now();\n if (endpoint === \"/v1/responses\") {\n // The same `Response` envelope the live `/v1/responses` mock writes\n // (`object: \"response\"`, `status`, `output[]` message with `output_text`,\n // `usage`), per openai@4.104.0 `resources/responses/responses.d.ts`.\n return buildTextResponse(\"Mock batch completion\", model) as Record<string, unknown>;\n }\n if (endpoint === \"/v1/embeddings\") {\n return {\n object: \"list\",\n model,\n data: [{ object: \"embedding\", index: 0, embedding: [0, 0, 0] }],\n usage: { prompt_tokens: 1, total_tokens: 1 },\n };\n }\n if (endpoint === \"/v1/completions\") {\n return {\n id: generateId(\"cmpl\"),\n object: \"text_completion\",\n created,\n model,\n choices: [{ index: 0, text: \"Mock batch completion\", finish_reason: \"stop\" }],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n };\n }\n return {\n id: generateId(\"chatcmpl\"),\n object: \"chat.completion\",\n created,\n model,\n choices: [\n {\n index: 0,\n message: { role: \"assistant\", content: \"Mock batch completion\" },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n };\n}\n\n/** `batch_req_…` / `req_…` — underscore-joined like the real ids, unlike `generateId`. */\nfunction underscoreId(prefix: string): string {\n return `${prefix}_${randomBytes(12).toString(\"base64url\")}`;\n}\n\n/**\n * Serialise one JSONL line per request, stopping the moment the running byte\n * count passes the Files store's per-file cap. Every output line is a fixed\n * ~430-byte canned body regardless of how small the input line was (a 3-byte\n * `{}` line amplifies ~143x), so an input file that is itself under the\n * upload cap can describe an output far over it; building the lines eagerly\n * would hold that whole over-cap buffer before anything could refuse it.\n * Returns `undefined` when the cap is exceeded, with at most `FILES_MAX_BYTES`\n * ever materialised.\n */\nfunction jsonlUnderCap<T>(items: T[], line: (item: T) => unknown): Buffer | undefined {\n const chunks: Buffer[] = [];\n let bytes = 0;\n for (const item of items) {\n const chunk = Buffer.from(JSON.stringify(line(item)) + \"\\n\", \"utf8\");\n bytes += chunk.length;\n if (bytes > FILES_MAX_BYTES) {\n return undefined;\n }\n chunks.push(chunk);\n }\n return Buffer.concat(chunks, bytes);\n}\n\n/**\n * The batch could not mint its result file under {@link FILES_MAX_BYTES}: it\n * fails with one `errors` entry naming the cap. `BatchError.code` is an open\n * `string` in the SDK types and the real API has no equivalent condition, so\n * the code value is UNVERIFIED against a live capture.\n */\nfunction failOverCap(batch: BatchObject, total: number, kind: \"output\" | \"error\"): void {\n batch.status = \"failed\";\n batch.errors = {\n object: \"list\",\n data: [\n {\n code: \"output_file_too_large\",\n message: `Batch ${kind} file would exceed the ${FILES_MAX_BYTES} byte per-file cap`,\n line: null,\n param: null,\n },\n ],\n };\n batch.request_counts = { total, completed: 0, failed: total };\n batch.failed_at = now();\n}\n\nfunction finish(batch: BatchObject, outcome: BatchOutcome): void {\n const requests = inputRequests(batch);\n const total = requests.length;\n batch.status = outcome;\n if (outcome === \"completed\") {\n const content = jsonlUnderCap(requests, (r) => ({\n id: underscoreId(\"batch_req\"),\n custom_id: r.custom_id,\n response: {\n status_code: 200,\n request_id: underscoreId(\"req\"),\n body: outputBody(batch.endpoint, r.model),\n },\n error: null,\n }));\n if (content === undefined) {\n failOverCap(batch, total, \"output\");\n return;\n }\n batch.output_file_id = storeFile({\n purpose: \"batch_output\",\n filename: `${batch.id}_output.jsonl`,\n content,\n }).id;\n batch.request_counts = { total, completed: total, failed: 0 };\n batch.completed_at = now();\n } else if (outcome === \"failed\") {\n const message = \"Mock batch failure (X-AIMock-Batch-Outcome: failed)\";\n const content = jsonlUnderCap(requests, (r) => ({\n id: underscoreId(\"batch_req\"),\n custom_id: r.custom_id,\n response: null,\n error: { code: \"mock_failure\", message },\n }));\n if (content === undefined) {\n failOverCap(batch, total, \"error\");\n return;\n }\n batch.error_file_id = storeFile({\n purpose: \"batch_output\",\n filename: `${batch.id}_error.jsonl`,\n content,\n }).id;\n // One file-level entry, not one per request: `errors` rides inline on\n // every retrieve and every list page, so a per-line list scales the batch\n // object with the input (2000 lines -> ~225 KB per GET). The per-request\n // detail lives in the error file. (The live API's `Batch.Errors` is typed\n // as a plain `BatchError[]` in openai@4.104.0 `resources/batches.d.ts`;\n // that it stays small is UNVERIFIED there but matches its purpose of\n // reporting input-file validation errors, not per-request outcomes.)\n batch.errors = {\n object: \"list\",\n data: [\n { code: \"mock_failure\", message: `${message}: see error_file_id`, line: null, param: null },\n ],\n };\n batch.request_counts = { total, completed: 0, failed: total };\n batch.failed_at = now();\n } else if (outcome === \"expired\") {\n batch.request_counts = { total, completed: 0, failed: 0 };\n batch.expired_at = now();\n } else {\n batch.request_counts = { total, completed: 0, failed: 0 };\n batch.cancelled_at = now();\n }\n}\n\nfunction advance(batch: BatchObject): BatchObject {\n if (isTerminal(batch.status)) {\n return batch;\n }\n if (batch.status === \"cancelling\") {\n // Same arm the `X-AIMock-Batch-Outcome: cancelled` path lands on, so a\n // route-cancelled batch reports the same `request_counts`.\n finish(batch, \"cancelled\");\n return batch;\n }\n const polls = (batchPolls.get(batch.id) ?? 0) + 1;\n batchPolls.set(batch.id, polls);\n if (polls === 1) {\n batch.status = \"in_progress\";\n batch.in_progress_at = now();\n // `total` is known once the input has been read; the terminal arms in\n // `finish()` fill in `completed` / `failed`. (Whether the live API stamps\n // `total` exactly at `in_progress` is UNVERIFIED: openai@4.104.0\n // `resources/batches.d.ts` types `BatchRequestCounts` but not its timing.)\n batch.request_counts = { total: inputRequests(batch).length, completed: 0, failed: 0 };\n } else {\n finish(batch, batchOutcomes.get(batch.id) ?? \"completed\");\n }\n return batch;\n}\n\nexport async function handleBatchesCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? \"/v1/batches\";\n const method = req.method ?? \"POST\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n\n let body: unknown;\n try {\n body = JSON.parse(raw) as unknown;\n } catch (err) {\n const detail = err instanceof Error ? err.message : \"unknown\";\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(`Malformed JSON: ${detail}`), setCorsHeaders);\n return;\n }\n if (!isJsonObject(body)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(\"Request body must be a JSON object\"), setCorsHeaders);\n return;\n }\n const inputFileId = body[\"input_file_id\"];\n const endpoint = body[\"endpoint\"];\n const window = body[\"completion_window\"];\n if (typeof inputFileId !== \"string\" || inputFileId.length === 0) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\"Invalid parameter: 'input_file_id' must be a non-empty string\"),\n setCorsHeaders,\n );\n return;\n }\n if (typeof endpoint !== \"string\" || !VALID_ENDPOINTS.has(endpoint)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\n `Invalid endpoint '${String(endpoint)}'. Expected one of: ${[...VALID_ENDPOINTS].join(\", \")}`,\n ),\n setCorsHeaders,\n );\n return;\n }\n if (window !== \"24h\") {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\"Invalid parameter: 'completion_window' must be '24h'\"),\n setCorsHeaders,\n );\n return;\n }\n // Same shared `Metadata` schema the fine-tuning mock validates and echoes.\n const metadata = readMetadata(body[\"metadata\"]);\n if (!metadata.ok) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(metadata.message), setCorsHeaders);\n return;\n }\n\n const outcomeHeader = req.headers[OUTCOME_HEADER];\n const outcomeRaw = (Array.isArray(outcomeHeader) ? outcomeHeader[0] : outcomeHeader)\n ?.trim()\n .toLowerCase();\n if (outcomeRaw !== undefined && !OUTCOMES.has(outcomeRaw)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\n `Invalid X-AIMock-Batch-Outcome '${outcomeRaw}'. Expected one of: ${[...OUTCOMES].join(\", \")}`,\n ),\n setCorsHeaders,\n );\n return;\n }\n\n const id = generateId(\"batch\");\n const batch: BatchObject = {\n id,\n object: \"batch\",\n endpoint,\n input_file_id: inputFileId,\n completion_window: \"24h\",\n status: \"validating\",\n created_at: Math.floor(Date.now() / 1000),\n request_counts: { total: 0, completed: 0, failed: 0 },\n };\n if (metadata.value !== null) batch.metadata = metadata.value;\n batches.set(id, batch);\n batchPolls.set(id, 0);\n if (outcomeRaw !== undefined) batchOutcomes.set(id, outcomeRaw as BatchOutcome);\n defaults.logger.debug(`Batches mock: created ${id}`);\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, batch, setCorsHeaders);\n}\n\nexport async function handleBatchesList(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? \"/v1/batches\";\n const method = req.method ?? \"GET\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n // Newest-first, mirroring `handleFineTuningList`: `paginate()` documents a\n // newest-first input, and reversing insertion order is the stable total\n // order a cursor walk needs (`created_at` has one-second resolution, so a\n // sort on it cannot order the ties that are the normal case).\n const data = [...batches.values()].reverse();\n // `after`/`limit` cursor paging shared with the fine-tuning mock; the real\n // list response also carries `first_id`/`last_id` (null on an empty page).\n const result = paginate(data, req.url);\n if (!result.ok) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(result.message), setCorsHeaders);\n return;\n }\n const page = result.page.data;\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(\n res,\n 200,\n {\n ...result.page,\n first_id: page[0]?.id ?? null,\n last_id: page[page.length - 1]?.id ?? null,\n },\n setCorsHeaders,\n );\n}\n\nexport async function handleBatchesRetrieve(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n batchId: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? `/v1/batches/${batchId}`;\n const method = req.method ?? \"GET\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n const batch = batches.get(batchId);\n if (!batch) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 404);\n writeJson(res, 404, invalid(`No such batch: ${batchId}`), setCorsHeaders);\n return;\n }\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, advance(batch), setCorsHeaders);\n}\n\nexport async function handleBatchesCancel(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n batchId: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? `/v1/batches/${batchId}/cancel`;\n const method = req.method ?? \"POST\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n const batch = batches.get(batchId);\n if (!batch) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 404);\n writeJson(res, 404, invalid(`No such batch: ${batchId}`), setCorsHeaders);\n return;\n }\n if (isTerminal(batch.status)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(`Cannot cancel a batch with status ${batch.status}.`),\n setCorsHeaders,\n );\n return;\n }\n // Idempotent on an already-cancelling batch: no second stamp.\n if (batch.status !== \"cancelling\") {\n batch.status = \"cancelling\";\n batch.cancelling_at = now();\n }\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, batch, setCorsHeaders);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"batches.js","names":[],"sources":["../src/batches.ts"],"sourcesContent":["/**\n * OpenAI Batches API mock.\n *\n * Covers `POST /v1/batches`, `GET /v1/batches`, `GET /v1/batches/{id}` and\n * `POST /v1/batches/{id}/cancel` with a deterministic poll progression so\n * batch flows can be tested without waiting 24h:\n *\n * validating → in_progress → completed | failed | expired | cancelled\n *\n * The first retrieve after create advances to `in_progress`, the second (and\n * later) to the batch's outcome. Terminal batches are stable. On `completed`\n * the mock registers a REAL file in the Files store (`output_file_id`, purpose\n * `batch_output`, filename `<batch-id>_output.jsonl`) holding one JSONL line\n * per input request, so `GET /v1/files/{id}/content` serves it back and the\n * upload → create → poll → download flow closes. When `input_file_id` names a\n * file in the store, its JSONL lines drive the `custom_id`s and\n * `request_counts.total`; when it does not (the id was never uploaded here),\n * the mock synthesizes ONE request (`custom_id: \"request-1\"`, `total: 1`) so\n * the output file is still downloadable.\n *\n * Outcome control: the real API only ever completes a healthy batch, so a\n * suite that has to exercise its failure handling needs a lever. Send\n * `X-AIMock-Batch-Outcome: completed | failed | expired | cancelled` on\n * `POST /v1/batches` (default `completed`, unknown values 400). `failed`\n * populates a one-entry `errors` list and a real `error_file_id` (one JSONL\n * error line per request) and stamps `failed_at`; `expired` stamps `expired_at`; `cancelled`\n * lands the batch on `cancelled` without a cancel call. Same `X-AIMock-*`\n * family as `X-AIMock-Strict` and the chaos headers.\n *\n * `cancel` on a non-terminal batch answers `cancelling` (stamping\n * `cancelling_at`); the next retrieve shows `cancelled` with `cancelled_at`.\n * Cancelling a terminal batch is 400 `Cannot cancel a batch with status\n * <status>.` (the SDK's `Batch.status` union, openai@4.104.0\n * `resources/batches.d.ts`, is the source for the status set and the `*_at`\n * stamps; the exact 400 message text is UNVERIFIED against the live API).\n *\n * Output-line shape (`{id: \"batch_req_…\", custom_id, response: {status_code,\n * request_id, body}, error}` / error lines with `response: null` and\n * `error: {code, message}`) follows the OpenAI Batch guide's documented\n * result-file format; neither the vendored SDK nor `openai/openai-openapi`\n * types the file's lines, so that shape is UNVERIFIED against a live capture.\n *\n * State is in-memory per process and cleared by the full reset path.\n * Every branch journals with `service: \"batches\"` and runs through the\n * chaos gate so fault-injection suites can target the batch surface.\n */\n\nimport type * as http from \"node:http\";\nimport { randomBytes } from \"node:crypto\";\nimport { flattenHeaders, generateId, isJsonObject } from \"./helpers.js\";\nimport { applyChaosAsync, type ChaosAsyncOutcome } from \"./chaos.js\";\nimport { paginate, readMetadata } from \"./fine-tuning.js\";\nimport { FILES_MAX_BYTES, getStoredFileBytes, storeFile } from \"./files.js\";\nimport { buildTextResponse } from \"./responses.js\";\nimport type { ChaosDefaults } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport { buildOpenAIError } from \"./response/errors.js\";\n\nexport interface BatchObject {\n id: string;\n object: \"batch\";\n endpoint: string;\n input_file_id: string;\n completion_window: string;\n status: BatchStatus;\n created_at: number;\n metadata?: Record<string, string>;\n in_progress_at?: number;\n completed_at?: number;\n failed_at?: number;\n expired_at?: number;\n cancelling_at?: number;\n cancelled_at?: number;\n output_file_id?: string;\n error_file_id?: string;\n errors?: { object: \"list\"; data: BatchError[] };\n request_counts?: { total: number; completed: number; failed: number };\n}\n\n/** `Batch.status` minus `finalizing`, which this mock never lingers in. */\nexport type BatchStatus =\n | \"validating\"\n | \"in_progress\"\n | \"completed\"\n | \"failed\"\n | \"expired\"\n | \"cancelling\"\n | \"cancelled\";\n\n/** `BatchError` from the SDK (`openai@4.104.0` `resources/batches.d.ts`). */\nexport interface BatchError {\n code?: string;\n line?: number | null;\n message?: string;\n param?: string | null;\n}\n\n/**\n * The statuses a batch never leaves. Single source of truth: `advance()`\n * refuses to move a terminal batch and the cancel handler rejects one, so the\n * two cannot drift apart.\n */\nconst TERMINAL_STATUSES = new Set<BatchStatus>([\"completed\", \"failed\", \"expired\", \"cancelled\"]);\n\nfunction isTerminal(status: BatchStatus): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n\n/** Values accepted on `X-AIMock-Batch-Outcome`; where a batch lands on its 2nd retrieve. */\nconst OUTCOMES = new Set([\"completed\", \"failed\", \"expired\", \"cancelled\"]);\ntype BatchOutcome = \"completed\" | \"failed\" | \"expired\" | \"cancelled\";\nconst OUTCOME_HEADER = \"x-aimock-batch-outcome\";\n\n// The `BatchCreateParams.endpoint` union in the vendored `openai` SDK 4.104.0\n// (`resources/batches.d.ts`): `/v1/responses`, `/v1/chat/completions`,\n// `/v1/embeddings`, `/v1/completions`.\nconst VALID_ENDPOINTS = new Set([\n \"/v1/responses\",\n \"/v1/chat/completions\",\n \"/v1/embeddings\",\n \"/v1/completions\",\n]);\n\nconst batches = new Map<string, BatchObject>();\nconst batchPolls = new Map<string, number>();\nconst batchOutcomes = new Map<string, BatchOutcome>();\n\nexport function clearBatchStore(): void {\n batches.clear();\n batchPolls.clear();\n batchOutcomes.clear();\n}\n\nfunction journalBatches(\n journal: Journal,\n method: string,\n path: string,\n headers: Record<string, string>,\n status: number,\n): void {\n journal.add({\n method,\n path,\n headers,\n body: null,\n service: \"batches\",\n response: { status, fixture: null },\n });\n}\n\nfunction writeJson(\n res: http.ServerResponse,\n status: number,\n payload: unknown,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): void {\n setCorsHeaders(res);\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(payload));\n}\n\nfunction invalid(message: string): { error: { message: string; type: string } } {\n return buildOpenAIError(\"service-validation\", { message });\n}\n\ntype Defaults = { logger: Logger; chaos?: ChaosDefaults; registry?: MetricsRegistry };\n\n/**\n * Roll the chaos dice for one batches request. Returns true when a fault\n * fired and the response is already written, so the caller returns early.\n *\n * CORS headers go on BEFORE the roll, because a fault writes the response\n * itself and never reaches `writeJson`. `service: \"batches\"` on the journal\n * context keeps faulted requests visible under `?service=batches`. Mirrors\n * the `chaosHit` in `src/fine-tuning.ts`.\n */\nasync function chaosHit(\n req: http.IncomingMessage,\n journal: Journal,\n defaults: Defaults,\n method: string,\n path: string,\n res: http.ServerResponse,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<ChaosAsyncOutcome> {\n setCorsHeaders(res);\n return await applyChaosAsync(\n res,\n null,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"batches\",\n },\n \"internal\",\n defaults.registry,\n defaults.logger,\n );\n}\n\nfunction now(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/**\n * The input file's requests, as `{ custom_id, model }` per JSONL line. Lines\n * that are not JSON objects still count as requests (the real API reports\n * them as errors by line; here they get a positional `custom_id`). An\n * `input_file_id` that was never uploaded to this mock yields one synthesized\n * request — see the module docblock.\n */\nfunction inputRequests(batch: BatchObject): { custom_id: string; model: string }[] {\n const bytes = getStoredFileBytes(batch.input_file_id);\n const lines =\n bytes === undefined\n ? []\n : bytes\n .toString(\"utf8\")\n .split(\"\\n\")\n .filter((l) => l.trim() !== \"\");\n const requests = lines.map((line, i) => {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line) as unknown;\n } catch {\n parsed = undefined;\n }\n const obj = isJsonObject(parsed) ? parsed : {};\n const body = isJsonObject(obj[\"body\"]) ? obj[\"body\"] : {};\n return {\n custom_id: typeof obj[\"custom_id\"] === \"string\" ? obj[\"custom_id\"] : `request-${i + 1}`,\n model: typeof body[\"model\"] === \"string\" ? body[\"model\"] : \"aimock\",\n };\n });\n return requests.length > 0 ? requests : [{ custom_id: \"request-1\", model: \"aimock\" }];\n}\n\n/** A minimal per-endpoint success body for one output line. */\nfunction outputBody(endpoint: string, model: string): Record<string, unknown> {\n const created = now();\n if (endpoint === \"/v1/responses\") {\n // The same `Response` envelope the live `/v1/responses` mock writes\n // (`object: \"response\"`, `status`, `output[]` message with `output_text`,\n // `usage`), per openai@4.104.0 `resources/responses/responses.d.ts`.\n return buildTextResponse(\"Mock batch completion\", model) as Record<string, unknown>;\n }\n if (endpoint === \"/v1/embeddings\") {\n return {\n object: \"list\",\n model,\n data: [{ object: \"embedding\", index: 0, embedding: [0, 0, 0] }],\n usage: { prompt_tokens: 1, total_tokens: 1 },\n };\n }\n if (endpoint === \"/v1/completions\") {\n return {\n id: generateId(\"cmpl\"),\n object: \"text_completion\",\n created,\n model,\n choices: [{ index: 0, text: \"Mock batch completion\", finish_reason: \"stop\" }],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n };\n }\n return {\n id: generateId(\"chatcmpl\"),\n object: \"chat.completion\",\n created,\n model,\n choices: [\n {\n index: 0,\n message: { role: \"assistant\", content: \"Mock batch completion\" },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n };\n}\n\n/** `batch_req_…` / `req_…` — underscore-joined like the real ids, unlike `generateId`. */\nfunction underscoreId(prefix: string): string {\n return `${prefix}_${randomBytes(12).toString(\"base64url\")}`;\n}\n\n/**\n * Serialise one JSONL line per request, stopping the moment the running byte\n * count passes the Files store's per-file cap. Every output line is a fixed\n * ~430-byte canned body regardless of how small the input line was (a 3-byte\n * `{}` line amplifies ~143x), so an input file that is itself under the\n * upload cap can describe an output far over it; building the lines eagerly\n * would hold that whole over-cap buffer before anything could refuse it.\n * Returns `undefined` when the cap is exceeded, with at most `FILES_MAX_BYTES`\n * ever materialised.\n */\nfunction jsonlUnderCap<T>(items: T[], line: (item: T) => unknown): Buffer | undefined {\n const chunks: Buffer[] = [];\n let bytes = 0;\n for (const item of items) {\n const chunk = Buffer.from(JSON.stringify(line(item)) + \"\\n\", \"utf8\");\n bytes += chunk.length;\n if (bytes > FILES_MAX_BYTES) {\n return undefined;\n }\n chunks.push(chunk);\n }\n return Buffer.concat(chunks, bytes);\n}\n\n/**\n * The batch could not mint its result file under {@link FILES_MAX_BYTES}: it\n * fails with one `errors` entry naming the cap. `BatchError.code` is an open\n * `string` in the SDK types and the real API has no equivalent condition, so\n * the code value is UNVERIFIED against a live capture.\n */\nfunction failOverCap(batch: BatchObject, total: number, kind: \"output\" | \"error\"): void {\n batch.status = \"failed\";\n batch.errors = {\n object: \"list\",\n data: [\n {\n code: \"output_file_too_large\",\n message: `Batch ${kind} file would exceed the ${FILES_MAX_BYTES} byte per-file cap`,\n line: null,\n param: null,\n },\n ],\n };\n batch.request_counts = { total, completed: 0, failed: total };\n batch.failed_at = now();\n}\n\nfunction finish(batch: BatchObject, outcome: BatchOutcome): void {\n const requests = inputRequests(batch);\n const total = requests.length;\n batch.status = outcome;\n if (outcome === \"completed\") {\n const content = jsonlUnderCap(requests, (r) => ({\n id: underscoreId(\"batch_req\"),\n custom_id: r.custom_id,\n response: {\n status_code: 200,\n request_id: underscoreId(\"req\"),\n body: outputBody(batch.endpoint, r.model),\n },\n error: null,\n }));\n if (content === undefined) {\n failOverCap(batch, total, \"output\");\n return;\n }\n batch.output_file_id = storeFile({\n purpose: \"batch_output\",\n filename: `${batch.id}_output.jsonl`,\n content,\n }).id;\n batch.request_counts = { total, completed: total, failed: 0 };\n batch.completed_at = now();\n } else if (outcome === \"failed\") {\n const message = \"Mock batch failure (X-AIMock-Batch-Outcome: failed)\";\n const content = jsonlUnderCap(requests, (r) => ({\n id: underscoreId(\"batch_req\"),\n custom_id: r.custom_id,\n response: null,\n error: { code: \"mock_failure\", message },\n }));\n if (content === undefined) {\n failOverCap(batch, total, \"error\");\n return;\n }\n batch.error_file_id = storeFile({\n purpose: \"batch_output\",\n filename: `${batch.id}_error.jsonl`,\n content,\n }).id;\n // One file-level entry, not one per request: `errors` rides inline on\n // every retrieve and every list page, so a per-line list scales the batch\n // object with the input (2000 lines -> ~225 KB per GET). The per-request\n // detail lives in the error file. (The live API's `Batch.Errors` is typed\n // as a plain `BatchError[]` in openai@4.104.0 `resources/batches.d.ts`;\n // that it stays small is UNVERIFIED there but matches its purpose of\n // reporting input-file validation errors, not per-request outcomes.)\n batch.errors = {\n object: \"list\",\n data: [\n { code: \"mock_failure\", message: `${message}: see error_file_id`, line: null, param: null },\n ],\n };\n batch.request_counts = { total, completed: 0, failed: total };\n batch.failed_at = now();\n } else if (outcome === \"expired\") {\n batch.request_counts = { total, completed: 0, failed: 0 };\n batch.expired_at = now();\n } else {\n batch.request_counts = { total, completed: 0, failed: 0 };\n batch.cancelled_at = now();\n }\n}\n\nfunction advance(batch: BatchObject): BatchObject {\n if (isTerminal(batch.status)) {\n return batch;\n }\n if (batch.status === \"cancelling\") {\n // Same arm the `X-AIMock-Batch-Outcome: cancelled` path lands on, so a\n // route-cancelled batch reports the same `request_counts`.\n finish(batch, \"cancelled\");\n return batch;\n }\n const polls = (batchPolls.get(batch.id) ?? 0) + 1;\n batchPolls.set(batch.id, polls);\n if (polls === 1) {\n batch.status = \"in_progress\";\n batch.in_progress_at = now();\n // `total` is known once the input has been read; the terminal arms in\n // `finish()` fill in `completed` / `failed`. (Whether the live API stamps\n // `total` exactly at `in_progress` is UNVERIFIED: openai@4.104.0\n // `resources/batches.d.ts` types `BatchRequestCounts` but not its timing.)\n batch.request_counts = { total: inputRequests(batch).length, completed: 0, failed: 0 };\n } else {\n finish(batch, batchOutcomes.get(batch.id) ?? \"completed\");\n }\n return batch;\n}\n\nexport async function handleBatchesCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? \"/v1/batches\";\n const method = req.method ?? \"POST\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n\n let body: unknown;\n try {\n body = JSON.parse(raw) as unknown;\n } catch (err) {\n const detail = err instanceof Error ? err.message : \"unknown\";\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(`Malformed JSON: ${detail}`), setCorsHeaders);\n return;\n }\n if (!isJsonObject(body)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(\"Request body must be a JSON object\"), setCorsHeaders);\n return;\n }\n const inputFileId = body[\"input_file_id\"];\n const endpoint = body[\"endpoint\"];\n const window = body[\"completion_window\"];\n if (typeof inputFileId !== \"string\" || inputFileId.length === 0) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\"Invalid parameter: 'input_file_id' must be a non-empty string\"),\n setCorsHeaders,\n );\n return;\n }\n if (typeof endpoint !== \"string\" || !VALID_ENDPOINTS.has(endpoint)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\n `Invalid endpoint '${String(endpoint)}'. Expected one of: ${[...VALID_ENDPOINTS].join(\", \")}`,\n ),\n setCorsHeaders,\n );\n return;\n }\n if (window !== \"24h\") {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\"Invalid parameter: 'completion_window' must be '24h'\"),\n setCorsHeaders,\n );\n return;\n }\n // Same shared `Metadata` schema the fine-tuning mock validates and echoes.\n const metadata = readMetadata(body[\"metadata\"]);\n if (!metadata.ok) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(metadata.message), setCorsHeaders);\n return;\n }\n\n const outcomeHeader = req.headers[OUTCOME_HEADER];\n const outcomeRaw = (Array.isArray(outcomeHeader) ? outcomeHeader[0] : outcomeHeader)\n ?.trim()\n .toLowerCase();\n if (outcomeRaw !== undefined && !OUTCOMES.has(outcomeRaw)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(\n `Invalid X-AIMock-Batch-Outcome '${outcomeRaw}'. Expected one of: ${[...OUTCOMES].join(\", \")}`,\n ),\n setCorsHeaders,\n );\n return;\n }\n\n const id = generateId(\"batch\");\n const batch: BatchObject = {\n id,\n object: \"batch\",\n endpoint,\n input_file_id: inputFileId,\n completion_window: \"24h\",\n status: \"validating\",\n created_at: Math.floor(Date.now() / 1000),\n request_counts: { total: 0, completed: 0, failed: 0 },\n };\n if (metadata.value !== null) batch.metadata = metadata.value;\n batches.set(id, batch);\n batchPolls.set(id, 0);\n if (outcomeRaw !== undefined) batchOutcomes.set(id, outcomeRaw as BatchOutcome);\n defaults.logger.debug(`Batches mock: created ${id}`);\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, batch, setCorsHeaders);\n}\n\nexport async function handleBatchesList(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? \"/v1/batches\";\n const method = req.method ?? \"GET\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n // Newest-first, mirroring `handleFineTuningList`: `paginate()` documents a\n // newest-first input, and reversing insertion order is the stable total\n // order a cursor walk needs (`created_at` has one-second resolution, so a\n // sort on it cannot order the ties that are the normal case).\n const data = [...batches.values()].reverse();\n // `after`/`limit` cursor paging shared with the fine-tuning mock; the real\n // list response also carries `first_id`/`last_id` (null on an empty page).\n const result = paginate(data, req.url);\n if (!result.ok) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(res, 400, invalid(result.message), setCorsHeaders);\n return;\n }\n const page = result.page.data;\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(\n res,\n 200,\n {\n ...result.page,\n first_id: page[0]?.id ?? null,\n last_id: page[page.length - 1]?.id ?? null,\n },\n setCorsHeaders,\n );\n}\n\nexport async function handleBatchesRetrieve(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n batchId: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? `/v1/batches/${batchId}`;\n const method = req.method ?? \"GET\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n const batch = batches.get(batchId);\n if (!batch) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 404);\n writeJson(res, 404, invalid(`No such batch: ${batchId}`), setCorsHeaders);\n return;\n }\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, advance(batch), setCorsHeaders);\n}\n\nexport async function handleBatchesCancel(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n batchId: string,\n journal: Journal,\n defaults: Defaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const path = req.url ?? `/v1/batches/${batchId}/cancel`;\n const method = req.method ?? \"POST\";\n if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;\n const batch = batches.get(batchId);\n if (!batch) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 404);\n writeJson(res, 404, invalid(`No such batch: ${batchId}`), setCorsHeaders);\n return;\n }\n if (isTerminal(batch.status)) {\n journalBatches(journal, method, path, flattenHeaders(req.headers), 400);\n writeJson(\n res,\n 400,\n invalid(`Cannot cancel a batch with status ${batch.status}.`),\n setCorsHeaders,\n );\n return;\n }\n // Idempotent on an already-cancelling batch: no second stamp.\n if (batch.status !== \"cancelling\") {\n batch.status = \"cancelling\";\n batch.cancelling_at = now();\n }\n journalBatches(journal, method, path, flattenHeaders(req.headers), 200);\n writeJson(res, 200, batch, setCorsHeaders);\n}\n"],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ const require_errors = require('./response/errors.cjs');
2
+ const require_policy = require('./response/policy.cjs');
1
3
  const require_helpers = require('./helpers.cjs');
2
4
  const require_sse_writer = require('./sse-writer.cjs');
3
5
  const require_chaos = require('./chaos.cjs');
@@ -5,15 +7,14 @@ const require_misbehavior = require('./misbehavior.cjs');
5
7
  const require_router = require('./router.cjs');
6
8
  const require_recorder = require('./recorder.cjs');
7
9
  const require_interruption = require('./interruption.cjs');
10
+ const require_finish = require('./response/finish.cjs');
8
11
  const require_aws_event_stream = require('./aws-event-stream.cjs');
12
+ const require_converse = require('./response/usage/codecs/converse.cjs');
9
13
 
10
14
  //#region src/bedrock-converse.ts
11
15
  function converseStopReason(overrideFinishReason, defaultReason) {
12
16
  if (!overrideFinishReason) return defaultReason;
13
- if (overrideFinishReason === "stop") return "end_turn";
14
- if (overrideFinishReason === "tool_calls") return "tool_use";
15
- if (overrideFinishReason === "length") return "max_tokens";
16
- return overrideFinishReason;
17
+ return require_finish.mapAnthropicFinish(overrideFinishReason);
17
18
  }
18
19
  /**
19
20
  * Build Converse-format usage from fixture overrides.
@@ -24,18 +25,7 @@ function converseStopReason(overrideFinishReason, defaultReason) {
24
25
  * usage should supply explicit `usage` overrides in their fixture.
25
26
  */
26
27
  function converseUsage(overrides) {
27
- if (!overrides?.usage) return {
28
- inputTokens: 0,
29
- outputTokens: 0,
30
- totalTokens: 0
31
- };
32
- const inputTokens = overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0;
33
- const outputTokens = overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0;
34
- return {
35
- inputTokens,
36
- outputTokens,
37
- totalTokens: inputTokens + outputTokens
38
- };
28
+ return require_converse.render(overrides);
39
29
  }
40
30
  function parseConverseToolArgumentsForStream(toolCall, logger) {
41
31
  return require_helpers.servedToolArgs(toolCall, "string", logger).text;
@@ -519,7 +509,7 @@ function prepareConverseMisbehavior(plan, request, stream, defaults, headers) {
519
509
  } : block;
520
510
  });
521
511
  }
522
- const reasoning = plan.reasoning ?? require_helpers.resolveReasoningForModel("reasoning" in response ? response.reasoning : void 0, request.model, require_helpers.resolveStrictMode(defaults.strict, headers), defaults.logger) ?? "";
512
+ const reasoning = plan.reasoning ?? require_helpers.resolveReasoningForModel("reasoning" in response ? response.reasoning : void 0, request.model, require_policy.resolveRequestDecisions(defaults.strict, headers).reasoningSuppression, defaults.logger) ?? "";
523
513
  const servedToolCalls = blocks.flatMap((block) => block.type === "toolCall" ? [{
524
514
  name: block.name,
525
515
  arguments: block.arguments,
@@ -538,11 +528,10 @@ function prepareConverseMisbehavior(plan, request, stream, defaults, headers) {
538
528
  blocks,
539
529
  reasoning,
540
530
  stopReason,
541
- usage: {
542
- inputTokens,
543
- outputTokens,
544
- totalTokens: inputTokens + outputTokens
545
- }
531
+ usage: require_converse.render(void 0, {
532
+ input: inputTokens,
533
+ output: outputTokens
534
+ })
546
535
  };
547
536
  }
548
537
  function buildConverseMisbehaviorResponse(prepared) {
@@ -651,10 +640,10 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
651
640
  fixture: null
652
641
  }
653
642
  });
654
- require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
643
+ require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
655
644
  message: `Malformed JSON: ${detail}`,
656
645
  type: "invalid_request_error"
657
- } }));
646
+ }).body);
658
647
  return;
659
648
  }
660
649
  if (!require_helpers.isJsonObject(converseReq)) {
@@ -668,10 +657,10 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
668
657
  fixture: null
669
658
  }
670
659
  });
671
- require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
660
+ require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
672
661
  message: "Request body must be a JSON object",
673
662
  type: "invalid_request_error"
674
- } }));
663
+ }).body);
675
664
  return;
676
665
  }
677
666
  const messageError = !Array.isArray(converseReq.messages) ? "Invalid request: messages array is required" : validateConverseMessages(converseReq) ?? validateConverseSystem(converseReq) ?? validateConverseTools(converseReq);
@@ -686,10 +675,10 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
686
675
  fixture: null
687
676
  }
688
677
  });
689
- require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
678
+ require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
690
679
  message: messageError,
691
680
  type: "invalid_request_error"
692
- } }));
681
+ }).body);
693
682
  return;
694
683
  }
695
684
  const completionReq = converseToCompletionRequest(converseReq, modelId, logger);
@@ -707,7 +696,7 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
707
696
  body: completionReq
708
697
  }, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
709
698
  if (!fixture) {
710
- if (require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
699
+ if (require_policy.resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
711
700
  const strictStatus = 503;
712
701
  const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
713
702
  logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", urlPath, skippedBySequenceOrTurn));
@@ -719,13 +708,13 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
719
708
  response: {
720
709
  status: strictStatus,
721
710
  fixture: null,
722
- ...require_helpers.strictOverrideField(defaults.strict, req.headers)
711
+ ...require_policy.strictOverrideField(defaults.strict, req.headers)
723
712
  }
724
713
  });
725
- require_sse_writer.writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
714
+ require_sse_writer.writeErrorResponse(res, strictStatus, require_errors.renderError("bedrock", "strict-miss", {
726
715
  message: strictMessage,
727
716
  type: "invalid_request_error"
728
- } }));
717
+ }).body);
729
718
  return;
730
719
  }
731
720
  if (defaults.record) {
@@ -754,13 +743,13 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
754
743
  response: {
755
744
  status: 404,
756
745
  fixture: null,
757
- ...require_helpers.strictOverrideField(defaults.strict, req.headers)
746
+ ...require_policy.strictOverrideField(defaults.strict, req.headers)
758
747
  }
759
748
  });
760
- require_sse_writer.writeErrorResponse(res, 404, JSON.stringify({ error: {
749
+ require_sse_writer.writeErrorResponse(res, 404, require_errors.renderError("bedrock", "fixture-miss", {
761
750
  message: "No fixture matched",
762
751
  type: "invalid_request_error"
763
- } }));
752
+ }).body);
764
753
  return;
765
754
  }
766
755
  const response = await require_helpers.resolveResponse(fixture, completionReq);
@@ -800,11 +789,11 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
800
789
  });
801
790
  const type = misbehavior.status === 400 ? "ValidationException" : "InternalServerException";
802
791
  res.setHeader("x-amzn-errortype", type);
803
- require_sse_writer.writeErrorResponse(res, misbehavior.status, JSON.stringify({
804
- __type: type,
792
+ require_sse_writer.writeErrorResponse(res, misbehavior.status, require_errors.renderError("bedrock-converse", "misbehavior", {
793
+ type,
805
794
  message: misbehavior.message,
806
795
  code: misbehavior.code
807
- }));
796
+ }).body);
808
797
  return;
809
798
  }
810
799
  if (misbehavior.kind === "applied") {
@@ -837,20 +826,16 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
837
826
  fixture
838
827
  }
839
828
  });
840
- const errBody = {
841
- type: "error",
842
- error: {
843
- type: response.error.type ?? "invalid_request_error",
844
- message: response.error.message
845
- }
846
- };
847
- require_sse_writer.writeErrorResponse(res, status, JSON.stringify(errBody), { retryAfter: response.retryAfter });
829
+ require_sse_writer.writeErrorResponse(res, status, require_errors.renderError("bedrock", "fixture", {
830
+ message: response.error.message,
831
+ type: response.error.type ?? "invalid_request_error"
832
+ }).body, { retryAfter: response.retryAfter });
848
833
  return;
849
834
  }
850
835
  if (require_helpers.isCombinedFixtureResponse(response)) {
851
836
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
852
837
  const overrides = require_helpers.extractOverrides(response);
853
- const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_helpers.resolveStrictMode(defaults.strict, req.headers), logger);
838
+ const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
854
839
  addResponseEntry({
855
840
  method: req.method ?? "POST",
856
841
  path: urlPath,
@@ -870,7 +855,7 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
870
855
  if (require_helpers.isTextResponse(response)) {
871
856
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
872
857
  const overrides = require_helpers.extractOverrides(response);
873
- const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_helpers.resolveStrictMode(defaults.strict, req.headers), logger);
858
+ const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
874
859
  addResponseEntry({
875
860
  method: req.method ?? "POST",
876
861
  path: urlPath,
@@ -889,7 +874,7 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
889
874
  if (require_helpers.isToolCallResponse(response)) {
890
875
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
891
876
  const overrides = require_helpers.extractOverrides(response);
892
- const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_helpers.resolveStrictMode(defaults.strict, req.headers), logger);
877
+ const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
893
878
  addResponseEntry({
894
879
  method: req.method ?? "POST",
895
880
  path: urlPath,
@@ -915,10 +900,10 @@ async function handleConverse(req, res, raw, modelId, fixtures, journal, default
915
900
  fixture
916
901
  }
917
902
  });
918
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
903
+ require_sse_writer.writeErrorResponse(res, 500, require_errors.renderError("bedrock", "unknown-response", {
919
904
  message: "Fixture response did not match any known type",
920
905
  type: "server_error"
921
- } }));
906
+ }).body);
922
907
  }
923
908
  async function handleConverseStream(req, res, raw, modelId, fixtures, journal, defaults, setCorsHeaders) {
924
909
  const { logger } = defaults;
@@ -939,10 +924,10 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
939
924
  fixture: null
940
925
  }
941
926
  });
942
- require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
927
+ require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
943
928
  message: `Malformed JSON: ${detail}`,
944
929
  type: "invalid_request_error"
945
- } }));
930
+ }).body);
946
931
  return;
947
932
  }
948
933
  if (!require_helpers.isJsonObject(converseReq)) {
@@ -956,10 +941,10 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
956
941
  fixture: null
957
942
  }
958
943
  });
959
- require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
944
+ require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
960
945
  message: "Request body must be a JSON object",
961
946
  type: "invalid_request_error"
962
- } }));
947
+ }).body);
963
948
  return;
964
949
  }
965
950
  const messageError = !Array.isArray(converseReq.messages) ? "Invalid request: messages array is required" : validateConverseMessages(converseReq) ?? validateConverseSystem(converseReq) ?? validateConverseTools(converseReq);
@@ -974,10 +959,10 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
974
959
  fixture: null
975
960
  }
976
961
  });
977
- require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
962
+ require_sse_writer.writeErrorResponse(res, 400, require_errors.renderError("bedrock", "validation", {
978
963
  message: messageError,
979
964
  type: "invalid_request_error"
980
- } }));
965
+ }).body);
981
966
  return;
982
967
  }
983
968
  const completionReq = converseToCompletionRequest(converseReq, modelId, logger);
@@ -996,7 +981,7 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
996
981
  body: completionReq
997
982
  }, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
998
983
  if (!fixture) {
999
- if (require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
984
+ if (require_policy.resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
1000
985
  const strictStatus = 503;
1001
986
  const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
1002
987
  logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", urlPath, skippedBySequenceOrTurn));
@@ -1008,13 +993,13 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
1008
993
  response: {
1009
994
  status: strictStatus,
1010
995
  fixture: null,
1011
- ...require_helpers.strictOverrideField(defaults.strict, req.headers)
996
+ ...require_policy.strictOverrideField(defaults.strict, req.headers)
1012
997
  }
1013
998
  });
1014
- require_sse_writer.writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
999
+ require_sse_writer.writeErrorResponse(res, strictStatus, require_errors.renderError("bedrock", "strict-miss", {
1015
1000
  message: strictMessage,
1016
1001
  type: "invalid_request_error"
1017
- } }));
1002
+ }).body);
1018
1003
  return;
1019
1004
  }
1020
1005
  if (defaults.record) {
@@ -1043,13 +1028,13 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
1043
1028
  response: {
1044
1029
  status: 404,
1045
1030
  fixture: null,
1046
- ...require_helpers.strictOverrideField(defaults.strict, req.headers)
1031
+ ...require_policy.strictOverrideField(defaults.strict, req.headers)
1047
1032
  }
1048
1033
  });
1049
- require_sse_writer.writeErrorResponse(res, 404, JSON.stringify({ error: {
1034
+ require_sse_writer.writeErrorResponse(res, 404, require_errors.renderError("bedrock", "fixture-miss", {
1050
1035
  message: "No fixture matched",
1051
1036
  type: "invalid_request_error"
1052
- } }));
1037
+ }).body);
1053
1038
  return;
1054
1039
  }
1055
1040
  const response = await require_helpers.resolveResponse(fixture, completionReq);
@@ -1089,11 +1074,11 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
1089
1074
  });
1090
1075
  const type = misbehavior.status === 400 ? "ValidationException" : "InternalServerException";
1091
1076
  res.setHeader("x-amzn-errortype", type);
1092
- require_sse_writer.writeErrorResponse(res, misbehavior.status, JSON.stringify({
1093
- __type: type,
1077
+ require_sse_writer.writeErrorResponse(res, misbehavior.status, require_errors.renderError("bedrock-converse", "misbehavior", {
1078
+ type,
1094
1079
  message: misbehavior.message,
1095
1080
  code: misbehavior.code
1096
- }));
1081
+ }).body);
1097
1082
  return;
1098
1083
  }
1099
1084
  const latency = fixture.latency ?? defaults.latency;
@@ -1140,20 +1125,16 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
1140
1125
  fixture
1141
1126
  }
1142
1127
  });
1143
- const errBody = {
1144
- type: "error",
1145
- error: {
1146
- type: response.error.type ?? "invalid_request_error",
1147
- message: response.error.message
1148
- }
1149
- };
1150
- require_sse_writer.writeErrorResponse(res, status, JSON.stringify(errBody), { retryAfter: response.retryAfter });
1128
+ require_sse_writer.writeErrorResponse(res, status, require_errors.renderError("bedrock", "fixture", {
1129
+ message: response.error.message,
1130
+ type: response.error.type ?? "invalid_request_error"
1131
+ }).body, { retryAfter: response.retryAfter });
1151
1132
  return;
1152
1133
  }
1153
1134
  if (require_helpers.isCombinedFixtureResponse(response)) {
1154
1135
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
1155
1136
  const overrides = require_helpers.extractOverrides(response);
1156
- const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_helpers.resolveStrictMode(defaults.strict, req.headers), logger);
1137
+ const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
1157
1138
  const journalEntry = addResponseEntry({
1158
1139
  method: req.method ?? "POST",
1159
1140
  path: urlPath,
@@ -1185,7 +1166,7 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
1185
1166
  if (require_helpers.isTextResponse(response)) {
1186
1167
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
1187
1168
  const overrides = require_helpers.extractOverrides(response);
1188
- const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_helpers.resolveStrictMode(defaults.strict, req.headers), logger);
1169
+ const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
1189
1170
  const journalEntry = addResponseEntry({
1190
1171
  method: req.method ?? "POST",
1191
1172
  path: urlPath,
@@ -1216,7 +1197,7 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
1216
1197
  if (require_helpers.isToolCallResponse(response)) {
1217
1198
  if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Bedrock Converse API — ignoring");
1218
1199
  const overrides = require_helpers.extractOverrides(response);
1219
- const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_helpers.resolveStrictMode(defaults.strict, req.headers), logger);
1200
+ const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
1220
1201
  const journalEntry = addResponseEntry({
1221
1202
  method: req.method ?? "POST",
1222
1203
  path: urlPath,
@@ -1254,10 +1235,10 @@ async function handleConverseStream(req, res, raw, modelId, fixtures, journal, d
1254
1235
  fixture
1255
1236
  }
1256
1237
  });
1257
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
1238
+ require_sse_writer.writeErrorResponse(res, 500, require_errors.renderError("bedrock", "unknown-response", {
1258
1239
  message: "Fixture response did not match any known type",
1259
1240
  type: "server_error"
1260
- } }));
1241
+ }).body);
1261
1242
  }
1262
1243
 
1263
1244
  //#endregion