@copilotkit/aimock 1.45.1 → 1.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (492) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +25 -1
  4. package/README.md +11 -2
  5. package/dist/agui-mock.cjs +25 -25
  6. package/dist/agui-mock.cjs.map +1 -1
  7. package/dist/agui-mock.d.cts.map +1 -1
  8. package/dist/agui-mock.d.ts.map +1 -1
  9. package/dist/agui-mock.js +25 -25
  10. package/dist/agui-mock.js.map +1 -1
  11. package/dist/agui-types.d.cts.map +1 -1
  12. package/dist/aimock-cli.cjs +15 -1
  13. package/dist/aimock-cli.cjs.map +1 -1
  14. package/dist/aimock-cli.d.cts.map +1 -1
  15. package/dist/aimock-cli.d.ts.map +1 -1
  16. package/dist/aimock-cli.js +16 -2
  17. package/dist/aimock-cli.js.map +1 -1
  18. package/dist/api-key-auth.cjs +10 -9
  19. package/dist/api-key-auth.cjs.map +1 -1
  20. package/dist/api-key-auth.d.cts.map +1 -1
  21. package/dist/api-key-auth.d.ts.map +1 -1
  22. package/dist/api-key-auth.js +11 -10
  23. package/dist/api-key-auth.js.map +1 -1
  24. package/dist/aws-event-stream.cjs +20 -23
  25. package/dist/aws-event-stream.cjs.map +1 -1
  26. package/dist/aws-event-stream.d.cts +0 -1
  27. package/dist/aws-event-stream.d.ts +0 -1
  28. package/dist/aws-event-stream.js +20 -24
  29. package/dist/aws-event-stream.js.map +1 -1
  30. package/dist/batches.cjs +2 -4
  31. package/dist/batches.cjs.map +1 -1
  32. package/dist/batches.d.cts.map +1 -1
  33. package/dist/batches.d.ts.map +1 -1
  34. package/dist/batches.js +2 -4
  35. package/dist/batches.js.map +1 -1
  36. package/dist/bedrock-converse.cjs +139 -161
  37. package/dist/bedrock-converse.cjs.map +1 -1
  38. package/dist/bedrock-converse.d.cts.map +1 -1
  39. package/dist/bedrock-converse.d.ts.map +1 -1
  40. package/dist/bedrock-converse.js +137 -159
  41. package/dist/bedrock-converse.js.map +1 -1
  42. package/dist/bedrock.cjs +175 -216
  43. package/dist/bedrock.cjs.map +1 -1
  44. package/dist/bedrock.d.cts.map +1 -1
  45. package/dist/bedrock.d.ts.map +1 -1
  46. package/dist/bedrock.js +172 -213
  47. package/dist/bedrock.js.map +1 -1
  48. package/dist/byteplus-video.cjs +11 -10
  49. package/dist/byteplus-video.cjs.map +1 -1
  50. package/dist/byteplus-video.d.cts.map +1 -1
  51. package/dist/byteplus-video.d.ts.map +1 -1
  52. package/dist/byteplus-video.js +5 -4
  53. package/dist/byteplus-video.js.map +1 -1
  54. package/dist/chaos.cjs +5 -4
  55. package/dist/chaos.cjs.map +1 -1
  56. package/dist/chaos.d.cts.map +1 -1
  57. package/dist/chaos.d.ts.map +1 -1
  58. package/dist/chaos.js +5 -4
  59. package/dist/chaos.js.map +1 -1
  60. package/dist/cli.cjs +15 -0
  61. package/dist/cli.cjs.map +1 -1
  62. package/dist/cli.js +15 -0
  63. package/dist/cli.js.map +1 -1
  64. package/dist/cohere.cjs +107 -180
  65. package/dist/cohere.cjs.map +1 -1
  66. package/dist/cohere.d.cts.map +1 -1
  67. package/dist/cohere.d.ts.map +1 -1
  68. package/dist/cohere.js +106 -179
  69. package/dist/cohere.js.map +1 -1
  70. package/dist/config-loader.cjs +29 -1
  71. package/dist/config-loader.cjs.map +1 -1
  72. package/dist/config-loader.d.cts +7 -1
  73. package/dist/config-loader.d.cts.map +1 -1
  74. package/dist/config-loader.d.ts +7 -1
  75. package/dist/config-loader.d.ts.map +1 -1
  76. package/dist/config-loader.js +29 -2
  77. package/dist/config-loader.js.map +1 -1
  78. package/dist/count-tokens.cjs +146 -0
  79. package/dist/count-tokens.cjs.map +1 -0
  80. package/dist/count-tokens.js +146 -0
  81. package/dist/count-tokens.js.map +1 -0
  82. package/dist/elevenlabs-audio.cjs +36 -34
  83. package/dist/elevenlabs-audio.cjs.map +1 -1
  84. package/dist/elevenlabs-audio.d.cts.map +1 -1
  85. package/dist/elevenlabs-audio.d.ts.map +1 -1
  86. package/dist/elevenlabs-audio.js +33 -31
  87. package/dist/elevenlabs-audio.js.map +1 -1
  88. package/dist/elevenlabs-voice.cjs +38 -37
  89. package/dist/elevenlabs-voice.cjs.map +1 -1
  90. package/dist/elevenlabs-voice.d.cts.map +1 -1
  91. package/dist/elevenlabs-voice.d.ts.map +1 -1
  92. package/dist/elevenlabs-voice.js +33 -32
  93. package/dist/elevenlabs-voice.js.map +1 -1
  94. package/dist/embedding-request.cjs +16 -0
  95. package/dist/embedding-request.cjs.map +1 -0
  96. package/dist/embedding-request.js +14 -0
  97. package/dist/embedding-request.js.map +1 -0
  98. package/dist/embeddings.cjs +30 -20
  99. package/dist/embeddings.cjs.map +1 -1
  100. package/dist/embeddings.d.cts.map +1 -1
  101. package/dist/embeddings.d.ts.map +1 -1
  102. package/dist/embeddings.js +30 -20
  103. package/dist/embeddings.js.map +1 -1
  104. package/dist/fal-audio.cjs +45 -43
  105. package/dist/fal-audio.cjs.map +1 -1
  106. package/dist/fal-audio.d.cts.map +1 -1
  107. package/dist/fal-audio.d.ts.map +1 -1
  108. package/dist/fal-audio.js +42 -40
  109. package/dist/fal-audio.js.map +1 -1
  110. package/dist/fal.cjs +17 -15
  111. package/dist/fal.cjs.map +1 -1
  112. package/dist/fal.js +16 -14
  113. package/dist/fal.js.map +1 -1
  114. package/dist/files.cjs +2 -4
  115. package/dist/files.cjs.map +1 -1
  116. package/dist/files.d.cts.map +1 -1
  117. package/dist/files.d.ts.map +1 -1
  118. package/dist/files.js +2 -4
  119. package/dist/files.js.map +1 -1
  120. package/dist/fine-tuning.cjs +2 -4
  121. package/dist/fine-tuning.cjs.map +1 -1
  122. package/dist/fine-tuning.d.cts.map +1 -1
  123. package/dist/fine-tuning.d.ts.map +1 -1
  124. package/dist/fine-tuning.js +2 -4
  125. package/dist/fine-tuning.js.map +1 -1
  126. package/dist/fixture-loader.cjs +140 -44
  127. package/dist/fixture-loader.cjs.map +1 -1
  128. package/dist/fixture-loader.d.cts.map +1 -1
  129. package/dist/fixture-loader.d.ts.map +1 -1
  130. package/dist/fixture-loader.js +141 -46
  131. package/dist/fixture-loader.js.map +1 -1
  132. package/dist/gemini-embeddings.cjs +26 -24
  133. package/dist/gemini-embeddings.cjs.map +1 -1
  134. package/dist/gemini-embeddings.js +26 -24
  135. package/dist/gemini-embeddings.js.map +1 -1
  136. package/dist/gemini-interactions.cjs +113 -156
  137. package/dist/gemini-interactions.cjs.map +1 -1
  138. package/dist/gemini-interactions.d.cts +0 -1
  139. package/dist/gemini-interactions.d.cts.map +1 -1
  140. package/dist/gemini-interactions.d.ts +0 -1
  141. package/dist/gemini-interactions.d.ts.map +1 -1
  142. package/dist/gemini-interactions.js +113 -156
  143. package/dist/gemini-interactions.js.map +1 -1
  144. package/dist/gemini.cjs +295 -218
  145. package/dist/gemini.cjs.map +1 -1
  146. package/dist/gemini.d.cts.map +1 -1
  147. package/dist/gemini.d.ts.map +1 -1
  148. package/dist/gemini.js +295 -219
  149. package/dist/gemini.js.map +1 -1
  150. package/dist/grok-video.cjs +14 -12
  151. package/dist/grok-video.cjs.map +1 -1
  152. package/dist/grok-video.d.cts.map +1 -1
  153. package/dist/grok-video.d.ts.map +1 -1
  154. package/dist/grok-video.js +8 -6
  155. package/dist/grok-video.js.map +1 -1
  156. package/dist/helpers.cjs +411 -75
  157. package/dist/helpers.cjs.map +1 -1
  158. package/dist/helpers.d.cts +51 -1
  159. package/dist/helpers.d.cts.map +1 -1
  160. package/dist/helpers.d.ts +51 -1
  161. package/dist/helpers.d.ts.map +1 -1
  162. package/dist/helpers.js +402 -74
  163. package/dist/helpers.js.map +1 -1
  164. package/dist/images.cjs +30 -28
  165. package/dist/images.cjs.map +1 -1
  166. package/dist/images.d.cts.map +1 -1
  167. package/dist/images.d.ts.map +1 -1
  168. package/dist/images.js +27 -25
  169. package/dist/images.js.map +1 -1
  170. package/dist/index.cjs +4 -2
  171. package/dist/index.d.cts +4 -4
  172. package/dist/index.d.ts +4 -4
  173. package/dist/index.js +4 -4
  174. package/dist/journal.cjs +3 -1
  175. package/dist/journal.cjs.map +1 -1
  176. package/dist/journal.d.cts.map +1 -1
  177. package/dist/journal.d.ts.map +1 -1
  178. package/dist/journal.js +2 -1
  179. package/dist/journal.js.map +1 -1
  180. package/dist/llmock.cjs +3 -0
  181. package/dist/llmock.cjs.map +1 -1
  182. package/dist/llmock.d.cts.map +1 -1
  183. package/dist/llmock.d.ts.map +1 -1
  184. package/dist/llmock.js +3 -0
  185. package/dist/llmock.js.map +1 -1
  186. package/dist/mcp-fakes.d.cts.map +1 -1
  187. package/dist/mcp-handler.cjs +7 -3
  188. package/dist/mcp-handler.cjs.map +1 -1
  189. package/dist/mcp-handler.js +7 -3
  190. package/dist/mcp-handler.js.map +1 -1
  191. package/dist/mcp-recorder.cjs +2 -1
  192. package/dist/mcp-recorder.cjs.map +1 -1
  193. package/dist/mcp-recorder.js +3 -2
  194. package/dist/mcp-recorder.js.map +1 -1
  195. package/dist/messages.cjs +235 -202
  196. package/dist/messages.cjs.map +1 -1
  197. package/dist/messages.d.cts.map +1 -1
  198. package/dist/messages.d.ts.map +1 -1
  199. package/dist/messages.js +234 -202
  200. package/dist/messages.js.map +1 -1
  201. package/dist/metrics.cjs +5 -2
  202. package/dist/metrics.cjs.map +1 -1
  203. package/dist/metrics.d.cts.map +1 -1
  204. package/dist/metrics.d.ts.map +1 -1
  205. package/dist/metrics.js +6 -3
  206. package/dist/metrics.js.map +1 -1
  207. package/dist/misbehavior.cjs +62 -3
  208. package/dist/misbehavior.cjs.map +1 -1
  209. package/dist/misbehavior.d.cts.map +1 -1
  210. package/dist/misbehavior.d.ts.map +1 -1
  211. package/dist/misbehavior.js +63 -4
  212. package/dist/misbehavior.js.map +1 -1
  213. package/dist/model-utils.cjs +1 -0
  214. package/dist/model-utils.cjs.map +1 -1
  215. package/dist/model-utils.js +1 -1
  216. package/dist/model-utils.js.map +1 -1
  217. package/dist/moderation.cjs +15 -12
  218. package/dist/moderation.cjs.map +1 -1
  219. package/dist/moderation.js +14 -11
  220. package/dist/moderation.js.map +1 -1
  221. package/dist/ndjson-writer.cjs +21 -23
  222. package/dist/ndjson-writer.cjs.map +1 -1
  223. package/dist/ndjson-writer.d.cts +0 -1
  224. package/dist/ndjson-writer.d.cts.map +1 -1
  225. package/dist/ndjson-writer.d.ts +0 -1
  226. package/dist/ndjson-writer.d.ts.map +1 -1
  227. package/dist/ndjson-writer.js +21 -24
  228. package/dist/ndjson-writer.js.map +1 -1
  229. package/dist/ollama.cjs +276 -210
  230. package/dist/ollama.cjs.map +1 -1
  231. package/dist/ollama.d.cts.map +1 -1
  232. package/dist/ollama.d.ts.map +1 -1
  233. package/dist/ollama.js +273 -207
  234. package/dist/ollama.js.map +1 -1
  235. package/dist/openapi.cjs +323 -3
  236. package/dist/openapi.cjs.map +1 -1
  237. package/dist/openapi.d.cts.map +1 -1
  238. package/dist/openapi.d.ts.map +1 -1
  239. package/dist/openapi.js +324 -4
  240. package/dist/openapi.js.map +1 -1
  241. package/dist/openrouter-chat.cjs +11 -155
  242. package/dist/openrouter-chat.cjs.map +1 -1
  243. package/dist/openrouter-chat.js +11 -155
  244. package/dist/openrouter-chat.js.map +1 -1
  245. package/dist/openrouter-video.cjs +51 -74
  246. package/dist/openrouter-video.cjs.map +1 -1
  247. package/dist/openrouter-video.d.cts.map +1 -1
  248. package/dist/openrouter-video.d.ts.map +1 -1
  249. package/dist/openrouter-video.js +37 -60
  250. package/dist/openrouter-video.js.map +1 -1
  251. package/dist/raw-validation.cjs +177 -0
  252. package/dist/raw-validation.cjs.map +1 -0
  253. package/dist/raw-validation.js +176 -0
  254. package/dist/raw-validation.js.map +1 -0
  255. package/dist/recorder.cjs +176 -95
  256. package/dist/recorder.cjs.map +1 -1
  257. package/dist/recorder.d.cts.map +1 -1
  258. package/dist/recorder.d.ts.map +1 -1
  259. package/dist/recorder.js +176 -95
  260. package/dist/recorder.js.map +1 -1
  261. package/dist/rerank.cjs +13 -11
  262. package/dist/rerank.cjs.map +1 -1
  263. package/dist/rerank.js +12 -10
  264. package/dist/rerank.js.map +1 -1
  265. package/dist/response/catalog/anthropic-page.cjs +39 -0
  266. package/dist/response/catalog/anthropic-page.cjs.map +1 -0
  267. package/dist/response/catalog/anthropic-page.js +38 -0
  268. package/dist/response/catalog/anthropic-page.js.map +1 -0
  269. package/dist/response/catalog/collect.cjs +61 -0
  270. package/dist/response/catalog/collect.cjs.map +1 -0
  271. package/dist/response/catalog/collect.js +59 -0
  272. package/dist/response/catalog/collect.js.map +1 -0
  273. package/dist/response/catalog/gemini-page.cjs +25 -0
  274. package/dist/response/catalog/gemini-page.cjs.map +1 -0
  275. package/dist/response/catalog/gemini-page.js +24 -0
  276. package/dist/response/catalog/gemini-page.js.map +1 -0
  277. package/dist/response/catalog/render.cjs +182 -0
  278. package/dist/response/catalog/render.cjs.map +1 -0
  279. package/dist/response/catalog/render.js +171 -0
  280. package/dist/response/catalog/render.js.map +1 -0
  281. package/dist/response/deliver.cjs +144 -0
  282. package/dist/response/deliver.cjs.map +1 -0
  283. package/dist/response/deliver.js +143 -0
  284. package/dist/response/deliver.js.map +1 -0
  285. package/dist/response/errors.cjs +225 -0
  286. package/dist/response/errors.cjs.map +1 -0
  287. package/dist/response/errors.js +224 -0
  288. package/dist/response/errors.js.map +1 -0
  289. package/dist/response/finish.cjs +39 -0
  290. package/dist/response/finish.cjs.map +1 -0
  291. package/dist/response/finish.js +35 -0
  292. package/dist/response/finish.js.map +1 -0
  293. package/dist/response/frame-loop.cjs +102 -0
  294. package/dist/response/frame-loop.cjs.map +1 -0
  295. package/dist/response/frame-loop.js +100 -0
  296. package/dist/response/frame-loop.js.map +1 -0
  297. package/dist/response/lifecycle.cjs +223 -0
  298. package/dist/response/lifecycle.cjs.map +1 -0
  299. package/dist/response/lifecycle.js +211 -0
  300. package/dist/response/lifecycle.js.map +1 -0
  301. package/dist/response/policy.cjs +126 -0
  302. package/dist/response/policy.cjs.map +1 -0
  303. package/dist/response/policy.js +120 -0
  304. package/dist/response/policy.js.map +1 -0
  305. package/dist/response/profiles.cjs +69 -0
  306. package/dist/response/profiles.cjs.map +1 -0
  307. package/dist/response/profiles.js +65 -0
  308. package/dist/response/profiles.js.map +1 -0
  309. package/dist/response/proxy.cjs +71 -0
  310. package/dist/response/proxy.cjs.map +1 -0
  311. package/dist/response/proxy.js +70 -0
  312. package/dist/response/proxy.js.map +1 -0
  313. package/dist/response/raw.cjs +228 -0
  314. package/dist/response/raw.cjs.map +1 -0
  315. package/dist/response/raw.js +227 -0
  316. package/dist/response/raw.js.map +1 -0
  317. package/dist/response/timing.cjs +157 -0
  318. package/dist/response/timing.cjs.map +1 -0
  319. package/dist/response/timing.js +150 -0
  320. package/dist/response/timing.js.map +1 -0
  321. package/dist/response/usage/codecs/anthropic.cjs +52 -0
  322. package/dist/response/usage/codecs/anthropic.cjs.map +1 -0
  323. package/dist/response/usage/codecs/anthropic.js +50 -0
  324. package/dist/response/usage/codecs/anthropic.js.map +1 -0
  325. package/dist/response/usage/codecs/cohere.cjs +36 -0
  326. package/dist/response/usage/codecs/cohere.cjs.map +1 -0
  327. package/dist/response/usage/codecs/cohere.js +35 -0
  328. package/dist/response/usage/codecs/cohere.js.map +1 -0
  329. package/dist/response/usage/codecs/converse.cjs +16 -0
  330. package/dist/response/usage/codecs/converse.cjs.map +1 -0
  331. package/dist/response/usage/codecs/converse.js +15 -0
  332. package/dist/response/usage/codecs/converse.js.map +1 -0
  333. package/dist/response/usage/codecs/gemini.cjs +31 -0
  334. package/dist/response/usage/codecs/gemini.cjs.map +1 -0
  335. package/dist/response/usage/codecs/gemini.js +31 -0
  336. package/dist/response/usage/codecs/gemini.js.map +1 -0
  337. package/dist/response/usage/codecs/interactions.cjs +22 -0
  338. package/dist/response/usage/codecs/interactions.cjs.map +1 -0
  339. package/dist/response/usage/codecs/interactions.js +21 -0
  340. package/dist/response/usage/codecs/interactions.js.map +1 -0
  341. package/dist/response/usage/codecs/ollama.cjs +17 -0
  342. package/dist/response/usage/codecs/ollama.cjs.map +1 -0
  343. package/dist/response/usage/codecs/ollama.js +17 -0
  344. package/dist/response/usage/codecs/ollama.js.map +1 -0
  345. package/dist/response/usage/codecs/openai.cjs +52 -0
  346. package/dist/response/usage/codecs/openai.cjs.map +1 -0
  347. package/dist/response/usage/codecs/openai.js +50 -0
  348. package/dist/response/usage/codecs/openai.js.map +1 -0
  349. package/dist/response/usage/codecs/openrouter.cjs +68 -0
  350. package/dist/response/usage/codecs/openrouter.cjs.map +1 -0
  351. package/dist/response/usage/codecs/openrouter.js +67 -0
  352. package/dist/response/usage/codecs/openrouter.js.map +1 -0
  353. package/dist/response/usage/codecs/realtime.cjs +19 -0
  354. package/dist/response/usage/codecs/realtime.cjs.map +1 -0
  355. package/dist/response/usage/codecs/realtime.js +18 -0
  356. package/dist/response/usage/codecs/realtime.js.map +1 -0
  357. package/dist/response/usage/codecs/responses.cjs +21 -0
  358. package/dist/response/usage/codecs/responses.cjs.map +1 -0
  359. package/dist/response/usage/codecs/responses.js +20 -0
  360. package/dist/response/usage/codecs/responses.js.map +1 -0
  361. package/dist/response/usage/estimate.cjs +23 -0
  362. package/dist/response/usage/estimate.cjs.map +1 -0
  363. package/dist/response/usage/estimate.js +21 -0
  364. package/dist/response/usage/estimate.js.map +1 -0
  365. package/dist/response/usage/fields.cjs +72 -0
  366. package/dist/response/usage/fields.cjs.map +1 -0
  367. package/dist/response/usage/fields.js +70 -0
  368. package/dist/response/usage/fields.js.map +1 -0
  369. package/dist/response/usage/recording.cjs +52 -0
  370. package/dist/response/usage/recording.cjs.map +1 -0
  371. package/dist/response/usage/recording.js +52 -0
  372. package/dist/response/usage/recording.js.map +1 -0
  373. package/dist/responses.cjs +304 -196
  374. package/dist/responses.cjs.map +1 -1
  375. package/dist/responses.d.cts.map +1 -1
  376. package/dist/responses.d.ts.map +1 -1
  377. package/dist/responses.js +303 -195
  378. package/dist/responses.js.map +1 -1
  379. package/dist/route-registry.cjs +45 -0
  380. package/dist/route-registry.cjs.map +1 -1
  381. package/dist/route-registry.d.cts.map +1 -1
  382. package/dist/route-registry.d.ts.map +1 -1
  383. package/dist/route-registry.js +41 -1
  384. package/dist/route-registry.js.map +1 -1
  385. package/dist/router.cjs +24 -4
  386. package/dist/router.cjs.map +1 -1
  387. package/dist/router.d.cts +7 -1
  388. package/dist/router.d.cts.map +1 -1
  389. package/dist/router.d.ts +7 -1
  390. package/dist/router.d.ts.map +1 -1
  391. package/dist/router.js +24 -5
  392. package/dist/router.js.map +1 -1
  393. package/dist/search.cjs +13 -11
  394. package/dist/search.cjs.map +1 -1
  395. package/dist/search.js +12 -10
  396. package/dist/search.js.map +1 -1
  397. package/dist/server.cjs +293 -248
  398. package/dist/server.cjs.map +1 -1
  399. package/dist/server.d.cts.map +1 -1
  400. package/dist/server.d.ts.map +1 -1
  401. package/dist/server.js +294 -249
  402. package/dist/server.js.map +1 -1
  403. package/dist/speech.cjs +20 -18
  404. package/dist/speech.cjs.map +1 -1
  405. package/dist/speech.d.cts.map +1 -1
  406. package/dist/speech.d.ts.map +1 -1
  407. package/dist/speech.js +19 -17
  408. package/dist/speech.js.map +1 -1
  409. package/dist/sse-writer.cjs +56 -30
  410. package/dist/sse-writer.cjs.map +1 -1
  411. package/dist/sse-writer.d.cts +0 -1
  412. package/dist/sse-writer.d.cts.map +1 -1
  413. package/dist/sse-writer.d.ts +0 -1
  414. package/dist/sse-writer.d.ts.map +1 -1
  415. package/dist/sse-writer.js +55 -31
  416. package/dist/sse-writer.js.map +1 -1
  417. package/dist/stream-collapse.cjs +118 -11
  418. package/dist/stream-collapse.cjs.map +1 -1
  419. package/dist/stream-collapse.d.cts +10 -9
  420. package/dist/stream-collapse.d.cts.map +1 -1
  421. package/dist/stream-collapse.d.ts +10 -9
  422. package/dist/stream-collapse.d.ts.map +1 -1
  423. package/dist/stream-collapse.js +118 -11
  424. package/dist/stream-collapse.js.map +1 -1
  425. package/dist/transcription.cjs +31 -43
  426. package/dist/transcription.cjs.map +1 -1
  427. package/dist/transcription.d.cts.map +1 -1
  428. package/dist/transcription.d.ts.map +1 -1
  429. package/dist/transcription.js +31 -43
  430. package/dist/transcription.js.map +1 -1
  431. package/dist/types.cjs.map +1 -1
  432. package/dist/types.d.cts +231 -9
  433. package/dist/types.d.cts.map +1 -1
  434. package/dist/types.d.ts +231 -9
  435. package/dist/types.d.ts.map +1 -1
  436. package/dist/types.js.map +1 -1
  437. package/dist/vector-mock.cjs +3 -8
  438. package/dist/vector-mock.cjs.map +1 -1
  439. package/dist/vector-mock.d.cts.map +1 -1
  440. package/dist/vector-mock.d.ts.map +1 -1
  441. package/dist/vector-mock.js +3 -8
  442. package/dist/vector-mock.js.map +1 -1
  443. package/dist/vector-stores.cjs +2 -4
  444. package/dist/vector-stores.cjs.map +1 -1
  445. package/dist/vector-stores.d.cts.map +1 -1
  446. package/dist/vector-stores.d.ts.map +1 -1
  447. package/dist/vector-stores.js +2 -4
  448. package/dist/vector-stores.js.map +1 -1
  449. package/dist/veo-video.cjs +16 -14
  450. package/dist/veo-video.cjs.map +1 -1
  451. package/dist/veo-video.js +10 -8
  452. package/dist/veo-video.js.map +1 -1
  453. package/dist/video.cjs +21 -19
  454. package/dist/video.cjs.map +1 -1
  455. package/dist/video.d.cts.map +1 -1
  456. package/dist/video.d.ts.map +1 -1
  457. package/dist/video.js +20 -18
  458. package/dist/video.js.map +1 -1
  459. package/dist/ws-framing.cjs +12 -6
  460. package/dist/ws-framing.cjs.map +1 -1
  461. package/dist/ws-framing.d.cts +2 -0
  462. package/dist/ws-framing.d.cts.map +1 -1
  463. package/dist/ws-framing.d.ts +2 -0
  464. package/dist/ws-framing.d.ts.map +1 -1
  465. package/dist/ws-framing.js +12 -6
  466. package/dist/ws-framing.js.map +1 -1
  467. package/dist/ws-gemini-live.cjs +131 -96
  468. package/dist/ws-gemini-live.cjs.map +1 -1
  469. package/dist/ws-gemini-live.d.cts +0 -1
  470. package/dist/ws-gemini-live.d.cts.map +1 -1
  471. package/dist/ws-gemini-live.d.ts +0 -1
  472. package/dist/ws-gemini-live.d.ts.map +1 -1
  473. package/dist/ws-gemini-live.js +130 -95
  474. package/dist/ws-gemini-live.js.map +1 -1
  475. package/dist/ws-live.cjs +6 -5
  476. package/dist/ws-live.cjs.map +1 -1
  477. package/dist/ws-live.js +5 -4
  478. package/dist/ws-live.js.map +1 -1
  479. package/dist/ws-realtime.cjs +108 -124
  480. package/dist/ws-realtime.cjs.map +1 -1
  481. package/dist/ws-realtime.d.cts.map +1 -1
  482. package/dist/ws-realtime.d.ts.map +1 -1
  483. package/dist/ws-realtime.js +105 -121
  484. package/dist/ws-realtime.js.map +1 -1
  485. package/dist/ws-responses.cjs +80 -59
  486. package/dist/ws-responses.cjs.map +1 -1
  487. package/dist/ws-responses.d.cts.map +1 -1
  488. package/dist/ws-responses.d.ts.map +1 -1
  489. package/dist/ws-responses.js +79 -58
  490. package/dist/ws-responses.js.map +1 -1
  491. package/package.json +4 -1
  492. package/skills/write-fixtures/SKILL.md +57 -16
@@ -1,12 +1,18 @@
1
+ import { httpSink, runFrameLoop } from "./response/frame-loop.js";
2
+
1
3
  //#region src/sse-writer.ts
2
4
  function delay(ms, signal) {
3
5
  if (ms <= 0 || signal?.aborted) return Promise.resolve();
4
6
  return new Promise((resolve) => {
5
- const timer = setTimeout(resolve, ms);
6
- signal?.addEventListener("abort", () => {
7
+ const onAbort = () => {
7
8
  clearTimeout(timer);
8
9
  resolve();
9
- }, { once: true });
10
+ };
11
+ const timer = setTimeout(() => {
12
+ signal?.removeEventListener("abort", onAbort);
13
+ resolve();
14
+ }, ms);
15
+ signal?.addEventListener("abort", onAbort, { once: true });
10
16
  });
11
17
  }
12
18
  /** OpenRouter's SSE keepalive comment line (colon-prefixed, per SSE syntax). */
@@ -39,35 +45,53 @@ function calculateDelay(chunkIndex, profile, fallbackLatency, recordedTimings, r
39
45
  delayMs = Math.max(0, delayMs);
40
46
  return speed > 0 ? delayMs / speed : delayMs;
41
47
  }
48
+ function calculateTotalDelay(contentLength, chunkSize, profile, recordedTimings, replaySpeed) {
49
+ const chunkCount = Math.max(1, Math.ceil(contentLength / chunkSize));
50
+ let total = 0;
51
+ for (let chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++) total += calculateDelay(chunkIndex, profile, void 0, recordedTimings, replaySpeed);
52
+ return total;
53
+ }
42
54
  async function writeSSEStream(res, chunks, optionsOrLatency) {
43
55
  const opts = typeof optionsOrLatency === "number" ? { latency: optionsOrLatency } : optionsOrLatency ?? {};
44
- const latency = opts.latency ?? 0;
45
- const profile = opts.streamingProfile;
46
- const { recordedTimings, replaySpeed } = opts;
47
- const signal = opts.signal;
48
- const onChunkSent = opts.onChunkSent;
49
- if (res.writableEnded) return true;
50
- res.setHeader("Content-Type", "text/event-stream");
51
- res.setHeader("Cache-Control", "no-cache");
52
- res.setHeader("Connection", "keep-alive");
53
- if (opts.openRouterProcessing && !res.writableEnded) res.write(OPENROUTER_PROCESSING_COMMENT);
54
- let chunkIndex = 0;
55
- for (const chunk of chunks) {
56
- const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
57
- if (chunkDelay > 0) await delay(chunkDelay, signal);
58
- if (signal?.aborted) return false;
59
- if (res.writableEnded) return true;
60
- res.write(`data: ${JSON.stringify(chunk)}\n\n`);
61
- onChunkSent?.();
62
- if (signal?.aborted) return false;
63
- chunkIndex++;
64
- }
65
- if (!res.writableEnded) {
66
- if (opts.usageChunk) res.write(`data: ${JSON.stringify(opts.usageChunk)}\n\n`);
67
- res.write("data: [DONE]\n\n");
68
- res.end();
69
- }
70
- return true;
56
+ return runFrameLoop(httpSink(res), chunks, sseCodec({
57
+ done: true,
58
+ usageChunk: opts.usageChunk,
59
+ openRouterProcessing: opts.openRouterProcessing
60
+ }), {
61
+ latency: opts.latency,
62
+ streamingProfile: opts.streamingProfile,
63
+ recordedTimings: opts.recordedTimings,
64
+ replaySpeed: opts.replaySpeed
65
+ }, {
66
+ signal: chunks.length > 0 ? opts.signal : void 0,
67
+ onChunkSent: opts.onChunkSent
68
+ });
69
+ }
70
+ const SSE_HEADERS = {
71
+ "Content-Type": "text/event-stream",
72
+ "Cache-Control": "no-cache",
73
+ Connection: "keep-alive"
74
+ };
75
+ function sseCodec(opts) {
76
+ return {
77
+ headers: SSE_HEADERS,
78
+ ...opts.openRouterProcessing ? { prelude: () => OPENROUTER_PROCESSING_COMMENT } : {},
79
+ encode: (frame) => `data: ${JSON.stringify(frame)}\n\n`,
80
+ trailer: () => {
81
+ if (!opts.done) return [];
82
+ const parts = [];
83
+ if (opts.usageChunk) parts.push(`data: ${JSON.stringify(opts.usageChunk)}\n\n`);
84
+ parts.push("data: [DONE]\n\n");
85
+ return parts;
86
+ }
87
+ };
88
+ }
89
+ function typedSseCodec(opts) {
90
+ return {
91
+ headers: SSE_HEADERS,
92
+ encode: (frame) => `event: ${frame.type}\ndata: ${JSON.stringify(frame)}\n\n`,
93
+ trailer: () => opts?.terminalSentinel ? ["data: [DONE]\n\n"] : []
94
+ };
71
95
  }
72
96
  /**
73
97
  * Default rate-limit response headers matching OpenAI's format.
@@ -92,5 +116,5 @@ function writeErrorResponse(res, status, body, options) {
92
116
  }
93
117
 
94
118
  //#endregion
95
- export { calculateDelay, delay, writeErrorResponse, writeSSEStream };
119
+ export { calculateDelay, calculateTotalDelay, delay, sseCodec, typedSseCodec, writeErrorResponse, writeSSEStream };
96
120
  //# sourceMappingURL=sse-writer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sse-writer.js","names":[],"sources":["../src/sse-writer.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type { SSEChunk, StreamingProfile, RecordedTimings } from \"./types.js\";\n\nexport function delay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0 || signal?.aborted) return Promise.resolve();\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true },\n );\n });\n}\n\nexport interface StreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n /** When set, emitted as the final chunk before [DONE] (OpenAI stream_options.include_usage). */\n usageChunk?: SSEChunk;\n /**\n * When true, a single OpenRouter `: OPENROUTER PROCESSING` SSE comment\n * (keepalive) line is emitted FIRST — before the first data chunk — matching\n * real OpenRouter bytes (or-capture/chat-stream.sse). A faithful client skips\n * `:`-prefixed lines; opt-in, default off (so strict OpenAI parsers aren't\n * surprised). Used only by the OpenRouter chat surface.\n */\n openRouterProcessing?: boolean;\n}\n\n/** OpenRouter's SSE keepalive comment line (colon-prefixed, per SSE syntax). */\nconst OPENROUTER_PROCESSING_COMMENT = \": OPENROUTER PROCESSING\\n\\n\";\n\nexport function calculateDelay(\n chunkIndex: number,\n profile?: StreamingProfile,\n fallbackLatency?: number,\n recordedTimings?: RecordedTimings,\n replaySpeed?: number,\n): number {\n const speed = replaySpeed ?? 1.0;\n let delayMs: number;\n\n if (profile) {\n // StreamingProfile has highest precedence\n let fromProfile = true;\n if (chunkIndex === 0 && profile.ttft !== undefined) {\n delayMs = profile.ttft;\n } else if (profile.tps !== undefined && profile.tps > 0) {\n delayMs = 1000 / profile.tps;\n } else {\n delayMs = fallbackLatency ?? 0;\n fromProfile = false;\n }\n // Jitter only applies when the delay came from ttft/tps, not fallback\n if (fromProfile && profile.jitter && profile.jitter > 0) {\n delayMs *= 1 + (Math.random() * 2 - 1) * profile.jitter;\n if (delayMs < 0) delayMs = 0;\n }\n } else if (recordedTimings) {\n // Recorded timings (second precedence)\n if (chunkIndex === 0) {\n delayMs = recordedTimings.ttftMs;\n } else {\n const idx = chunkIndex - 1;\n if (idx < recordedTimings.interChunkDelaysMs.length) {\n delayMs = recordedTimings.interChunkDelaysMs[idx];\n } else {\n // Excess chunks: derive average from recorded inter-chunk delays\n const totalInterChunk = recordedTimings.interChunkDelaysMs.reduce((a, b) => a + b, 0);\n delayMs =\n recordedTimings.interChunkDelaysMs.length > 0\n ? totalInterChunk / recordedTimings.interChunkDelaysMs.length\n : 0;\n }\n }\n } else {\n delayMs = fallbackLatency ?? 0;\n }\n\n delayMs = Math.max(0, delayMs);\n return speed > 0 ? delayMs / speed : delayMs;\n}\n\nexport async function writeSSEStream(\n res: http.ServerResponse,\n chunks: SSEChunk[],\n optionsOrLatency?: number | StreamOptions,\n): Promise<boolean> {\n const opts: StreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const { recordedTimings, replaySpeed } = opts;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n // OpenRouter emits exactly ONE `: OPENROUTER PROCESSING` keepalive comment\n // first — before any data frame (opt-in; see or-capture/chat-stream.sse). A\n // faithful client discards `:`-prefixed lines before JSON-parsing a `data:`.\n if (opts.openRouterProcessing && !res.writableEnded) {\n res.write(OPENROUTER_PROCESSING_COMMENT);\n }\n\n let chunkIndex = 0;\n for (const chunk of chunks) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) {\n await delay(chunkDelay, signal);\n }\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n res.write(`data: ${JSON.stringify(chunk)}\\n\\n`);\n onChunkSent?.();\n if (signal?.aborted) return false;\n chunkIndex++;\n }\n\n if (!res.writableEnded) {\n if (opts.usageChunk) {\n res.write(`data: ${JSON.stringify(opts.usageChunk)}\\n\\n`);\n }\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n }\n return true;\n}\n\n/**\n * Default rate-limit response headers matching OpenAI's format.\n * Values are static — aimock doesn't track actual request counts.\n */\nconst RATE_LIMIT_HEADERS: Record<string, string> = {\n \"x-ratelimit-limit-requests\": \"60\",\n \"x-ratelimit-limit-tokens\": \"150000\",\n \"x-ratelimit-remaining-requests\": \"0\",\n \"x-ratelimit-remaining-tokens\": \"0\",\n \"x-ratelimit-reset-requests\": \"1s\",\n \"x-ratelimit-reset-tokens\": \"6m0s\",\n};\n\nexport interface ErrorResponseOptions {\n /** Override the Retry-After header value (seconds). Default: 1. Only applied on 429. */\n retryAfter?: number;\n}\n\nexport function writeErrorResponse(\n res: http.ServerResponse,\n status: number,\n body: string,\n options?: ErrorResponseOptions,\n): void {\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (status === 429) {\n headers[\"Retry-After\"] = String(options?.retryAfter ?? 1);\n Object.assign(headers, RATE_LIMIT_HEADERS);\n }\n res.writeHead(status, headers);\n res.end(body);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sse-writer.js","names":[],"sources":["../src/sse-writer.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type { SSEChunk, StreamingProfile, RecordedTimings } from \"./types.js\";\nimport { httpSink, runFrameLoop, type FrameCodec } from \"./response/frame-loop.js\";\n\nexport function delay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0 || signal?.aborted) return Promise.resolve();\n return new Promise((resolve) => {\n const onAbort = (): void => {\n clearTimeout(timer);\n resolve();\n };\n // Remove the abort listener when the timer fires, so a long stream sharing\n // one lifecycle signal does not keep one listener per delayed frame.\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nexport interface StreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n /** When set, emitted as the final chunk before [DONE] (OpenAI stream_options.include_usage). */\n usageChunk?: SSEChunk;\n /**\n * When true, a single OpenRouter `: OPENROUTER PROCESSING` SSE comment\n * (keepalive) line is emitted FIRST — before the first data chunk — matching\n * real OpenRouter bytes (or-capture/chat-stream.sse). A faithful client skips\n * `:`-prefixed lines; opt-in, default off (so strict OpenAI parsers aren't\n * surprised). Used only by the OpenRouter chat surface.\n */\n openRouterProcessing?: boolean;\n}\n\n/** OpenRouter's SSE keepalive comment line (colon-prefixed, per SSE syntax). */\nconst OPENROUTER_PROCESSING_COMMENT = \": OPENROUTER PROCESSING\\n\\n\";\n\nexport function calculateDelay(\n chunkIndex: number,\n profile?: StreamingProfile,\n fallbackLatency?: number,\n recordedTimings?: RecordedTimings,\n replaySpeed?: number,\n): number {\n const speed = replaySpeed ?? 1.0;\n let delayMs: number;\n\n if (profile) {\n // StreamingProfile has highest precedence\n let fromProfile = true;\n if (chunkIndex === 0 && profile.ttft !== undefined) {\n delayMs = profile.ttft;\n } else if (profile.tps !== undefined && profile.tps > 0) {\n delayMs = 1000 / profile.tps;\n } else {\n delayMs = fallbackLatency ?? 0;\n fromProfile = false;\n }\n // Jitter only applies when the delay came from ttft/tps, not fallback\n if (fromProfile && profile.jitter && profile.jitter > 0) {\n delayMs *= 1 + (Math.random() * 2 - 1) * profile.jitter;\n if (delayMs < 0) delayMs = 0;\n }\n } else if (recordedTimings) {\n // Recorded timings (second precedence)\n if (chunkIndex === 0) {\n delayMs = recordedTimings.ttftMs;\n } else {\n const idx = chunkIndex - 1;\n if (idx < recordedTimings.interChunkDelaysMs.length) {\n delayMs = recordedTimings.interChunkDelaysMs[idx];\n } else {\n // Excess chunks: derive average from recorded inter-chunk delays\n const totalInterChunk = recordedTimings.interChunkDelaysMs.reduce((a, b) => a + b, 0);\n delayMs =\n recordedTimings.interChunkDelaysMs.length > 0\n ? totalInterChunk / recordedTimings.interChunkDelaysMs.length\n : 0;\n }\n }\n } else {\n delayMs = fallbackLatency ?? 0;\n }\n\n delayMs = Math.max(0, delayMs);\n return speed > 0 ? delayMs / speed : delayMs;\n}\n\n// Internal (not part of the public API): the total wait before a non-streaming\n// (`stream: false`) JSON response is written. Sums calculateDelay over the\n// content chunk count that `contentLength` at `chunkSize` would produce, so a\n// fixture with an explicit `streamingProfile` or `recordedTimings` pays roughly\n// the same ttft/tps cost as its streamed content chunks. It is an\n// approximation, not an exact match: a streamed reply also pays delays for its\n// role and finish chunks. There is deliberately no fallback-latency parameter:\n// bare `latency` / `--latency` is an inter-SSE-chunk gap and never delays a\n// non-streaming response. With neither a profile nor recorded timings, this\n// returns 0.\n// Kept as a line comment, not JSDoc: the bundled .d.ts drops this non-public\n// declaration but keeps a JSDoc block, which would then attach to writeSSEStream.\nexport function calculateTotalDelay(\n contentLength: number,\n chunkSize: number,\n profile?: StreamingProfile,\n recordedTimings?: RecordedTimings,\n replaySpeed?: number,\n): number {\n const chunkCount = Math.max(1, Math.ceil(contentLength / chunkSize));\n let total = 0;\n for (let chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++) {\n total += calculateDelay(chunkIndex, profile, undefined, recordedTimings, replaySpeed);\n }\n return total;\n}\n\nexport async function writeSSEStream(\n res: http.ServerResponse,\n chunks: SSEChunk[],\n optionsOrLatency?: number | StreamOptions,\n): Promise<boolean> {\n const opts: StreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n // No lifecycle: an external caller gets close-awareness only through its own\n // `signal` (a gone client still returns true).\n // Base contract (public API): with no frames, an aborted signal does not\n // stop the writer; it still finishes the stream (any trailer, then end())\n // and returns true. The frame loop returns false there, so pass it no\n // signal when there are no frames (it reads the signal only per frame and\n // after the loop).\n return runFrameLoop(\n httpSink(res),\n chunks,\n sseCodec({\n done: true,\n usageChunk: opts.usageChunk,\n openRouterProcessing: opts.openRouterProcessing,\n }),\n {\n latency: opts.latency,\n streamingProfile: opts.streamingProfile,\n recordedTimings: opts.recordedTimings,\n replaySpeed: opts.replaySpeed,\n },\n { signal: chunks.length > 0 ? opts.signal : undefined, onChunkSent: opts.onChunkSent },\n );\n}\n\nconst SSE_HEADERS: Record<string, string> = {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n};\n\n// Internal (not part of the public API): data-only SSE (`data: <json>\\n\\n`).\n// `done: true` writes the optional usage chunk then `data: [DONE]` after the\n// last frame (OpenAI chat); `done: false` writes no trailer.\n// `openRouterProcessing` writes the OpenRouter keepalive comment first. Line\n// comments, not JSDoc: see calculateTotalDelay.\nexport function sseCodec<F = SSEChunk>(opts: {\n done: boolean;\n usageChunk?: SSEChunk;\n openRouterProcessing?: boolean;\n}): FrameCodec<F> {\n return {\n headers: SSE_HEADERS,\n ...(opts.openRouterProcessing ? { prelude: () => OPENROUTER_PROCESSING_COMMENT } : {}),\n encode: (frame) => `data: ${JSON.stringify(frame)}\\n\\n`,\n trailer: () => {\n if (!opts.done) return [];\n const parts: string[] = [];\n if (opts.usageChunk) parts.push(`data: ${JSON.stringify(opts.usageChunk)}\\n\\n`);\n parts.push(\"data: [DONE]\\n\\n\");\n return parts;\n },\n };\n}\n\n// Internal: typed SSE (`event: <type>\\ndata: <json>\\n\\n`), used by Anthropic.\n// `terminalSentinel` writes `data: [DONE]` after the last frame, only when the\n// stream was not aborted.\nexport function typedSseCodec<F extends { type: string }>(opts?: {\n terminalSentinel?: boolean;\n}): FrameCodec<F> {\n return {\n headers: SSE_HEADERS,\n encode: (frame) => `event: ${frame.type}\\ndata: ${JSON.stringify(frame)}\\n\\n`,\n trailer: () => (opts?.terminalSentinel ? [\"data: [DONE]\\n\\n\"] : []),\n };\n}\n\n/**\n * Default rate-limit response headers matching OpenAI's format.\n * Values are static — aimock doesn't track actual request counts.\n */\nconst RATE_LIMIT_HEADERS: Record<string, string> = {\n \"x-ratelimit-limit-requests\": \"60\",\n \"x-ratelimit-limit-tokens\": \"150000\",\n \"x-ratelimit-remaining-requests\": \"0\",\n \"x-ratelimit-remaining-tokens\": \"0\",\n \"x-ratelimit-reset-requests\": \"1s\",\n \"x-ratelimit-reset-tokens\": \"6m0s\",\n};\n\nexport interface ErrorResponseOptions {\n /** Override the Retry-After header value (seconds). Default: 1. Only applied on 429. */\n retryAfter?: number;\n}\n\nexport function writeErrorResponse(\n res: http.ServerResponse,\n status: number,\n body: string,\n options?: ErrorResponseOptions,\n): void {\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (status === 429) {\n headers[\"Retry-After\"] = String(options?.retryAfter ?? 1);\n Object.assign(headers, RATE_LIMIT_HEADERS);\n }\n res.writeHead(status, headers);\n res.end(body);\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
+ const require_recording = require('./response/usage/recording.cjs');
2
3
  const require_harmony = require('./harmony.cjs');
3
4
  let node_zlib = require("node:zlib");
4
5
 
@@ -361,7 +362,15 @@ function collapseOpenAISSEWithResponsesTools(rawBody) {
361
362
  transcript = parsed.text;
362
363
  transcriptSeen = true;
363
364
  if (Array.isArray(parsed.languages)) transcriptLanguages = parsed.languages.filter((language) => typeof language === "object" && language !== null && typeof language.code === "string").map((language) => ({ code: language.code }));
364
- if (parsed.usage && typeof parsed.usage === "object" && !Array.isArray(parsed.usage) && Object.keys(parsed.usage).length > 0) transcriptUsage = parsed.usage;
365
+ transcriptUsage = require_recording.captureUsage({
366
+ provider: "openai",
367
+ contentType: "text/event-stream",
368
+ operation: "transcription",
369
+ family: "transcription",
370
+ stage: "stream",
371
+ previous: transcriptUsage,
372
+ observation: parsed
373
+ });
365
374
  continue;
366
375
  }
367
376
  if (parsed.type === "response.reasoning_summary_text.delta" && typeof parsed.delta === "string") {
@@ -548,7 +557,15 @@ function collapseOpenAISSEWithResponsesTools(rawBody) {
548
557
  continue;
549
558
  }
550
559
  if (typeof parsed.type === "string" && parsed.type.startsWith("response.")) continue;
551
- if (parsed.usage && typeof parsed.usage === "object" && !Array.isArray(parsed.usage) && Object.keys(parsed.usage).length > 0) usage = parsed.usage;
560
+ usage = require_recording.captureUsage({
561
+ provider: "openai",
562
+ contentType: "text/event-stream",
563
+ operation: "chat",
564
+ family: "openai",
565
+ stage: "stream",
566
+ previous: usage,
567
+ observation: parsed
568
+ });
552
569
  const choices = parsed.choices;
553
570
  if (!choices || choices.length === 0) continue;
554
571
  const delta = choices[0].delta;
@@ -664,6 +681,8 @@ function collapseOpenAISSEWithResponsesTools(rawBody) {
664
681
  }
665
682
  /**
666
683
  * Collapse Anthropic Claude Messages SSE stream into a single response.
684
+ * Captures start usage and numeric delta updates; null keeps the start value.
685
+ * Cache TTL objects are captured from message_start, alongside flat counts.
667
686
  *
668
687
  * Format:
669
688
  * event: message_start\ndata: {...}\n\n
@@ -672,6 +691,7 @@ function collapseOpenAISSEWithResponsesTools(rawBody) {
672
691
  function collapseAnthropicSSE(rawBody) {
673
692
  const inputTruncated = isCollapseInputTruncated(rawBody);
674
693
  const blocks = splitSSEEvents(guardCollapseBody(rawBody));
694
+ let usage;
675
695
  let content = "";
676
696
  let reasoning = "";
677
697
  let reasoningSignature;
@@ -697,6 +717,15 @@ function collapseAnthropicSSE(rawBody) {
697
717
  if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(payload, 200)}`;
698
718
  continue;
699
719
  }
720
+ usage = require_recording.captureUsage({
721
+ provider: "anthropic",
722
+ contentType: "text/event-stream",
723
+ operation: "chat",
724
+ family: "anthropic",
725
+ stage: "stream",
726
+ previous: usage,
727
+ observation: parsed
728
+ });
700
729
  if (eventType === "content_block_start") {
701
730
  const contentBlock = parsed.content_block;
702
731
  const redactedData = capturedRedactedData(contentBlock);
@@ -754,6 +783,7 @@ function collapseAnthropicSSE(rawBody) {
754
783
  ...tc.id ? { id: tc.id } : {}
755
784
  }));
756
785
  return {
786
+ ...usage ? { usage } : {},
757
787
  ...orderedBlocks ? { blocks: orderedBlocks } : {},
758
788
  ...content ? { content } : {},
759
789
  toolCalls: orderedBlocks ? orderedToolCalls : indexSortedToolCalls,
@@ -766,6 +796,7 @@ function collapseAnthropicSSE(rawBody) {
766
796
  };
767
797
  }
768
798
  return {
799
+ ...usage ? { usage } : {},
769
800
  content,
770
801
  ...reasoning ? { reasoning } : {},
771
802
  ...reasoningSignature ? { reasoningSignature } : {},
@@ -784,6 +815,7 @@ function collapseAnthropicSSE(rawBody) {
784
815
  function collapseGeminiSSE(rawBody) {
785
816
  const inputTruncated = isCollapseInputTruncated(rawBody);
786
817
  const lines = splitSSEEvents(guardCollapseBody(rawBody));
818
+ let usage;
787
819
  let content = "";
788
820
  let reasoning = "";
789
821
  let droppedChunks = 0;
@@ -804,6 +836,15 @@ function collapseGeminiSSE(rawBody) {
804
836
  if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(payload, 200)}`;
805
837
  continue;
806
838
  }
839
+ usage = require_recording.captureUsage({
840
+ provider: "gemini",
841
+ contentType: "text/event-stream",
842
+ operation: "chat",
843
+ family: "gemini",
844
+ stage: "stream",
845
+ previous: usage,
846
+ observation: parsed
847
+ });
807
848
  const candidates = parsed.candidates;
808
849
  if (!candidates || candidates.length === 0) continue;
809
850
  const candidateContent = candidates[0].content;
@@ -839,6 +880,7 @@ function collapseGeminiSSE(rawBody) {
839
880
  arguments: normalizeToolArguments(tc.arguments)
840
881
  }));
841
882
  if (audioB64) return {
883
+ ...usage ? { usage } : {},
842
884
  audioB64,
843
885
  audioMimeType,
844
886
  ...content ? { content } : {},
@@ -851,6 +893,7 @@ function collapseGeminiSSE(rawBody) {
851
893
  if (toolCalls.length > 0) {
852
894
  const blocks = buildOrderedBlocks(orderAtoms);
853
895
  return {
896
+ ...usage ? { usage } : {},
854
897
  ...blocks ? { blocks } : {},
855
898
  ...content ? { content } : {},
856
899
  toolCalls: normalizedToolCalls,
@@ -861,6 +904,7 @@ function collapseGeminiSSE(rawBody) {
861
904
  };
862
905
  }
863
906
  return {
907
+ ...usage ? { usage } : {},
864
908
  content,
865
909
  ...reasoning ? { reasoning } : {},
866
910
  ...droppedChunks > 0 ? { droppedChunks } : {},
@@ -885,6 +929,7 @@ function collapseGeminiSSE(rawBody) {
885
929
  function collapseOllamaNDJSON(rawBody) {
886
930
  const inputTruncated = isCollapseInputTruncated(rawBody);
887
931
  const lines = guardCollapseBody(rawBody).split("\n").filter((l) => l.trim().length > 0);
932
+ let usage;
888
933
  let content = "";
889
934
  let reasoning = "";
890
935
  let droppedChunks = 0;
@@ -902,6 +947,15 @@ function collapseOllamaNDJSON(rawBody) {
902
947
  if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(line.trim(), 200)}`;
903
948
  continue;
904
949
  }
950
+ usage = require_recording.captureUsage({
951
+ provider: "ollama",
952
+ contentType: "application/x-ndjson",
953
+ operation: "chat",
954
+ family: "ollama",
955
+ stage: "stream",
956
+ previous: usage,
957
+ observation: parsed
958
+ });
905
959
  const message = parsed.message;
906
960
  if (message) {
907
961
  if (typeof message.content === "string") {
@@ -951,6 +1005,7 @@ function collapseOllamaNDJSON(rawBody) {
951
1005
  arguments: normalizeToolArguments(tc.arguments)
952
1006
  }));
953
1007
  return {
1008
+ ...usage ? { usage } : {},
954
1009
  ...blocks ? { blocks } : {},
955
1010
  ...content ? { content } : {},
956
1011
  toolCalls: normalizedToolCalls,
@@ -963,6 +1018,7 @@ function collapseOllamaNDJSON(rawBody) {
963
1018
  };
964
1019
  }
965
1020
  return {
1021
+ ...usage ? { usage } : {},
966
1022
  content,
967
1023
  ...reasoning ? { reasoning } : {},
968
1024
  ...droppedChunks > 0 ? { droppedChunks } : {},
@@ -981,6 +1037,7 @@ function collapseOllamaNDJSON(rawBody) {
981
1037
  function collapseCohereSSE(rawBody) {
982
1038
  const inputTruncated = isCollapseInputTruncated(rawBody);
983
1039
  const blocks = splitSSEEvents(guardCollapseBody(rawBody));
1040
+ let usage;
984
1041
  let content = "";
985
1042
  let reasoning = "";
986
1043
  let droppedChunks = 0;
@@ -1004,6 +1061,15 @@ function collapseCohereSSE(rawBody) {
1004
1061
  if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(payload, 200)}`;
1005
1062
  continue;
1006
1063
  }
1064
+ usage = require_recording.captureUsage({
1065
+ provider: "cohere",
1066
+ contentType: "text/event-stream",
1067
+ operation: "chat",
1068
+ family: "cohere",
1069
+ stage: "stream",
1070
+ previous: usage,
1071
+ observation: parsed
1072
+ });
1007
1073
  if (eventType === "content-delta") {
1008
1074
  const contentObj = (parsed.delta?.message)?.content;
1009
1075
  if (contentObj && contentObj.type === "thinking" && typeof contentObj.thinking === "string") reasoning += contentObj.thinking;
@@ -1064,6 +1130,7 @@ function collapseCohereSSE(rawBody) {
1064
1130
  ...tc.id ? { id: tc.id } : {}
1065
1131
  }));
1066
1132
  return {
1133
+ ...usage ? { usage } : {},
1067
1134
  ...orderedBlocks ? { blocks: orderedBlocks } : {},
1068
1135
  ...content ? { content } : {},
1069
1136
  toolCalls: orderedBlocks ? orderedToolCalls : indexSortedToolCalls,
@@ -1074,6 +1141,7 @@ function collapseCohereSSE(rawBody) {
1074
1141
  };
1075
1142
  }
1076
1143
  return {
1144
+ ...usage ? { usage } : {},
1077
1145
  content,
1078
1146
  ...reasoning ? { reasoning } : {},
1079
1147
  ...droppedChunks > 0 ? { droppedChunks } : {},
@@ -1168,6 +1236,8 @@ function decodeEventStreamFrames(buf) {
1168
1236
  }
1169
1237
  /**
1170
1238
  * Collapse Bedrock binary Event Stream into a single response.
1239
+ * Claude invoke events preserve usage with the Anthropic start/delta rules;
1240
+ * Converse usage remains omitted.
1171
1241
  *
1172
1242
  * Each frame contains a JSON payload with event types like:
1173
1243
  * contentBlockDelta, contentBlockStart, etc.
@@ -1177,6 +1247,7 @@ function collapseBedrockEventStream(rawBody) {
1177
1247
  const inputTruncated = rawBody.byteLength > limit;
1178
1248
  const { frames, truncated: frameTruncated } = decodeEventStreamFrames(inputTruncated ? rawBody.subarray(0, limit) : rawBody);
1179
1249
  const truncated = frameTruncated || inputTruncated;
1250
+ let usage;
1180
1251
  let content = "";
1181
1252
  let reasoning = "";
1182
1253
  let reasoningSignature;
@@ -1205,6 +1276,15 @@ function collapseBedrockEventStream(rawBody) {
1205
1276
  if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(frameStr, 200)}`;
1206
1277
  continue;
1207
1278
  }
1279
+ usage = require_recording.captureUsage({
1280
+ provider: "bedrock",
1281
+ contentType: "application/vnd.amazon.eventstream",
1282
+ operation: "chat",
1283
+ family: typeof parsed.type === "string" ? "bedrock-invoke" : "bedrock-converse",
1284
+ stage: "stream",
1285
+ previous: usage,
1286
+ observation: parsed
1287
+ });
1208
1288
  if (parsed.type === "content_block_delta") {
1209
1289
  const delta = parsed.delta;
1210
1290
  if (delta?.type === "text_delta" && typeof delta.text === "string") {
@@ -1312,6 +1392,7 @@ function collapseBedrockEventStream(rawBody) {
1312
1392
  ...tc.id ? { id: tc.id } : {}
1313
1393
  }));
1314
1394
  return {
1395
+ ...usage ? { usage } : {},
1315
1396
  ...orderedBlocks ? { blocks: orderedBlocks } : {},
1316
1397
  ...content ? { content } : {},
1317
1398
  toolCalls: orderedBlocks ? orderedToolCalls : indexSortedToolCalls,
@@ -1324,6 +1405,7 @@ function collapseBedrockEventStream(rawBody) {
1324
1405
  };
1325
1406
  }
1326
1407
  return {
1408
+ ...usage ? { usage } : {},
1327
1409
  content,
1328
1410
  ...reasoning ? { reasoning } : {},
1329
1411
  ...reasoningSignature ? { reasoningSignature } : {},
@@ -1350,6 +1432,7 @@ function collapseBedrockEventStream(rawBody) {
1350
1432
  function collapseGeminiInteractionsSSE(rawBody) {
1351
1433
  const inputTruncated = isCollapseInputTruncated(rawBody);
1352
1434
  const lines = splitSSEEvents(guardCollapseBody(rawBody));
1435
+ let usage;
1353
1436
  let content = "";
1354
1437
  let reasoning = "";
1355
1438
  let droppedChunks = 0;
@@ -1369,6 +1452,15 @@ function collapseGeminiInteractionsSSE(rawBody) {
1369
1452
  if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(payload, 200)}`;
1370
1453
  continue;
1371
1454
  }
1455
+ usage = require_recording.captureUsage({
1456
+ provider: "gemini-interactions",
1457
+ contentType: "text/event-stream",
1458
+ operation: "chat",
1459
+ family: "interactions",
1460
+ stage: "stream",
1461
+ previous: usage,
1462
+ observation: parsed
1463
+ });
1372
1464
  const eventType = parsed.event_type;
1373
1465
  if (!eventType) continue;
1374
1466
  const index = typeof parsed.index === "number" ? parsed.index : void 0;
@@ -1426,6 +1518,7 @@ function collapseGeminiInteractionsSSE(rawBody) {
1426
1518
  });
1427
1519
  }
1428
1520
  if (toolCalls.length > 0) return {
1521
+ ...usage ? { usage } : {},
1429
1522
  ...content ? { content } : {},
1430
1523
  toolCalls,
1431
1524
  ...reasoning ? { reasoning } : {},
@@ -1434,6 +1527,7 @@ function collapseGeminiInteractionsSSE(rawBody) {
1434
1527
  ...inputTruncated ? { truncated: true } : {}
1435
1528
  };
1436
1529
  return {
1530
+ ...usage ? { usage } : {},
1437
1531
  content,
1438
1532
  ...reasoning ? { reasoning } : {},
1439
1533
  ...droppedChunks > 0 ? { droppedChunks } : {},
@@ -1459,24 +1553,37 @@ function collapseStreamingResponse(contentType, providerKey, body, logger) {
1459
1553
  */
1460
1554
  function collapseStreamingResponseWithResponsesTools(contentType, providerKey, body, logger) {
1461
1555
  const ct = contentType.toLowerCase();
1462
- if (ct.includes("application/vnd.amazon.eventstream")) return collapseBedrockEventStream(typeof body === "string" ? Buffer.from(body, "binary") : body);
1463
- if (ct.includes("application/x-ndjson")) return collapseOllamaNDJSON(typeof body === "string" ? body : body.toString("utf8"));
1556
+ const complete = (result) => {
1557
+ const usage = require_recording.captureUsage({
1558
+ provider: providerKey,
1559
+ contentType,
1560
+ family: "stream-dispatch",
1561
+ stage: "collapsed",
1562
+ observation: result
1563
+ });
1564
+ return usage ? {
1565
+ ...result,
1566
+ usage
1567
+ } : result;
1568
+ };
1569
+ if (ct.includes("application/vnd.amazon.eventstream")) return complete(collapseBedrockEventStream(typeof body === "string" ? Buffer.from(body, "binary") : body));
1570
+ if (ct.includes("application/x-ndjson")) return complete(collapseOllamaNDJSON(typeof body === "string" ? body : body.toString("utf8")));
1464
1571
  if (ct.includes("text/event-stream")) {
1465
1572
  const str = typeof body === "string" ? body : body.toString("utf8");
1466
1573
  switch (providerKey) {
1467
1574
  case "openai":
1468
1575
  case "azure":
1469
1576
  case "openrouter":
1470
- case "byteplus": return collapseOpenAISSEWithResponsesTools(str);
1471
- case "anthropic": return collapseAnthropicSSE(str);
1577
+ case "byteplus": return complete(collapseOpenAISSEWithResponsesTools(str));
1578
+ case "anthropic": return complete(collapseAnthropicSSE(str));
1472
1579
  case "gemini":
1473
- case "vertexai": return collapseGeminiSSE(str);
1474
- case "gemini-interactions": return collapseGeminiInteractionsSSE(str);
1475
- case "cohere": return collapseCohereSSE(str);
1476
- case "bedrock": return collapseAnthropicSSE(str);
1580
+ case "vertexai": return complete(collapseGeminiSSE(str));
1581
+ case "gemini-interactions": return complete(collapseGeminiInteractionsSSE(str));
1582
+ case "cohere": return complete(collapseCohereSSE(str));
1583
+ case "bedrock": return complete(collapseAnthropicSSE(str));
1477
1584
  default:
1478
1585
  logger?.warn(`[stream-collapse] unknown SSE provider "${providerKey}", falling back to OpenAI SSE format`);
1479
- return collapseOpenAISSEWithResponsesTools(str);
1586
+ return complete(collapseOpenAISSEWithResponsesTools(str));
1480
1587
  }
1481
1588
  }
1482
1589
  return null;