@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,9 +1,12 @@
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');
4
6
  const require_router = require('./router.cjs');
5
7
  const require_recorder = require('./recorder.cjs');
6
- const require_interruption = require('./interruption.cjs');
8
+ const require_deliver = require('./response/deliver.cjs');
9
+ const require_proxy = require('./response/proxy.cjs');
7
10
 
8
11
  //#region src/transcription.ts
9
12
  /**
@@ -86,7 +89,7 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
86
89
  body: syntheticReq
87
90
  }, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
88
91
  if (!fixture) {
89
- if (require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
92
+ if (require_policy.resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
90
93
  const strictMessage = require_helpers.strictNoMatchMessage(skippedBySequenceOrTurn);
91
94
  defaults.logger.error(require_helpers.strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));
92
95
  journal.add({
@@ -97,18 +100,18 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
97
100
  response: {
98
101
  status: 503,
99
102
  fixture: null,
100
- ...require_helpers.strictOverrideField(defaults.strict, req.headers)
103
+ ...require_policy.strictOverrideField(defaults.strict, req.headers)
101
104
  }
102
105
  });
103
- require_sse_writer.writeErrorResponse(res, 503, JSON.stringify({ error: {
106
+ require_sse_writer.writeErrorResponse(res, 503, require_errors.renderError("openai", "strict-miss", {
104
107
  message: strictMessage,
105
108
  type: "invalid_request_error",
106
109
  code: "no_fixture_match"
107
- } }));
110
+ }).body);
108
111
  return;
109
112
  }
110
113
  if (defaults.record) {
111
- const outcome = await require_recorder.proxyAndRecord(req, res, syntheticReq, "openai", req.url ?? defaultPath, fixtures, defaults, raw);
114
+ const { outcome, clientHungUp } = await require_proxy.withHangUpDetection(req, res, () => require_recorder.proxyAndRecord(req, res, syntheticReq, "openai", req.url ?? defaultPath, fixtures, defaults, raw));
112
115
  if (outcome === "handled_by_hook") return;
113
116
  if (outcome !== "not_configured") {
114
117
  journal.add({
@@ -119,7 +122,8 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
119
122
  response: {
120
123
  status: res.statusCode ?? 200,
121
124
  fixture: null,
122
- source: "proxy"
125
+ source: "proxy",
126
+ ...require_proxy.proxyInterruption(res, clientHungUp)
123
127
  }
124
128
  });
125
129
  return;
@@ -133,14 +137,14 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
133
137
  response: {
134
138
  status: 404,
135
139
  fixture: null,
136
- ...require_helpers.strictOverrideField(defaults.strict, req.headers)
140
+ ...require_policy.strictOverrideField(defaults.strict, req.headers)
137
141
  }
138
142
  });
139
- require_sse_writer.writeErrorResponse(res, 404, JSON.stringify({ error: {
143
+ require_sse_writer.writeErrorResponse(res, 404, require_errors.renderError("openai", "fixture-miss", {
140
144
  message: "No fixture matched",
141
145
  type: "invalid_request_error",
142
146
  code: "no_fixture_match"
143
- } }));
147
+ }).body);
144
148
  return;
145
149
  }
146
150
  const response = await require_helpers.resolveResponse(fixture, syntheticReq);
@@ -156,7 +160,7 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
156
160
  fixture
157
161
  }
158
162
  });
159
- require_sse_writer.writeErrorResponse(res, status, require_helpers.serializeErrorResponse(response), { retryAfter: response.retryAfter });
163
+ require_sse_writer.writeErrorResponse(res, status, require_errors.renderError("openai", "fixture", response.error).body, { retryAfter: response.retryAfter });
160
164
  return;
161
165
  }
162
166
  if (!require_helpers.isTranscriptionResponse(response)) {
@@ -170,10 +174,10 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
170
174
  fixture
171
175
  }
172
176
  });
173
- require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
177
+ require_sse_writer.writeErrorResponse(res, 500, require_errors.renderError("openai", "unknown-response", {
174
178
  message: "Fixture response is not a transcription type",
175
179
  type: "server_error"
176
- } }));
180
+ }).body);
177
181
  return;
178
182
  }
179
183
  const journalEntry = journal.add({
@@ -194,38 +198,22 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
194
198
  ...t.languages !== void 0 ? { languages: t.languages } : {},
195
199
  ...t.usage !== void 0 ? { usage: t.usage } : {}
196
200
  };
197
- const latency = fixture.latency ?? defaults.latency;
198
201
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
199
- const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
200
- res.writeHead(200, {
201
- "Content-Type": "text/event-stream",
202
- "Cache-Control": "no-cache",
203
- Connection: "keep-alive"
202
+ const frames = [];
203
+ for (let index = 0; index < t.text.length; index += chunkSize) frames.push({
204
+ type: "transcript.text.delta",
205
+ delta: t.text.slice(index, index + chunkSize)
206
+ });
207
+ await require_deliver.deliverStream(res, journalEntry, fixture, {
208
+ ...require_sse_writer.sseCodec({ done: false }),
209
+ writeHeadImmediately: true,
210
+ trailer: () => [`data: ${JSON.stringify(done)}\n\n`, "data: [DONE]\n\n"]
211
+ }, frames, {
212
+ latency: fixture.latency ?? defaults.latency,
213
+ streamingProfile: fixture.streamingProfile,
214
+ recordedTimings: fixture.recordedTimings,
215
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
204
216
  });
205
- let chunkIndex = 0;
206
- const interruption = require_interruption.createInterruptionSignal(fixture);
207
- for (let index = 0; index < t.text.length; index += chunkSize) {
208
- const chunkDelay = require_sse_writer.calculateDelay(chunkIndex, fixture.streamingProfile, latency, fixture.recordedTimings, replaySpeed);
209
- if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, interruption?.signal);
210
- if (interruption?.signal.aborted) break;
211
- res.write(`data: ${JSON.stringify({
212
- type: "transcript.text.delta",
213
- delta: t.text.slice(index, index + chunkSize)
214
- })}\n\n`);
215
- interruption?.tick();
216
- chunkIndex++;
217
- }
218
- if (interruption?.signal.aborted) {
219
- journalEntry.response.interrupted = true;
220
- journalEntry.response.interruptReason = interruption.reason();
221
- interruption.cleanup();
222
- res.destroy();
223
- return;
224
- }
225
- res.write(`data: ${JSON.stringify(done)}\n\n`);
226
- res.write("data: [DONE]\n\n");
227
- res.end();
228
- interruption?.cleanup();
229
217
  return;
230
218
  }
231
219
  if (responseFormat === "verbose_json" || t.words != null || t.segments != null) {
@@ -1 +1 @@
1
- {"version":3,"file":"transcription.cjs","names":["getContext","getTestId","matchFixtureDiagnostic","applyChaosAsync","flattenHeaders","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","resolveResponse","isErrorResponse","serializeErrorResponse","isTranscriptionResponse","createInterruptionSignal","calculateDelay","delay"],"sources":["../src/transcription.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults } from \"./types.js\";\nimport {\n isTranscriptionResponse,\n isErrorResponse,\n serializeErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { calculateDelay, delay, writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaosAsync } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\n\n/**\n * Extract the multipart boundary string from a Content-Type header.\n *\n * RFC 2046 §5.1.1 defines the boundary parameter as either a bare token\n * (`boundary=abc123`) or a quoted-string (`boundary=\"abc123\"`), and its\n * `bchars` production INCLUDES a space — which is exactly the case the quoted\n * form exists for, and the one a capture that stops at whitespace can never\n * read. So the quotes are consumed BY the match rather than stripped after it:\n * a post-strip runs on an already-truncated value and leaves `boundary=\"a b c\"`\n * as the useless `\"a`, and the delimiter then never matches, so every form\n * field silently falls back to its default (`whisper-1`, `gpt-image-1`, …).\n *\n * Only DQUOTE quotes. An apostrophe is a legal RFC 2045 token character and a\n * legal RFC 2046 `bchar`, so `boundary='abc'` is a BARE token whose value\n * literally includes the quotes, and stripping them would break a working\n * request.\n *\n * The `(?:^|[;\\s])` prefix keeps a decoy parameter such as `myboundary=` from\n * matching, and `\\s*=\\s*` tolerates the LWSP some clients put around the `=`.\n */\nexport function extractBoundary(contentType: string | undefined): string | undefined {\n if (!contentType) return undefined;\n const match = contentType.match(/(?:^|[;\\s])boundary\\s*=\\s*(?:\"([^\"]*)\"|([^\\s;]+))/i);\n if (!match) return undefined;\n // Group 1 is the quoted form (possibly the empty string), group 2 the bare\n // token; an empty boundary is meaningless, so it reads as absent.\n const boundary = match[1] !== undefined ? match[1] : match[2];\n return boundary || undefined;\n}\n\n/**\n * Extract a text field from multipart form data using boundary-based parsing.\n * Splits the body by the multipart boundary so each part is isolated, then\n * checks each part's Content-Disposition header for the target field name.\n * This avoids false matches from binary audio data that might contain\n * header-like byte sequences.\n */\nexport function extractFormField(\n raw: string,\n fieldName: string,\n boundary: string | undefined,\n): string | undefined {\n if (!boundary) {\n // Fallback: no boundary available, use simple regex (best-effort)\n console.warn(\"extractFormField: no multipart boundary found, using best-effort regex fallback\");\n const escaped = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = new RegExp(\n `Content-Disposition:\\\\s*form-data;(?:[^\\\\r\\\\n]*;)?\\\\s*name=\"${escaped}\"[^\\\\r\\\\n]*\\\\r\\\\n\\\\r\\\\n([^\\\\r\\\\n]*)`,\n \"i\",\n );\n const match = raw.match(pattern);\n return match?.[1];\n }\n\n // Split by boundary delimiter — each chunk is one part\n const delimiter = `--${boundary}`;\n const parts = raw.split(delimiter);\n\n for (const part of parts) {\n // Skip the preamble (before first boundary) and epilogue (after closing boundary)\n if (!part || part.trimStart().startsWith(\"--\")) continue;\n\n // Split part into headers and body at the first blank line (\\r\\n\\r\\n)\n const headerEnd = part.indexOf(\"\\r\\n\\r\\n\");\n if (headerEnd === -1) continue;\n\n const headers = part.slice(0, headerEnd);\n const body = part.slice(headerEnd + 4);\n\n // Check if this part's Content-Disposition names the target field\n const cdMatch = headers.match(\n /Content-Disposition:\\s*form-data;(?:[^\\r\\n]*;)?\\s*name=\"([^\"]+)\"/i,\n );\n if (cdMatch && cdMatch[1] === fieldName) {\n // Return the body value, trimming trailing \\r\\n from the part boundary\n return body.replace(/\\r\\n$/, \"\");\n }\n }\n return undefined;\n}\n\nexport async function handleTranscription(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n endpointType: \"transcription\" | \"translation\" = \"transcription\",\n): Promise<void> {\n setCorsHeaders(res);\n const defaultPath =\n endpointType === \"translation\" ? \"/v1/audio/translations\" : \"/v1/audio/transcriptions\";\n const path = req.url ?? defaultPath;\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const boundary = extractBoundary(contentType);\n\n const model = extractFormField(raw, \"model\", boundary) ?? \"whisper-1\";\n const responseFormat = extractFormField(raw, \"response_format\", boundary) ?? \"json\";\n const stream = extractFormField(raw, \"stream\", boundary) === \"true\";\n\n const syntheticReq: ChatCompletionRequest = {\n model,\n messages: [],\n _endpointType: endpointType,\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? defaultPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isTranscriptionResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response is not a transcription type\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n const journalEntry = journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const t = response.transcription;\n\n // Only the transcription endpoint streams modern models. Whisper-1 ignores\n // `stream=true`, and translations continue to return their JSON response.\n if (endpointType === \"transcription\" && stream && model !== \"whisper-1\") {\n const done = {\n type: \"transcript.text.done\",\n text: t.text,\n ...(t.languages !== undefined ? { languages: t.languages } : {}),\n ...(t.usage !== undefined ? { usage: t.usage } : {}),\n };\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n let chunkIndex = 0;\n const interruption = createInterruptionSignal(fixture);\n for (let index = 0; index < t.text.length; index += chunkSize) {\n const chunkDelay = calculateDelay(\n chunkIndex,\n fixture.streamingProfile,\n latency,\n fixture.recordedTimings,\n replaySpeed,\n );\n if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);\n if (interruption?.signal.aborted) break;\n res.write(\n `data: ${JSON.stringify({ type: \"transcript.text.delta\", delta: t.text.slice(index, index + chunkSize) })}\\n\\n`,\n );\n interruption?.tick();\n chunkIndex++;\n }\n if (interruption?.signal.aborted) {\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption.reason();\n interruption.cleanup();\n res.destroy();\n return;\n }\n res.write(`data: ${JSON.stringify(done)}\\n\\n`);\n // The live `gpt-transcribe&stream=true` stream ends with the `[DONE]`\n // sentinel after `transcript.text.done`. Omitting it leaves clients that\n // loop until the sentinel waiting on a stream that never terminates.\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n interruption?.cleanup();\n return;\n }\n\n const useVerbose = responseFormat === \"verbose_json\" || t.words != null || t.segments != null;\n\n if (useVerbose) {\n const verboseBody: Record<string, unknown> = {\n task: endpointType === \"translation\" ? \"translate\" : \"transcribe\",\n language: t.language ?? \"english\",\n duration: t.duration ?? 0,\n text: t.text,\n };\n if (t.words && t.words.length > 0) {\n verboseBody.words = t.words;\n }\n if (t.segments && t.segments.length > 0) {\n verboseBody.segments = t.segments;\n }\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(verboseBody));\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n text: t.text,\n ...(t.languages !== undefined ? { languages: t.languages } : {}),\n ...(t.usage !== undefined ? { usage: t.usage } : {}),\n }),\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"transcription.cjs","names":["getContext","getTestId","matchFixtureDiagnostic","applyChaosAsync","flattenHeaders","resolveRequestDecisions","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","renderError","withHangUpDetection","proxyAndRecord","proxyInterruption","resolveResponse","isErrorResponse","isTranscriptionResponse","deliverStream","sseCodec"],"sources":["../src/transcription.ts"],"sourcesContent":["import { renderError } from \"./response/errors.js\";\nimport { resolveRequestDecisions, strictOverrideField } from \"./response/policy.js\";\nimport type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults } from \"./types.js\";\nimport {\n isTranscriptionResponse,\n isErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { sseCodec, writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaosAsync } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport { proxyInterruption, withHangUpDetection } from \"./response/proxy.js\";\nimport { deliverStream } from \"./response/deliver.js\";\nimport type { FrameCodec } from \"./response/frame-loop.js\";\n\n/** One `transcript.text.delta` frame of a streamed transcription. */\ninterface TranscriptDelta {\n type: \"transcript.text.delta\";\n delta: string;\n}\n\n/**\n * Extract the multipart boundary string from a Content-Type header.\n *\n * RFC 2046 §5.1.1 defines the boundary parameter as either a bare token\n * (`boundary=abc123`) or a quoted-string (`boundary=\"abc123\"`), and its\n * `bchars` production INCLUDES a space — which is exactly the case the quoted\n * form exists for, and the one a capture that stops at whitespace can never\n * read. So the quotes are consumed BY the match rather than stripped after it:\n * a post-strip runs on an already-truncated value and leaves `boundary=\"a b c\"`\n * as the useless `\"a`, and the delimiter then never matches, so every form\n * field silently falls back to its default (`whisper-1`, `gpt-image-1`, …).\n *\n * Only DQUOTE quotes. An apostrophe is a legal RFC 2045 token character and a\n * legal RFC 2046 `bchar`, so `boundary='abc'` is a BARE token whose value\n * literally includes the quotes, and stripping them would break a working\n * request.\n *\n * The `(?:^|[;\\s])` prefix keeps a decoy parameter such as `myboundary=` from\n * matching, and `\\s*=\\s*` tolerates the LWSP some clients put around the `=`.\n */\nexport function extractBoundary(contentType: string | undefined): string | undefined {\n if (!contentType) return undefined;\n const match = contentType.match(/(?:^|[;\\s])boundary\\s*=\\s*(?:\"([^\"]*)\"|([^\\s;]+))/i);\n if (!match) return undefined;\n // Group 1 is the quoted form (possibly the empty string), group 2 the bare\n // token; an empty boundary is meaningless, so it reads as absent.\n const boundary = match[1] !== undefined ? match[1] : match[2];\n return boundary || undefined;\n}\n\n/**\n * Extract a text field from multipart form data using boundary-based parsing.\n * Splits the body by the multipart boundary so each part is isolated, then\n * checks each part's Content-Disposition header for the target field name.\n * This avoids false matches from binary audio data that might contain\n * header-like byte sequences.\n */\nexport function extractFormField(\n raw: string,\n fieldName: string,\n boundary: string | undefined,\n): string | undefined {\n if (!boundary) {\n // Fallback: no boundary available, use simple regex (best-effort)\n console.warn(\"extractFormField: no multipart boundary found, using best-effort regex fallback\");\n const escaped = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = new RegExp(\n `Content-Disposition:\\\\s*form-data;(?:[^\\\\r\\\\n]*;)?\\\\s*name=\"${escaped}\"[^\\\\r\\\\n]*\\\\r\\\\n\\\\r\\\\n([^\\\\r\\\\n]*)`,\n \"i\",\n );\n const match = raw.match(pattern);\n return match?.[1];\n }\n\n // Split by boundary delimiter — each chunk is one part\n const delimiter = `--${boundary}`;\n const parts = raw.split(delimiter);\n\n for (const part of parts) {\n // Skip the preamble (before first boundary) and epilogue (after closing boundary)\n if (!part || part.trimStart().startsWith(\"--\")) continue;\n\n // Split part into headers and body at the first blank line (\\r\\n\\r\\n)\n const headerEnd = part.indexOf(\"\\r\\n\\r\\n\");\n if (headerEnd === -1) continue;\n\n const headers = part.slice(0, headerEnd);\n const body = part.slice(headerEnd + 4);\n\n // Check if this part's Content-Disposition names the target field\n const cdMatch = headers.match(\n /Content-Disposition:\\s*form-data;(?:[^\\r\\n]*;)?\\s*name=\"([^\"]+)\"/i,\n );\n if (cdMatch && cdMatch[1] === fieldName) {\n // Return the body value, trimming trailing \\r\\n from the part boundary\n return body.replace(/\\r\\n$/, \"\");\n }\n }\n return undefined;\n}\n\nexport async function handleTranscription(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n endpointType: \"transcription\" | \"translation\" = \"transcription\",\n): Promise<void> {\n setCorsHeaders(res);\n const defaultPath =\n endpointType === \"translation\" ? \"/v1/audio/translations\" : \"/v1/audio/transcriptions\";\n const path = req.url ?? defaultPath;\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const boundary = extractBoundary(contentType);\n\n const model = extractFormField(raw, \"model\", boundary) ?? \"whisper-1\";\n const responseFormat = extractFormField(raw, \"response_format\", boundary) ?? \"json\";\n const stream = extractFormField(raw, \"stream\", boundary) === \"true\";\n\n const syntheticReq: ChatCompletionRequest = {\n model,\n messages: [],\n _endpointType: endpointType,\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss;\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n renderError(\"openai\", \"strict-miss\", {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n }).body,\n );\n return;\n }\n if (defaults.record) {\n const { outcome, clientHungUp } = await withHangUpDetection(req, res, () =>\n proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? defaultPath,\n fixtures,\n defaults,\n raw,\n ),\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: res.statusCode ?? 200,\n fixture: null,\n source: \"proxy\",\n ...proxyInterruption(res, clientHungUp),\n },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n renderError(\"openai\", \"fixture-miss\", {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n }).body,\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, renderError(\"openai\", \"fixture\", response.error).body, {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isTranscriptionResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n renderError(\"openai\", \"unknown-response\", {\n message: \"Fixture response is not a transcription type\",\n type: \"server_error\",\n }).body,\n );\n return;\n }\n\n const journalEntry = journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const t = response.transcription;\n\n // Only the transcription endpoint streams modern models. Whisper-1 ignores\n // `stream=true`, and translations continue to return their JSON response.\n if (endpointType === \"transcription\" && stream && model !== \"whisper-1\") {\n const done = {\n type: \"transcript.text.done\",\n text: t.text,\n ...(t.languages !== undefined ? { languages: t.languages } : {}),\n ...(t.usage !== undefined ? { usage: t.usage } : {}),\n };\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n const frames: TranscriptDelta[] = [];\n for (let index = 0; index < t.text.length; index += chunkSize) {\n frames.push({ type: \"transcript.text.delta\", delta: t.text.slice(index, index + chunkSize) });\n }\n // The live `gpt-transcribe&stream=true` stream ends with the `[DONE]`\n // sentinel after `transcript.text.done`. Omitting it leaves clients that\n // loop until the sentinel waiting on a stream that never terminates.\n const codec: FrameCodec<TranscriptDelta> = {\n ...sseCodec<TranscriptDelta>({ done: false }),\n writeHeadImmediately: true,\n trailer: () => [`data: ${JSON.stringify(done)}\\n\\n`, \"data: [DONE]\\n\\n\"],\n };\n await deliverStream(res, journalEntry, fixture, codec, frames, {\n latency: fixture.latency ?? defaults.latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\n });\n return;\n }\n\n const useVerbose = responseFormat === \"verbose_json\" || t.words != null || t.segments != null;\n\n if (useVerbose) {\n const verboseBody: Record<string, unknown> = {\n task: endpointType === \"translation\" ? \"translate\" : \"transcribe\",\n language: t.language ?? \"english\",\n duration: t.duration ?? 0,\n text: t.text,\n };\n if (t.words && t.words.length > 0) {\n verboseBody.words = t.words;\n }\n if (t.segments && t.segments.length > 0) {\n verboseBody.segments = t.segments;\n }\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(verboseBody));\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n text: t.text,\n ...(t.languages !== undefined ? { languages: t.languages } : {}),\n ...(t.usage !== undefined ? { usage: t.usage } : {}),\n }),\n );\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"transcription.d.cts","names":[],"sources":["../src/transcription.ts"],"sourcesContent":[],"mappings":";;;;;;iBAuGsB,mBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0EAE1B"}
1
+ {"version":3,"file":"transcription.d.cts","names":[],"sources":["../src/transcription.ts"],"sourcesContent":[],"mappings":";;;;;;iBA8GsB,mBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0EAE1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"transcription.d.ts","names":[],"sources":["../src/transcription.ts"],"sourcesContent":[],"mappings":";;;;;;iBAuGsB,mBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0EAE1B"}
1
+ {"version":3,"file":"transcription.d.ts","names":[],"sources":["../src/transcription.ts"],"sourcesContent":[],"mappings":";;;;;;iBA8GsB,mBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0EAE1B"}
@@ -1,9 +1,12 @@
1
- import { flattenHeaders, getContext, getTestId, isErrorResponse, isTranscriptionResponse, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
2
- import { calculateDelay, delay, writeErrorResponse } from "./sse-writer.js";
1
+ import { renderError } from "./response/errors.js";
2
+ import { resolveRequestDecisions, strictOverrideField } from "./response/policy.js";
3
+ import { flattenHeaders, getContext, getTestId, isErrorResponse, isTranscriptionResponse, resolveResponse, strictNoMatchLogLine, strictNoMatchMessage } from "./helpers.js";
4
+ import { sseCodec, writeErrorResponse } from "./sse-writer.js";
3
5
  import { applyChaosAsync } from "./chaos.js";
4
6
  import { matchFixtureDiagnostic } from "./router.js";
5
7
  import { proxyAndRecord } from "./recorder.js";
6
- import { createInterruptionSignal } from "./interruption.js";
8
+ import { deliverStream } from "./response/deliver.js";
9
+ import { proxyInterruption, withHangUpDetection } from "./response/proxy.js";
7
10
 
8
11
  //#region src/transcription.ts
9
12
  /**
@@ -86,7 +89,7 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
86
89
  body: syntheticReq
87
90
  }, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
88
91
  if (!fixture) {
89
- if (resolveStrictMode(defaults.strict, req.headers)) {
92
+ if (resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
90
93
  const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
91
94
  defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));
92
95
  journal.add({
@@ -100,15 +103,15 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
100
103
  ...strictOverrideField(defaults.strict, req.headers)
101
104
  }
102
105
  });
103
- writeErrorResponse(res, 503, JSON.stringify({ error: {
106
+ writeErrorResponse(res, 503, renderError("openai", "strict-miss", {
104
107
  message: strictMessage,
105
108
  type: "invalid_request_error",
106
109
  code: "no_fixture_match"
107
- } }));
110
+ }).body);
108
111
  return;
109
112
  }
110
113
  if (defaults.record) {
111
- const outcome = await proxyAndRecord(req, res, syntheticReq, "openai", req.url ?? defaultPath, fixtures, defaults, raw);
114
+ const { outcome, clientHungUp } = await withHangUpDetection(req, res, () => proxyAndRecord(req, res, syntheticReq, "openai", req.url ?? defaultPath, fixtures, defaults, raw));
112
115
  if (outcome === "handled_by_hook") return;
113
116
  if (outcome !== "not_configured") {
114
117
  journal.add({
@@ -119,7 +122,8 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
119
122
  response: {
120
123
  status: res.statusCode ?? 200,
121
124
  fixture: null,
122
- source: "proxy"
125
+ source: "proxy",
126
+ ...proxyInterruption(res, clientHungUp)
123
127
  }
124
128
  });
125
129
  return;
@@ -136,11 +140,11 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
136
140
  ...strictOverrideField(defaults.strict, req.headers)
137
141
  }
138
142
  });
139
- writeErrorResponse(res, 404, JSON.stringify({ error: {
143
+ writeErrorResponse(res, 404, renderError("openai", "fixture-miss", {
140
144
  message: "No fixture matched",
141
145
  type: "invalid_request_error",
142
146
  code: "no_fixture_match"
143
- } }));
147
+ }).body);
144
148
  return;
145
149
  }
146
150
  const response = await resolveResponse(fixture, syntheticReq);
@@ -156,7 +160,7 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
156
160
  fixture
157
161
  }
158
162
  });
159
- writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
163
+ writeErrorResponse(res, status, renderError("openai", "fixture", response.error).body, { retryAfter: response.retryAfter });
160
164
  return;
161
165
  }
162
166
  if (!isTranscriptionResponse(response)) {
@@ -170,10 +174,10 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
170
174
  fixture
171
175
  }
172
176
  });
173
- writeErrorResponse(res, 500, JSON.stringify({ error: {
177
+ writeErrorResponse(res, 500, renderError("openai", "unknown-response", {
174
178
  message: "Fixture response is not a transcription type",
175
179
  type: "server_error"
176
- } }));
180
+ }).body);
177
181
  return;
178
182
  }
179
183
  const journalEntry = journal.add({
@@ -194,38 +198,22 @@ async function handleTranscription(req, res, raw, fixtures, journal, defaults, s
194
198
  ...t.languages !== void 0 ? { languages: t.languages } : {},
195
199
  ...t.usage !== void 0 ? { usage: t.usage } : {}
196
200
  };
197
- const latency = fixture.latency ?? defaults.latency;
198
201
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
199
- const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
200
- res.writeHead(200, {
201
- "Content-Type": "text/event-stream",
202
- "Cache-Control": "no-cache",
203
- Connection: "keep-alive"
202
+ const frames = [];
203
+ for (let index = 0; index < t.text.length; index += chunkSize) frames.push({
204
+ type: "transcript.text.delta",
205
+ delta: t.text.slice(index, index + chunkSize)
206
+ });
207
+ await deliverStream(res, journalEntry, fixture, {
208
+ ...sseCodec({ done: false }),
209
+ writeHeadImmediately: true,
210
+ trailer: () => [`data: ${JSON.stringify(done)}\n\n`, "data: [DONE]\n\n"]
211
+ }, frames, {
212
+ latency: fixture.latency ?? defaults.latency,
213
+ streamingProfile: fixture.streamingProfile,
214
+ recordedTimings: fixture.recordedTimings,
215
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
204
216
  });
205
- let chunkIndex = 0;
206
- const interruption = createInterruptionSignal(fixture);
207
- for (let index = 0; index < t.text.length; index += chunkSize) {
208
- const chunkDelay = calculateDelay(chunkIndex, fixture.streamingProfile, latency, fixture.recordedTimings, replaySpeed);
209
- if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);
210
- if (interruption?.signal.aborted) break;
211
- res.write(`data: ${JSON.stringify({
212
- type: "transcript.text.delta",
213
- delta: t.text.slice(index, index + chunkSize)
214
- })}\n\n`);
215
- interruption?.tick();
216
- chunkIndex++;
217
- }
218
- if (interruption?.signal.aborted) {
219
- journalEntry.response.interrupted = true;
220
- journalEntry.response.interruptReason = interruption.reason();
221
- interruption.cleanup();
222
- res.destroy();
223
- return;
224
- }
225
- res.write(`data: ${JSON.stringify(done)}\n\n`);
226
- res.write("data: [DONE]\n\n");
227
- res.end();
228
- interruption?.cleanup();
229
217
  return;
230
218
  }
231
219
  if (responseFormat === "verbose_json" || t.words != null || t.segments != null) {
@@ -1 +1 @@
1
- {"version":3,"file":"transcription.js","names":[],"sources":["../src/transcription.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults } from \"./types.js\";\nimport {\n isTranscriptionResponse,\n isErrorResponse,\n serializeErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { calculateDelay, delay, writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaosAsync } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\n\n/**\n * Extract the multipart boundary string from a Content-Type header.\n *\n * RFC 2046 §5.1.1 defines the boundary parameter as either a bare token\n * (`boundary=abc123`) or a quoted-string (`boundary=\"abc123\"`), and its\n * `bchars` production INCLUDES a space — which is exactly the case the quoted\n * form exists for, and the one a capture that stops at whitespace can never\n * read. So the quotes are consumed BY the match rather than stripped after it:\n * a post-strip runs on an already-truncated value and leaves `boundary=\"a b c\"`\n * as the useless `\"a`, and the delimiter then never matches, so every form\n * field silently falls back to its default (`whisper-1`, `gpt-image-1`, …).\n *\n * Only DQUOTE quotes. An apostrophe is a legal RFC 2045 token character and a\n * legal RFC 2046 `bchar`, so `boundary='abc'` is a BARE token whose value\n * literally includes the quotes, and stripping them would break a working\n * request.\n *\n * The `(?:^|[;\\s])` prefix keeps a decoy parameter such as `myboundary=` from\n * matching, and `\\s*=\\s*` tolerates the LWSP some clients put around the `=`.\n */\nexport function extractBoundary(contentType: string | undefined): string | undefined {\n if (!contentType) return undefined;\n const match = contentType.match(/(?:^|[;\\s])boundary\\s*=\\s*(?:\"([^\"]*)\"|([^\\s;]+))/i);\n if (!match) return undefined;\n // Group 1 is the quoted form (possibly the empty string), group 2 the bare\n // token; an empty boundary is meaningless, so it reads as absent.\n const boundary = match[1] !== undefined ? match[1] : match[2];\n return boundary || undefined;\n}\n\n/**\n * Extract a text field from multipart form data using boundary-based parsing.\n * Splits the body by the multipart boundary so each part is isolated, then\n * checks each part's Content-Disposition header for the target field name.\n * This avoids false matches from binary audio data that might contain\n * header-like byte sequences.\n */\nexport function extractFormField(\n raw: string,\n fieldName: string,\n boundary: string | undefined,\n): string | undefined {\n if (!boundary) {\n // Fallback: no boundary available, use simple regex (best-effort)\n console.warn(\"extractFormField: no multipart boundary found, using best-effort regex fallback\");\n const escaped = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = new RegExp(\n `Content-Disposition:\\\\s*form-data;(?:[^\\\\r\\\\n]*;)?\\\\s*name=\"${escaped}\"[^\\\\r\\\\n]*\\\\r\\\\n\\\\r\\\\n([^\\\\r\\\\n]*)`,\n \"i\",\n );\n const match = raw.match(pattern);\n return match?.[1];\n }\n\n // Split by boundary delimiter — each chunk is one part\n const delimiter = `--${boundary}`;\n const parts = raw.split(delimiter);\n\n for (const part of parts) {\n // Skip the preamble (before first boundary) and epilogue (after closing boundary)\n if (!part || part.trimStart().startsWith(\"--\")) continue;\n\n // Split part into headers and body at the first blank line (\\r\\n\\r\\n)\n const headerEnd = part.indexOf(\"\\r\\n\\r\\n\");\n if (headerEnd === -1) continue;\n\n const headers = part.slice(0, headerEnd);\n const body = part.slice(headerEnd + 4);\n\n // Check if this part's Content-Disposition names the target field\n const cdMatch = headers.match(\n /Content-Disposition:\\s*form-data;(?:[^\\r\\n]*;)?\\s*name=\"([^\"]+)\"/i,\n );\n if (cdMatch && cdMatch[1] === fieldName) {\n // Return the body value, trimming trailing \\r\\n from the part boundary\n return body.replace(/\\r\\n$/, \"\");\n }\n }\n return undefined;\n}\n\nexport async function handleTranscription(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n endpointType: \"transcription\" | \"translation\" = \"transcription\",\n): Promise<void> {\n setCorsHeaders(res);\n const defaultPath =\n endpointType === \"translation\" ? \"/v1/audio/translations\" : \"/v1/audio/transcriptions\";\n const path = req.url ?? defaultPath;\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const boundary = extractBoundary(contentType);\n\n const model = extractFormField(raw, \"model\", boundary) ?? \"whisper-1\";\n const responseFormat = extractFormField(raw, \"response_format\", boundary) ?? \"json\";\n const stream = extractFormField(raw, \"stream\", boundary) === \"true\";\n\n const syntheticReq: ChatCompletionRequest = {\n model,\n messages: [],\n _endpointType: endpointType,\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? defaultPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isTranscriptionResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response is not a transcription type\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n const journalEntry = journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const t = response.transcription;\n\n // Only the transcription endpoint streams modern models. Whisper-1 ignores\n // `stream=true`, and translations continue to return their JSON response.\n if (endpointType === \"transcription\" && stream && model !== \"whisper-1\") {\n const done = {\n type: \"transcript.text.done\",\n text: t.text,\n ...(t.languages !== undefined ? { languages: t.languages } : {}),\n ...(t.usage !== undefined ? { usage: t.usage } : {}),\n };\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n let chunkIndex = 0;\n const interruption = createInterruptionSignal(fixture);\n for (let index = 0; index < t.text.length; index += chunkSize) {\n const chunkDelay = calculateDelay(\n chunkIndex,\n fixture.streamingProfile,\n latency,\n fixture.recordedTimings,\n replaySpeed,\n );\n if (chunkDelay > 0) await delay(chunkDelay, interruption?.signal);\n if (interruption?.signal.aborted) break;\n res.write(\n `data: ${JSON.stringify({ type: \"transcript.text.delta\", delta: t.text.slice(index, index + chunkSize) })}\\n\\n`,\n );\n interruption?.tick();\n chunkIndex++;\n }\n if (interruption?.signal.aborted) {\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption.reason();\n interruption.cleanup();\n res.destroy();\n return;\n }\n res.write(`data: ${JSON.stringify(done)}\\n\\n`);\n // The live `gpt-transcribe&stream=true` stream ends with the `[DONE]`\n // sentinel after `transcript.text.done`. Omitting it leaves clients that\n // loop until the sentinel waiting on a stream that never terminates.\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n interruption?.cleanup();\n return;\n }\n\n const useVerbose = responseFormat === \"verbose_json\" || t.words != null || t.segments != null;\n\n if (useVerbose) {\n const verboseBody: Record<string, unknown> = {\n task: endpointType === \"translation\" ? \"translate\" : \"transcribe\",\n language: t.language ?? \"english\",\n duration: t.duration ?? 0,\n text: t.text,\n };\n if (t.words && t.words.length > 0) {\n verboseBody.words = t.words;\n }\n if (t.segments && t.segments.length > 0) {\n verboseBody.segments = t.segments;\n }\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(verboseBody));\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n text: t.text,\n ...(t.languages !== undefined ? { languages: t.languages } : {}),\n ...(t.usage !== undefined ? { usage: t.usage } : {}),\n }),\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"transcription.js","names":[],"sources":["../src/transcription.ts"],"sourcesContent":["import { renderError } from \"./response/errors.js\";\nimport { resolveRequestDecisions, strictOverrideField } from \"./response/policy.js\";\nimport type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults } from \"./types.js\";\nimport {\n isTranscriptionResponse,\n isErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { sseCodec, writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaosAsync } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport { proxyInterruption, withHangUpDetection } from \"./response/proxy.js\";\nimport { deliverStream } from \"./response/deliver.js\";\nimport type { FrameCodec } from \"./response/frame-loop.js\";\n\n/** One `transcript.text.delta` frame of a streamed transcription. */\ninterface TranscriptDelta {\n type: \"transcript.text.delta\";\n delta: string;\n}\n\n/**\n * Extract the multipart boundary string from a Content-Type header.\n *\n * RFC 2046 §5.1.1 defines the boundary parameter as either a bare token\n * (`boundary=abc123`) or a quoted-string (`boundary=\"abc123\"`), and its\n * `bchars` production INCLUDES a space — which is exactly the case the quoted\n * form exists for, and the one a capture that stops at whitespace can never\n * read. So the quotes are consumed BY the match rather than stripped after it:\n * a post-strip runs on an already-truncated value and leaves `boundary=\"a b c\"`\n * as the useless `\"a`, and the delimiter then never matches, so every form\n * field silently falls back to its default (`whisper-1`, `gpt-image-1`, …).\n *\n * Only DQUOTE quotes. An apostrophe is a legal RFC 2045 token character and a\n * legal RFC 2046 `bchar`, so `boundary='abc'` is a BARE token whose value\n * literally includes the quotes, and stripping them would break a working\n * request.\n *\n * The `(?:^|[;\\s])` prefix keeps a decoy parameter such as `myboundary=` from\n * matching, and `\\s*=\\s*` tolerates the LWSP some clients put around the `=`.\n */\nexport function extractBoundary(contentType: string | undefined): string | undefined {\n if (!contentType) return undefined;\n const match = contentType.match(/(?:^|[;\\s])boundary\\s*=\\s*(?:\"([^\"]*)\"|([^\\s;]+))/i);\n if (!match) return undefined;\n // Group 1 is the quoted form (possibly the empty string), group 2 the bare\n // token; an empty boundary is meaningless, so it reads as absent.\n const boundary = match[1] !== undefined ? match[1] : match[2];\n return boundary || undefined;\n}\n\n/**\n * Extract a text field from multipart form data using boundary-based parsing.\n * Splits the body by the multipart boundary so each part is isolated, then\n * checks each part's Content-Disposition header for the target field name.\n * This avoids false matches from binary audio data that might contain\n * header-like byte sequences.\n */\nexport function extractFormField(\n raw: string,\n fieldName: string,\n boundary: string | undefined,\n): string | undefined {\n if (!boundary) {\n // Fallback: no boundary available, use simple regex (best-effort)\n console.warn(\"extractFormField: no multipart boundary found, using best-effort regex fallback\");\n const escaped = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = new RegExp(\n `Content-Disposition:\\\\s*form-data;(?:[^\\\\r\\\\n]*;)?\\\\s*name=\"${escaped}\"[^\\\\r\\\\n]*\\\\r\\\\n\\\\r\\\\n([^\\\\r\\\\n]*)`,\n \"i\",\n );\n const match = raw.match(pattern);\n return match?.[1];\n }\n\n // Split by boundary delimiter — each chunk is one part\n const delimiter = `--${boundary}`;\n const parts = raw.split(delimiter);\n\n for (const part of parts) {\n // Skip the preamble (before first boundary) and epilogue (after closing boundary)\n if (!part || part.trimStart().startsWith(\"--\")) continue;\n\n // Split part into headers and body at the first blank line (\\r\\n\\r\\n)\n const headerEnd = part.indexOf(\"\\r\\n\\r\\n\");\n if (headerEnd === -1) continue;\n\n const headers = part.slice(0, headerEnd);\n const body = part.slice(headerEnd + 4);\n\n // Check if this part's Content-Disposition names the target field\n const cdMatch = headers.match(\n /Content-Disposition:\\s*form-data;(?:[^\\r\\n]*;)?\\s*name=\"([^\"]+)\"/i,\n );\n if (cdMatch && cdMatch[1] === fieldName) {\n // Return the body value, trimming trailing \\r\\n from the part boundary\n return body.replace(/\\r\\n$/, \"\");\n }\n }\n return undefined;\n}\n\nexport async function handleTranscription(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n endpointType: \"transcription\" | \"translation\" = \"transcription\",\n): Promise<void> {\n setCorsHeaders(res);\n const defaultPath =\n endpointType === \"translation\" ? \"/v1/audio/translations\" : \"/v1/audio/transcriptions\";\n const path = req.url ?? defaultPath;\n const method = req.method ?? \"POST\";\n\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n const boundary = extractBoundary(contentType);\n\n const model = extractFormField(raw, \"model\", boundary) ?? \"whisper-1\";\n const responseFormat = extractFormField(raw, \"response_format\", boundary) ?? \"json\";\n const stream = extractFormField(raw, \"stream\", boundary) === \"true\";\n\n const syntheticReq: ChatCompletionRequest = {\n model,\n messages: [],\n _endpointType: endpointType,\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss;\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n renderError(\"openai\", \"strict-miss\", {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n }).body,\n );\n return;\n }\n if (defaults.record) {\n const { outcome, clientHungUp } = await withHangUpDetection(req, res, () =>\n proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? defaultPath,\n fixtures,\n defaults,\n raw,\n ),\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: res.statusCode ?? 200,\n fixture: null,\n source: \"proxy\",\n ...proxyInterruption(res, clientHungUp),\n },\n });\n return;\n }\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n renderError(\"openai\", \"fixture-miss\", {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n }).body,\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, renderError(\"openai\", \"fixture\", response.error).body, {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isTranscriptionResponse(response)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n renderError(\"openai\", \"unknown-response\", {\n message: \"Fixture response is not a transcription type\",\n type: \"server_error\",\n }).body,\n );\n return;\n }\n\n const journalEntry = journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const t = response.transcription;\n\n // Only the transcription endpoint streams modern models. Whisper-1 ignores\n // `stream=true`, and translations continue to return their JSON response.\n if (endpointType === \"transcription\" && stream && model !== \"whisper-1\") {\n const done = {\n type: \"transcript.text.done\",\n text: t.text,\n ...(t.languages !== undefined ? { languages: t.languages } : {}),\n ...(t.usage !== undefined ? { usage: t.usage } : {}),\n };\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n const frames: TranscriptDelta[] = [];\n for (let index = 0; index < t.text.length; index += chunkSize) {\n frames.push({ type: \"transcript.text.delta\", delta: t.text.slice(index, index + chunkSize) });\n }\n // The live `gpt-transcribe&stream=true` stream ends with the `[DONE]`\n // sentinel after `transcript.text.done`. Omitting it leaves clients that\n // loop until the sentinel waiting on a stream that never terminates.\n const codec: FrameCodec<TranscriptDelta> = {\n ...sseCodec<TranscriptDelta>({ done: false }),\n writeHeadImmediately: true,\n trailer: () => [`data: ${JSON.stringify(done)}\\n\\n`, \"data: [DONE]\\n\\n\"],\n };\n await deliverStream(res, journalEntry, fixture, codec, frames, {\n latency: fixture.latency ?? defaults.latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\n });\n return;\n }\n\n const useVerbose = responseFormat === \"verbose_json\" || t.words != null || t.segments != null;\n\n if (useVerbose) {\n const verboseBody: Record<string, unknown> = {\n task: endpointType === \"translation\" ? \"translate\" : \"transcribe\",\n language: t.language ?? \"english\",\n duration: t.duration ?? 0,\n text: t.text,\n };\n if (t.words && t.words.length > 0) {\n verboseBody.words = t.words;\n }\n if (t.segments && t.segments.length > 0) {\n verboseBody.segments = t.segments;\n }\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(verboseBody));\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n text: t.text,\n ...(t.languages !== undefined ? { languages: t.languages } : {}),\n ...(t.usage !== undefined ? { usage: t.usage } : {}),\n }),\n );\n }\n}\n"],"mappings":"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"}