@copilotkit/aimock 1.45.1 → 1.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +13 -0
  4. package/README.md +7 -1
  5. package/dist/agui-mock.cjs +25 -25
  6. package/dist/agui-mock.cjs.map +1 -1
  7. package/dist/agui-mock.d.cts.map +1 -1
  8. package/dist/agui-mock.d.ts.map +1 -1
  9. package/dist/agui-mock.js +25 -25
  10. package/dist/agui-mock.js.map +1 -1
  11. package/dist/agui-types.d.cts.map +1 -1
  12. package/dist/aimock-cli.cjs +15 -1
  13. package/dist/aimock-cli.cjs.map +1 -1
  14. package/dist/aimock-cli.d.cts.map +1 -1
  15. package/dist/aimock-cli.d.ts.map +1 -1
  16. package/dist/aimock-cli.js +16 -2
  17. package/dist/aimock-cli.js.map +1 -1
  18. package/dist/api-key-auth.cjs +10 -9
  19. package/dist/api-key-auth.cjs.map +1 -1
  20. package/dist/api-key-auth.d.cts.map +1 -1
  21. package/dist/api-key-auth.d.ts.map +1 -1
  22. package/dist/api-key-auth.js +11 -10
  23. package/dist/api-key-auth.js.map +1 -1
  24. package/dist/batches.cjs +2 -4
  25. package/dist/batches.cjs.map +1 -1
  26. package/dist/batches.d.cts.map +1 -1
  27. package/dist/batches.d.ts.map +1 -1
  28. package/dist/batches.js +2 -4
  29. package/dist/batches.js.map +1 -1
  30. package/dist/bedrock-converse.cjs +61 -80
  31. package/dist/bedrock-converse.cjs.map +1 -1
  32. package/dist/bedrock-converse.d.cts.map +1 -1
  33. package/dist/bedrock-converse.d.ts.map +1 -1
  34. package/dist/bedrock-converse.js +58 -77
  35. package/dist/bedrock-converse.js.map +1 -1
  36. package/dist/bedrock.cjs +65 -97
  37. package/dist/bedrock.cjs.map +1 -1
  38. package/dist/bedrock.d.cts.map +1 -1
  39. package/dist/bedrock.d.ts.map +1 -1
  40. package/dist/bedrock.js +62 -94
  41. package/dist/bedrock.js.map +1 -1
  42. package/dist/byteplus-video.cjs +11 -10
  43. package/dist/byteplus-video.cjs.map +1 -1
  44. package/dist/byteplus-video.d.cts.map +1 -1
  45. package/dist/byteplus-video.d.ts.map +1 -1
  46. package/dist/byteplus-video.js +5 -4
  47. package/dist/byteplus-video.js.map +1 -1
  48. package/dist/chaos.cjs +5 -4
  49. package/dist/chaos.cjs.map +1 -1
  50. package/dist/chaos.d.cts.map +1 -1
  51. package/dist/chaos.d.ts.map +1 -1
  52. package/dist/chaos.js +5 -4
  53. package/dist/chaos.js.map +1 -1
  54. package/dist/cli.cjs +15 -0
  55. package/dist/cli.cjs.map +1 -1
  56. package/dist/cli.js +15 -0
  57. package/dist/cli.js.map +1 -1
  58. package/dist/cohere.cjs +49 -74
  59. package/dist/cohere.cjs.map +1 -1
  60. package/dist/cohere.d.cts.map +1 -1
  61. package/dist/cohere.d.ts.map +1 -1
  62. package/dist/cohere.js +47 -72
  63. package/dist/cohere.js.map +1 -1
  64. package/dist/config-loader.cjs +29 -1
  65. package/dist/config-loader.cjs.map +1 -1
  66. package/dist/config-loader.d.cts +7 -1
  67. package/dist/config-loader.d.cts.map +1 -1
  68. package/dist/config-loader.d.ts +7 -1
  69. package/dist/config-loader.d.ts.map +1 -1
  70. package/dist/config-loader.js +29 -2
  71. package/dist/config-loader.js.map +1 -1
  72. package/dist/count-tokens.cjs +146 -0
  73. package/dist/count-tokens.cjs.map +1 -0
  74. package/dist/count-tokens.js +146 -0
  75. package/dist/count-tokens.js.map +1 -0
  76. package/dist/elevenlabs-audio.cjs +36 -34
  77. package/dist/elevenlabs-audio.cjs.map +1 -1
  78. package/dist/elevenlabs-audio.d.cts.map +1 -1
  79. package/dist/elevenlabs-audio.d.ts.map +1 -1
  80. package/dist/elevenlabs-audio.js +33 -31
  81. package/dist/elevenlabs-audio.js.map +1 -1
  82. package/dist/elevenlabs-voice.cjs +38 -37
  83. package/dist/elevenlabs-voice.cjs.map +1 -1
  84. package/dist/elevenlabs-voice.d.cts.map +1 -1
  85. package/dist/elevenlabs-voice.d.ts.map +1 -1
  86. package/dist/elevenlabs-voice.js +33 -32
  87. package/dist/elevenlabs-voice.js.map +1 -1
  88. package/dist/embedding-request.cjs +16 -0
  89. package/dist/embedding-request.cjs.map +1 -0
  90. package/dist/embedding-request.js +14 -0
  91. package/dist/embedding-request.js.map +1 -0
  92. package/dist/embeddings.cjs +30 -20
  93. package/dist/embeddings.cjs.map +1 -1
  94. package/dist/embeddings.d.cts.map +1 -1
  95. package/dist/embeddings.d.ts.map +1 -1
  96. package/dist/embeddings.js +30 -20
  97. package/dist/embeddings.js.map +1 -1
  98. package/dist/fal-audio.cjs +45 -43
  99. package/dist/fal-audio.cjs.map +1 -1
  100. package/dist/fal-audio.d.cts.map +1 -1
  101. package/dist/fal-audio.d.ts.map +1 -1
  102. package/dist/fal-audio.js +42 -40
  103. package/dist/fal-audio.js.map +1 -1
  104. package/dist/fal.cjs +17 -15
  105. package/dist/fal.cjs.map +1 -1
  106. package/dist/fal.js +16 -14
  107. package/dist/fal.js.map +1 -1
  108. package/dist/files.cjs +2 -4
  109. package/dist/files.cjs.map +1 -1
  110. package/dist/files.d.cts.map +1 -1
  111. package/dist/files.d.ts.map +1 -1
  112. package/dist/files.js +2 -4
  113. package/dist/files.js.map +1 -1
  114. package/dist/fine-tuning.cjs +2 -4
  115. package/dist/fine-tuning.cjs.map +1 -1
  116. package/dist/fine-tuning.d.cts.map +1 -1
  117. package/dist/fine-tuning.d.ts.map +1 -1
  118. package/dist/fine-tuning.js +2 -4
  119. package/dist/fine-tuning.js.map +1 -1
  120. package/dist/fixture-loader.cjs +37 -42
  121. package/dist/fixture-loader.cjs.map +1 -1
  122. package/dist/fixture-loader.d.cts.map +1 -1
  123. package/dist/fixture-loader.d.ts.map +1 -1
  124. package/dist/fixture-loader.js +37 -42
  125. package/dist/fixture-loader.js.map +1 -1
  126. package/dist/gemini-embeddings.cjs +26 -24
  127. package/dist/gemini-embeddings.cjs.map +1 -1
  128. package/dist/gemini-embeddings.js +26 -24
  129. package/dist/gemini-embeddings.js.map +1 -1
  130. package/dist/gemini-interactions.cjs +50 -35
  131. package/dist/gemini-interactions.cjs.map +1 -1
  132. package/dist/gemini-interactions.d.cts +0 -1
  133. package/dist/gemini-interactions.d.cts.map +1 -1
  134. package/dist/gemini-interactions.d.ts +0 -1
  135. package/dist/gemini-interactions.d.ts.map +1 -1
  136. package/dist/gemini-interactions.js +49 -34
  137. package/dist/gemini-interactions.js.map +1 -1
  138. package/dist/gemini.cjs +57 -67
  139. package/dist/gemini.cjs.map +1 -1
  140. package/dist/gemini.d.cts.map +1 -1
  141. package/dist/gemini.d.ts.map +1 -1
  142. package/dist/gemini.js +56 -67
  143. package/dist/gemini.js.map +1 -1
  144. package/dist/grok-video.cjs +14 -12
  145. package/dist/grok-video.cjs.map +1 -1
  146. package/dist/grok-video.d.cts.map +1 -1
  147. package/dist/grok-video.d.ts.map +1 -1
  148. package/dist/grok-video.js +8 -6
  149. package/dist/grok-video.js.map +1 -1
  150. package/dist/helpers.cjs +11 -68
  151. package/dist/helpers.cjs.map +1 -1
  152. package/dist/helpers.d.cts.map +1 -1
  153. package/dist/helpers.d.ts.map +1 -1
  154. package/dist/helpers.js +12 -67
  155. package/dist/helpers.js.map +1 -1
  156. package/dist/images.cjs +30 -28
  157. package/dist/images.cjs.map +1 -1
  158. package/dist/images.d.cts.map +1 -1
  159. package/dist/images.d.ts.map +1 -1
  160. package/dist/images.js +27 -25
  161. package/dist/images.js.map +1 -1
  162. package/dist/index.d.cts +2 -2
  163. package/dist/index.d.ts +2 -2
  164. package/dist/journal.cjs +3 -1
  165. package/dist/journal.cjs.map +1 -1
  166. package/dist/journal.d.cts.map +1 -1
  167. package/dist/journal.d.ts.map +1 -1
  168. package/dist/journal.js +2 -1
  169. package/dist/journal.js.map +1 -1
  170. package/dist/llmock.cjs +3 -0
  171. package/dist/llmock.cjs.map +1 -1
  172. package/dist/llmock.d.cts.map +1 -1
  173. package/dist/llmock.d.ts.map +1 -1
  174. package/dist/llmock.js +3 -0
  175. package/dist/llmock.js.map +1 -1
  176. package/dist/mcp-handler.cjs +7 -3
  177. package/dist/mcp-handler.cjs.map +1 -1
  178. package/dist/mcp-handler.js +7 -3
  179. package/dist/mcp-handler.js.map +1 -1
  180. package/dist/mcp-recorder.cjs +2 -1
  181. package/dist/mcp-recorder.cjs.map +1 -1
  182. package/dist/mcp-recorder.js +3 -2
  183. package/dist/mcp-recorder.js.map +1 -1
  184. package/dist/messages.cjs +111 -96
  185. package/dist/messages.cjs.map +1 -1
  186. package/dist/messages.d.cts.map +1 -1
  187. package/dist/messages.d.ts.map +1 -1
  188. package/dist/messages.js +109 -95
  189. package/dist/messages.js.map +1 -1
  190. package/dist/metrics.cjs +5 -2
  191. package/dist/metrics.cjs.map +1 -1
  192. package/dist/metrics.d.cts.map +1 -1
  193. package/dist/metrics.d.ts.map +1 -1
  194. package/dist/metrics.js +6 -3
  195. package/dist/metrics.js.map +1 -1
  196. package/dist/model-utils.cjs +1 -0
  197. package/dist/model-utils.cjs.map +1 -1
  198. package/dist/model-utils.js +1 -1
  199. package/dist/model-utils.js.map +1 -1
  200. package/dist/moderation.cjs +15 -12
  201. package/dist/moderation.cjs.map +1 -1
  202. package/dist/moderation.js +14 -11
  203. package/dist/moderation.js.map +1 -1
  204. package/dist/ollama.cjs +128 -68
  205. package/dist/ollama.cjs.map +1 -1
  206. package/dist/ollama.d.cts.map +1 -1
  207. package/dist/ollama.d.ts.map +1 -1
  208. package/dist/ollama.js +124 -64
  209. package/dist/ollama.js.map +1 -1
  210. package/dist/openapi.cjs +323 -3
  211. package/dist/openapi.cjs.map +1 -1
  212. package/dist/openapi.d.cts.map +1 -1
  213. package/dist/openapi.d.ts.map +1 -1
  214. package/dist/openapi.js +324 -4
  215. package/dist/openapi.js.map +1 -1
  216. package/dist/openrouter-chat.cjs +11 -155
  217. package/dist/openrouter-chat.cjs.map +1 -1
  218. package/dist/openrouter-chat.js +11 -155
  219. package/dist/openrouter-chat.js.map +1 -1
  220. package/dist/openrouter-video.cjs +51 -74
  221. package/dist/openrouter-video.cjs.map +1 -1
  222. package/dist/openrouter-video.d.cts.map +1 -1
  223. package/dist/openrouter-video.d.ts.map +1 -1
  224. package/dist/openrouter-video.js +37 -60
  225. package/dist/openrouter-video.js.map +1 -1
  226. package/dist/recorder.cjs +176 -95
  227. package/dist/recorder.cjs.map +1 -1
  228. package/dist/recorder.d.cts.map +1 -1
  229. package/dist/recorder.d.ts.map +1 -1
  230. package/dist/recorder.js +176 -95
  231. package/dist/recorder.js.map +1 -1
  232. package/dist/rerank.cjs +13 -11
  233. package/dist/rerank.cjs.map +1 -1
  234. package/dist/rerank.js +12 -10
  235. package/dist/rerank.js.map +1 -1
  236. package/dist/response/catalog/anthropic-page.cjs +39 -0
  237. package/dist/response/catalog/anthropic-page.cjs.map +1 -0
  238. package/dist/response/catalog/anthropic-page.js +38 -0
  239. package/dist/response/catalog/anthropic-page.js.map +1 -0
  240. package/dist/response/catalog/collect.cjs +61 -0
  241. package/dist/response/catalog/collect.cjs.map +1 -0
  242. package/dist/response/catalog/collect.js +59 -0
  243. package/dist/response/catalog/collect.js.map +1 -0
  244. package/dist/response/catalog/gemini-page.cjs +25 -0
  245. package/dist/response/catalog/gemini-page.cjs.map +1 -0
  246. package/dist/response/catalog/gemini-page.js +24 -0
  247. package/dist/response/catalog/gemini-page.js.map +1 -0
  248. package/dist/response/catalog/render.cjs +182 -0
  249. package/dist/response/catalog/render.cjs.map +1 -0
  250. package/dist/response/catalog/render.js +171 -0
  251. package/dist/response/catalog/render.js.map +1 -0
  252. package/dist/response/errors.cjs +225 -0
  253. package/dist/response/errors.cjs.map +1 -0
  254. package/dist/response/errors.js +224 -0
  255. package/dist/response/errors.js.map +1 -0
  256. package/dist/response/finish.cjs +39 -0
  257. package/dist/response/finish.cjs.map +1 -0
  258. package/dist/response/finish.js +35 -0
  259. package/dist/response/finish.js.map +1 -0
  260. package/dist/response/policy.cjs +126 -0
  261. package/dist/response/policy.cjs.map +1 -0
  262. package/dist/response/policy.js +120 -0
  263. package/dist/response/policy.js.map +1 -0
  264. package/dist/response/profiles.cjs +69 -0
  265. package/dist/response/profiles.cjs.map +1 -0
  266. package/dist/response/profiles.js +65 -0
  267. package/dist/response/profiles.js.map +1 -0
  268. package/dist/response/usage/codecs/anthropic.cjs +52 -0
  269. package/dist/response/usage/codecs/anthropic.cjs.map +1 -0
  270. package/dist/response/usage/codecs/anthropic.js +50 -0
  271. package/dist/response/usage/codecs/anthropic.js.map +1 -0
  272. package/dist/response/usage/codecs/cohere.cjs +36 -0
  273. package/dist/response/usage/codecs/cohere.cjs.map +1 -0
  274. package/dist/response/usage/codecs/cohere.js +35 -0
  275. package/dist/response/usage/codecs/cohere.js.map +1 -0
  276. package/dist/response/usage/codecs/converse.cjs +16 -0
  277. package/dist/response/usage/codecs/converse.cjs.map +1 -0
  278. package/dist/response/usage/codecs/converse.js +15 -0
  279. package/dist/response/usage/codecs/converse.js.map +1 -0
  280. package/dist/response/usage/codecs/gemini.cjs +31 -0
  281. package/dist/response/usage/codecs/gemini.cjs.map +1 -0
  282. package/dist/response/usage/codecs/gemini.js +31 -0
  283. package/dist/response/usage/codecs/gemini.js.map +1 -0
  284. package/dist/response/usage/codecs/interactions.cjs +22 -0
  285. package/dist/response/usage/codecs/interactions.cjs.map +1 -0
  286. package/dist/response/usage/codecs/interactions.js +21 -0
  287. package/dist/response/usage/codecs/interactions.js.map +1 -0
  288. package/dist/response/usage/codecs/ollama.cjs +17 -0
  289. package/dist/response/usage/codecs/ollama.cjs.map +1 -0
  290. package/dist/response/usage/codecs/ollama.js +17 -0
  291. package/dist/response/usage/codecs/ollama.js.map +1 -0
  292. package/dist/response/usage/codecs/openai.cjs +52 -0
  293. package/dist/response/usage/codecs/openai.cjs.map +1 -0
  294. package/dist/response/usage/codecs/openai.js +50 -0
  295. package/dist/response/usage/codecs/openai.js.map +1 -0
  296. package/dist/response/usage/codecs/openrouter.cjs +68 -0
  297. package/dist/response/usage/codecs/openrouter.cjs.map +1 -0
  298. package/dist/response/usage/codecs/openrouter.js +67 -0
  299. package/dist/response/usage/codecs/openrouter.js.map +1 -0
  300. package/dist/response/usage/codecs/realtime.cjs +19 -0
  301. package/dist/response/usage/codecs/realtime.cjs.map +1 -0
  302. package/dist/response/usage/codecs/realtime.js +18 -0
  303. package/dist/response/usage/codecs/realtime.js.map +1 -0
  304. package/dist/response/usage/codecs/responses.cjs +21 -0
  305. package/dist/response/usage/codecs/responses.cjs.map +1 -0
  306. package/dist/response/usage/codecs/responses.js +20 -0
  307. package/dist/response/usage/codecs/responses.js.map +1 -0
  308. package/dist/response/usage/estimate.cjs +23 -0
  309. package/dist/response/usage/estimate.cjs.map +1 -0
  310. package/dist/response/usage/estimate.js +21 -0
  311. package/dist/response/usage/estimate.js.map +1 -0
  312. package/dist/response/usage/fields.cjs +72 -0
  313. package/dist/response/usage/fields.cjs.map +1 -0
  314. package/dist/response/usage/fields.js +70 -0
  315. package/dist/response/usage/fields.js.map +1 -0
  316. package/dist/response/usage/recording.cjs +52 -0
  317. package/dist/response/usage/recording.cjs.map +1 -0
  318. package/dist/response/usage/recording.js +52 -0
  319. package/dist/response/usage/recording.js.map +1 -0
  320. package/dist/responses.cjs +45 -58
  321. package/dist/responses.cjs.map +1 -1
  322. package/dist/responses.d.cts.map +1 -1
  323. package/dist/responses.d.ts.map +1 -1
  324. package/dist/responses.js +44 -57
  325. package/dist/responses.js.map +1 -1
  326. package/dist/route-registry.cjs +45 -0
  327. package/dist/route-registry.cjs.map +1 -1
  328. package/dist/route-registry.d.cts.map +1 -1
  329. package/dist/route-registry.d.ts.map +1 -1
  330. package/dist/route-registry.js +41 -1
  331. package/dist/route-registry.js.map +1 -1
  332. package/dist/router.cjs +4 -0
  333. package/dist/router.cjs.map +1 -1
  334. package/dist/router.d.cts.map +1 -1
  335. package/dist/router.d.ts.map +1 -1
  336. package/dist/router.js +3 -0
  337. package/dist/router.js.map +1 -1
  338. package/dist/search.cjs +13 -11
  339. package/dist/search.cjs.map +1 -1
  340. package/dist/search.js +12 -10
  341. package/dist/search.js.map +1 -1
  342. package/dist/server.cjs +222 -146
  343. package/dist/server.cjs.map +1 -1
  344. package/dist/server.d.cts.map +1 -1
  345. package/dist/server.d.ts.map +1 -1
  346. package/dist/server.js +223 -147
  347. package/dist/server.js.map +1 -1
  348. package/dist/speech.cjs +20 -18
  349. package/dist/speech.cjs.map +1 -1
  350. package/dist/speech.d.cts.map +1 -1
  351. package/dist/speech.d.ts.map +1 -1
  352. package/dist/speech.js +19 -17
  353. package/dist/speech.js.map +1 -1
  354. package/dist/sse-writer.cjs +7 -0
  355. package/dist/sse-writer.cjs.map +1 -1
  356. package/dist/sse-writer.d.cts.map +1 -1
  357. package/dist/sse-writer.d.ts.map +1 -1
  358. package/dist/sse-writer.js +7 -1
  359. package/dist/sse-writer.js.map +1 -1
  360. package/dist/stream-collapse.cjs +118 -11
  361. package/dist/stream-collapse.cjs.map +1 -1
  362. package/dist/stream-collapse.d.cts +10 -9
  363. package/dist/stream-collapse.d.cts.map +1 -1
  364. package/dist/stream-collapse.d.ts +10 -9
  365. package/dist/stream-collapse.d.ts.map +1 -1
  366. package/dist/stream-collapse.js +118 -11
  367. package/dist/stream-collapse.js.map +1 -1
  368. package/dist/transcription.cjs +12 -10
  369. package/dist/transcription.cjs.map +1 -1
  370. package/dist/transcription.d.cts.map +1 -1
  371. package/dist/transcription.d.ts.map +1 -1
  372. package/dist/transcription.js +11 -9
  373. package/dist/transcription.js.map +1 -1
  374. package/dist/types.cjs.map +1 -1
  375. package/dist/types.d.cts +62 -3
  376. package/dist/types.d.cts.map +1 -1
  377. package/dist/types.d.ts +62 -3
  378. package/dist/types.d.ts.map +1 -1
  379. package/dist/types.js.map +1 -1
  380. package/dist/vector-mock.cjs +3 -8
  381. package/dist/vector-mock.cjs.map +1 -1
  382. package/dist/vector-mock.d.cts.map +1 -1
  383. package/dist/vector-mock.d.ts.map +1 -1
  384. package/dist/vector-mock.js +3 -8
  385. package/dist/vector-mock.js.map +1 -1
  386. package/dist/vector-stores.cjs +2 -4
  387. package/dist/vector-stores.cjs.map +1 -1
  388. package/dist/vector-stores.d.cts.map +1 -1
  389. package/dist/vector-stores.d.ts.map +1 -1
  390. package/dist/vector-stores.js +2 -4
  391. package/dist/vector-stores.js.map +1 -1
  392. package/dist/veo-video.cjs +16 -14
  393. package/dist/veo-video.cjs.map +1 -1
  394. package/dist/veo-video.js +10 -8
  395. package/dist/veo-video.js.map +1 -1
  396. package/dist/video.cjs +21 -19
  397. package/dist/video.cjs.map +1 -1
  398. package/dist/video.d.cts.map +1 -1
  399. package/dist/video.d.ts.map +1 -1
  400. package/dist/video.js +20 -18
  401. package/dist/video.js.map +1 -1
  402. package/dist/ws-gemini-live.cjs +11 -3
  403. package/dist/ws-gemini-live.cjs.map +1 -1
  404. package/dist/ws-gemini-live.d.cts +0 -1
  405. package/dist/ws-gemini-live.d.cts.map +1 -1
  406. package/dist/ws-gemini-live.d.ts +0 -1
  407. package/dist/ws-gemini-live.d.ts.map +1 -1
  408. package/dist/ws-gemini-live.js +10 -2
  409. package/dist/ws-gemini-live.js.map +1 -1
  410. package/dist/ws-live.cjs +6 -5
  411. package/dist/ws-live.cjs.map +1 -1
  412. package/dist/ws-live.js +5 -4
  413. package/dist/ws-live.js.map +1 -1
  414. package/dist/ws-realtime.cjs +28 -54
  415. package/dist/ws-realtime.cjs.map +1 -1
  416. package/dist/ws-realtime.d.cts.map +1 -1
  417. package/dist/ws-realtime.d.ts.map +1 -1
  418. package/dist/ws-realtime.js +25 -51
  419. package/dist/ws-realtime.js.map +1 -1
  420. package/dist/ws-responses.cjs +8 -6
  421. package/dist/ws-responses.cjs.map +1 -1
  422. package/dist/ws-responses.js +7 -5
  423. package/dist/ws-responses.js.map +1 -1
  424. package/package.json +4 -1
  425. package/skills/write-fixtures/SKILL.md +25 -16
@@ -1 +1 @@
1
- {"version":3,"file":"elevenlabs-voice.cjs","names":["wouldProxyMiss","resolveStrictMode","flattenHeaders","isJsonObject","getContext","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","getTestId","selectFixtureForServing","applyChaosAsync","resolveResponse","isErrorResponse","serializeErrorResponse","isJSONResponse"],"sources":["../src/elevenlabs-voice.ts"],"sourcesContent":["import type http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RawJSONResponse,\n VoiceDesignResponse,\n} from \"./types.js\";\nimport {\n isErrorResponse,\n isJSONResponse,\n isJsonObject,\n serializeErrorResponse,\n flattenHeaders,\n getContext,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n wouldProxyMiss,\n} from \"./helpers.js\";\nimport { releaseOneShotError } from \"./fixture-loader.js\";\nimport { selectFixtureForServing } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport type { ProxyCapturedResponse, ProxyOptions } from \"./recorder.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaosAsync, type ChaosAsyncOutcome } from \"./chaos.js\";\n\n/**\n * ElevenLabs Voice Design record + replay: `POST /v1/text-to-voice/design`\n * (previews), `POST /v1/text-to-voice` (create a voice from a preview),\n * `GET /v1/voices/{voice_id}`, `DELETE /v1/voices/{voice_id}`.\n *\n * PROVENANCE OF EVERY WIRE FACT IN THIS FILE. Read this before changing a\n * field name, a status code, or a literal below.\n *\n * FIRSTHAND — observed by this repo against live `api.elevenlabs.io` on\n * 2026-09-15, with NO api key and with a deliberately invalid one. No\n * ElevenLabs api key is reachable from this machine (1Password holds an\n * `elevenlabs.io` web login, not a key), so only the pre-auth and auth layers\n * were ever exercised:\n * - `POST /v1/text-to-voice/design` with a >=20-char `voice_description` and\n * no credential -> 401\n * `{\"detail\":{\"type\":\"authentication_error\",\"code\":\"unauthorized\",\n * \"message\":\"Neither authorization header nor xi-api-key received, please\n * provide one.\",\"status\":\"needs_authorization\",\"request_id\":\"<hex>\"}}`.\n * Same envelope from `GET /v1/voices/{id}`; with a bogus `xi-api-key` the\n * message/status become `\"Invalid API key\"` / `\"invalid_api_key\"`.\n * - `voice_description` shorter than 20 chars -> 422 with a pydantic-style\n * `{\"detail\":[{\"type\":\"string_too_short\",\"loc\":[\"body\",\n * \"voice_description\"],...}]}`; `POST /v1/text-to-voice` reports missing\n * `voice_name` / `generated_voice_id` the same way. So the real service\n * validates the body BEFORE it authenticates. THIS HANDLER NOW ENFORCES\n * THAT 20-CODE-POINT MINIMUM on `POST /v1/text-to-voice/design`\n * (`VOICE_DESCRIPTION_MIN_LENGTH`) and answers with that same `422\n * {\"detail\":[...]}` envelope rather than aimock's house error shape — the\n * one response on this surface whose real shape was observed. The `msg`\n * STRING it fills in (\"String should have at least 20 characters\") is\n * AUTHORED: the observed body was recorded with `msg` elided, so only the\n * status, the `detail` array and the `loc`/`type` values are firsthand.\n * ORDER ON `POST /v1/text-to-voice/design`: missing-parameter 400 ->\n * fixture match (a registered short-description fixture still serves) ->\n * chaos -> strict refusal / record-mode proxy (so the REAL 422 is recorded\n * verbatim instead of pre-empted) -> this local 20-code-point 422 ->\n * no-match 404. `POST /v1/text-to-voice` follows the same order, with its\n * empty-string 422 in the same slot ahead of the synthesized voice.\n * The minimum is NOT enforced on `POST /v1/text-to-voice`, whose\n * `voice_description` bound has never been probed; that route rejects only\n * an EMPTY description, which is short under any `min_length >= 1`.\n * - `GET /v1/voices/aimock-nonexistent-voice` -> 400\n * `{\"detail\":{\"type\":\"invalid_request\",\"code\":\"bad_request\",\"message\":\"An\n * invalid ID has been received: ...\",\"status\":\"invalid_uid\",...}}`.\n * KNOWN DIVERGENCE, recorded rather than papered over: every real envelope\n * above is keyed `detail`, while this handler still emits aimock's house\n * `{ error: { message, type, code } }` shape — and aimock's own 400/404/503\n * codes — for EVERY failure except the `voice_description` minimum. That is\n * deliberate consistency with every other surface here, but it means those\n * ERROR envelopes are NOT wire-faithful to ElevenLabs. Making the whole\n * module speak `detail` is a FOLLOW-UP: it is a breaking change for every\n * existing consumer of this surface, and the shapes of the OTHER failures\n * (missing parameter, unknown voice, strict-mode refusal) have not been\n * observed, so they cannot be authored from the one case that has. A\n * RECORDED fixture is a second exception: it replays the upstream `detail`\n * body verbatim under the upstream status, so it is faithful by\n * construction.\n *\n * SECONDARY — the official client `@elevenlabs/elevenlabs-js`, pinned to the\n * exact published version `2.68.0` (`node_modules/@elevenlabs/elevenlabs-js/\n * package.json` -> `\"version\": \"2.68.0\"`). Every claim below was read out of\n * these files, at that version, and nowhere else:\n * - `api/resources/textToVoice/client/Client.d.ts` /`.js` — the routes, and\n * the `@throws` list for each (`UnprocessableEntityError` only);\n * - `api/resources/textToVoice/client/requests/VoiceDesignRequestModel.d.ts`\n * and `.../BodyCreateANewVoiceFromVoicePreviewV1TextToVoicePost.d.ts` —\n * the request bodies;\n * - `api/types/VoiceDesignPreviewResponse.d.ts`,\n * `api/types/VoicePreviewResponseModel.d.ts`, `api/types/Voice.d.ts`,\n * `api/types/HttpValidationError.d.ts`, `api/types/ValidationError.d.ts`,\n * `api/types/ValidationErrorLocItem.d.ts` — the response models;\n * - the matching `serialization/types/*.d.ts` `Raw` interfaces and\n * `serialization/types/*.js` schemas — the on-wire snake_case names and\n * each field's optional/nullable treatment;\n * - `api/errors/UnprocessableEntityError.js` (422) and\n * `api/errors/ConflictError.js` (409, never declared for these routes).\n * It pins:\n * - the four routes above, and that create-from-preview is `POST\n * /v1/text-to-voice` taking `voice_name`, `voice_description`,\n * `generated_voice_id`, optional `labels`;\n * - the design SUCCESS body `{ previews: [...], text }` where each preview is\n * `{ audio_base_64, generated_voice_id, media_type, duration_secs,\n * language? }` — `language` optional, the rest required;\n * - `model_id` values `eleven_multilingual_ttv_v2` / `eleven_ttv_v3`;\n * - the `Voice` object's wire fields. Every key `buildSyntheticVoice()`\n * emits (`voice_id`, `name`, `category`, `description`, `labels`,\n * `preview_url`, `available_for_tiers`, `settings`, `sharing`,\n * `high_quality_base_model_ids`, `samples`, `safety_control`,\n * `voice_verification` with its four inner keys, `permission_on_resource`,\n * `is_owner`, `is_legacy`, `is_mixed`) appears in `Voice.Raw` /\n * `VoiceVerificationResponse.Raw`, and `\"generated\"` is one of that SDK's\n * six `VoiceCategory` tokens;\n * - `DELETE /v1/voices/{id}` answering `{ status: \"ok\" }`.\n * ElevenLabs' own documentation site is a client-rendered SPA and was NOT\n * read; the SDK is the only document behind these shapes.\n *\n * UNVERIFIED / HAND-AUTHORED — name it, do not soften it:\n * - NO successful response from ElevenLabs has ever been observed by this\n * repo, for any of these four routes. There is no recorded fixture or tape\n * for this surface anywhere in the tree; the drift test in\n * `src/__tests__/drift/elevenlabs-voice.drift.ts` restates the same\n * hand-authored shape it checks, so it detects mock-vs-fixture divergence\n * only and can\n * never detect divergence from the real API.\n * - The VALUES `buildSyntheticVoice()` fills those fields with (`null`s, `[]`,\n * `is_owner: true`, `is_legacy`/`is_mixed` false, the zeroed\n * `voice_verification`) are aimock's choices for a just-created voice, not\n * anything observed. The SDK marks all of them optional and nullable, so\n * they are plausible, not confirmed.\n * - `voice_id === generated_voice_id` for a created voice. AUTHORED — see\n * `buildSyntheticVoice()`. The SDK relates the two nowhere; aimock picks\n * the alias so a caller can GET back what it just created. No code should\n * depend on it as a wire fact.\n * - Re-creating an already-created `generated_voice_id` overwrites and\n * answers 200. AUTHORED — see `rememberElevenLabsVoice()`. The real\n * behaviour is unknown and the SDK declares no `ConflictError` for the\n * route, so aimock warns through the module logger instead of inventing a\n * rejection.\n * - `Voice.Raw` carries further fields this handler never emits\n * (`fine_tuning`, `verified_languages`, `collection_ids`,\n * `created_at_unix`, `favorited_at_unix`, `is_bookmarked`,\n * `recording_quality`, `labelling_status`, `recording_quality_reason`). A\n * consumer reading them off a replayed voice gets `undefined`.\n * The standing rule still holds: THE MOCK NEVER AUTHORS A WIRE VALUE IT DID\n * NOT OBSERVE. This surface currently does, and this block is where that debt\n * is declared. Recording one real design + create round-trip against a funded\n * key would retire most of it.\n */\n\nconst VOICE_STORE_MAX = 10_000;\n\n/**\n * `POST /v1/text-to-voice/design` rejects a `voice_description` shorter than\n * this. FIRSTHAND: observed against live `api.elevenlabs.io` on 2026-09-15 (see\n * the PROVENANCE block above) — a sub-20-character description comes back 422\n * with a pydantic `string_too_short` entry, BEFORE the request is even\n * authenticated, so the bound is observable without a key.\n *\n * THE UNIT IS CODE POINTS, NOT UTF-16 CODE UNITS. The bound is pydantic's\n * `min_length`, which is `len(str)` in Python — a count of code points. A\n * JavaScript `String.length` counts UTF-16 code units instead, so it\n * DOUBLE-COUNTS every non-BMP character: a 19-emoji description is 19 code\n * points to the real service (422) and 38 `.length` to JavaScript (would have\n * passed here and then failed on the wire, which is the exact class of\n * divergence this mock exists to prevent). Measure with `[...s].length`, whose\n * string iterator yields code points. Grapheme clusters are deliberately NOT\n * the unit: Python does not count those either, so `Intl.Segmenter` would\n * re-introduce the same disagreement from the other side.\n */\nconst VOICE_DESCRIPTION_MIN_LENGTH = 20;\n\n/**\n * The real service answers body-validation failures with FastAPI/pydantic's\n * envelope: HTTP 422 and `{\"detail\":[{\"loc\":[...],\"msg\":...,\"type\":...}]}`.\n * The key set is pinned by `@elevenlabs/elevenlabs-js@2.68.0`\n * (`api/types/HttpValidationError.d.ts` -> `detail?: ValidationError[]`,\n * `api/types/ValidationError.d.ts` -> `{ loc, msg, type }`,\n * `serialization/types/ValidationError.d.ts` for the identical on-wire Raw),\n * and the status by `api/errors/UnprocessableEntityError.js`\n * (`statusCode: 422`), which is what that SDK throws for these routes.\n *\n * DELIBERATE DIVERGENCE FROM ITS SIBLINGS: every other validation failure in\n * this file answers aimock's house `400 {\"error\":{...}}`. This one does not,\n * because this is the only body-validation response on this surface whose real\n * shape this repo has actually observed. Aligning the rest is a follow-up, not\n * a licence to author the others' shapes from this one.\n */\nfunction writeValidationError(\n res: http.ServerResponse,\n loc: Array<string | number>,\n msg: string,\n type: string,\n): void {\n writeErrorResponse(res, 422, JSON.stringify({ detail: [{ loc, msg, type }] }));\n}\n\n/**\n * Voices created via POST /v1/text-to-voice, keyed by voice_id.\n *\n * MODULE-GLOBAL, deliberately: it is the same shape every other bounded store\n * here uses (`FalJobMap` in `./fal-audio.ts`, `FalQueueStateMap` in `./fal.ts`,\n * and the four video job maps), so one process shares one voice store across\n * every `LLMock` instance. `performFullReset` (`./server.ts`) clears it\n * alongside those siblings, which is what `POST /__aimock/reset` and\n * `LLMock.reset()` both route through. `stop()` does NOT clear it — no sibling\n * store is cleared on `stop()` either; reset is the isolation barrier, not\n * server teardown. A test file that creates voices must therefore reset (or\n * call `clearElevenLabsVoices()`) between tests, or a voice saved by one test\n * is still visible to the next.\n */\nconst elevenLabsVoices = new Map<string, Record<string, unknown>>();\n\nexport function clearElevenLabsVoices(): void {\n elevenLabsVoices.clear();\n}\n\n/**\n * Store a created voice under its own `voice_id` — the id the client sees in\n * the body we are about to write, so a follow-up `GET /v1/voices/{id}` by that\n * id hits.\n *\n * Returns `false` when the body carries no string `voice_id` and nothing was\n * stored, so the caller can say so instead of dropping the voice silently.\n *\n * AUTHORED: a second create for a `voice_id` already in the store OVERWRITES\n * the first, and still answers 200. Whether the real\n * `POST /v1/text-to-voice` accepts an already-consumed `generated_voice_id` is\n * UNKNOWN to this repo — no successful create has ever been observed, and\n * `@elevenlabs/elevenlabs-js@2.68.0` gives no signal either way: its\n * `api/resources/textToVoice/client/Client.d.ts` declares only\n * `@throws UnprocessableEntityError` for `create`, and never `ConflictError`\n * (`api/errors/ConflictError.js`), so authoring a 409 here would be inventing a\n * status nothing supports. Overwrite-and-200 is aimock's choice; it is made\n * AUDIBLE via `logger.warn` rather than silent, so a test that overwrites a\n * voice by accident can see it.\n *\n * EVICTION IS FIFO AND SILENT, by insertion order — the same rule every\n * sibling store applies (see `FalJobMap.set`, `src/fal-audio.ts:107-116`, and\n * the identical block in `fal.ts` / `openrouter-video.ts` / `grok-video.ts` /\n * `byteplus-video.ts`). Once the map exceeds `VOICE_STORE_MAX`, the\n * oldest-inserted entries are deleted with no log line and no error: a later\n * `GET /v1/voices/{id}` for an evicted voice is indistinguishable from one for\n * a voice that never existed (404, or 503 under strict). It is NOT LRU —\n * reading a voice does not refresh it, and re-saving an existing `voice_id`\n * keeps its ORIGINAL insertion slot, because `Map.set` on a present key does\n * not reorder. The cap exists to bound a long-lived mock process, not to model\n * anything ElevenLabs does; at 10k voices per process no test approaches it.\n */\nexport function rememberElevenLabsVoice(voice: Record<string, unknown>, logger?: Logger): boolean {\n const voiceId = typeof voice.voice_id === \"string\" ? voice.voice_id : undefined;\n if (!voiceId) return false;\n if (elevenLabsVoices.has(voiceId)) {\n logger?.warn(\n `ElevenLabs voice '${voiceId}' already existed and was overwritten; aimock does not know whether the real API rejects a reused generated_voice_id`,\n );\n }\n elevenLabsVoices.set(voiceId, voice);\n if (elevenLabsVoices.size > VOICE_STORE_MAX) {\n const excess = elevenLabsVoices.size - VOICE_STORE_MAX;\n const iter = elevenLabsVoices.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) elevenLabsVoices.delete(next.value);\n }\n }\n return true;\n}\n\n/**\n * The status a recorded `{ json, status }` replay must answer with.\n *\n * `proxyAndRecord` persists the upstream status alongside the verbatim body for\n * this surface (`src/recorder.ts:901` — `{ json: parsedResponse, status:\n * upstreamStatus }`), and `RawJSONResponse.status` (`src/types.ts:476`) is the\n * field it lands in. Replaying every such fixture as 200 turns every recorded\n * ElevenLabs error — all of which carry a `detail` body, not aimock's house\n * error envelope, so `isErrorResponse()` never claims them — into a fabricated\n * success. `src/fal-audio.ts:1061` reads the same field the same way.\n *\n * Only `status` round-trips: that recorder branch stores no headers for a\n * verbatim-JSON recording, so there is no recorded header to honour here.\n */\nfunction replayStatus(response: RawJSONResponse): number {\n return response.status ?? 200;\n}\n\nexport function voiceDesignToJson(response: VoiceDesignResponse): RawJSONResponse {\n const previews = response.previews.map((preview, index) => {\n const entry: Record<string, unknown> = {\n generated_voice_id: preview.generated_voice_id || `aimock-preview-${index}`,\n audio_base_64: preview.audio_base_64 ?? \"\",\n media_type: preview.media_type ?? \"audio/mpeg\",\n duration_secs: preview.duration_secs ?? 0,\n };\n // `language` is OMITTED, never null, when the caller did not supply one:\n // the SDK models it `language?: string` (absent-or-string, never null) in\n // `api/types/VoicePreviewResponseModel.d.ts`, and its runtime schema\n // (`serialization/types/VoicePreviewResponseModel.js`,\n // `core.serialization.string().optional()`) parses an incoming `null` away\n // to `undefined`. A `null` here is therefore a value no SDK consumer can\n // ever observe from the real API.\n if (typeof preview.language === \"string\") entry.language = preview.language;\n return entry;\n });\n return {\n json: {\n previews,\n text: response.text ?? \"\",\n },\n };\n}\n\nfunction writeJson(res: http.ServerResponse, status: number, payload: unknown): void {\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(payload));\n}\n\n/**\n * The journal `source` for a chaos fault on a request no fixture answers — the\n * same rule `src/server.ts` applies, via the shared {@link wouldProxyMiss}:\n * the miss is aimock's own answer, \"internal\", unless record mode has an\n * ElevenLabs upstream to forward it to AND strict mode (server default or the\n * per-request `X-AIMock-Strict` header) is not refusing it first. Both halves\n * used to be hardcoded — design/create said \"proxy\" with no record config at\n * all, and the slot routes said \"internal\" while record mode was about to\n * proxy the very same miss.\n */\nfunction noFixtureSource(\n defaults: HandlerDefaults,\n headers: http.IncomingHttpHeaders,\n): \"proxy\" | \"internal\" {\n return wouldProxyMiss(resolveStrictMode(defaults.strict, headers), defaults.record, \"elevenlabs\")\n ? \"proxy\"\n : \"internal\";\n}\n\nfunction parseJsonObject(\n body: string,\n req: http.IncomingMessage,\n res: http.ServerResponse,\n journal: Journal,\n path: string,\n method: string,\n): Record<string, unknown> | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: null,\n response: { status: 400, fixture: null },\n });\n return null;\n }\n\n if (!isJsonObject(parsed)) {\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: null,\n response: { status: 400, fixture: null },\n });\n return null;\n }\n\n return parsed;\n}\n\nfunction buildSyntheticReq(\n parsed: Record<string, unknown>,\n matchText: string,\n endpoint: \"elevenlabs-voice-design\" | \"elevenlabs-voice\",\n modelFallback: string,\n req: http.IncomingMessage,\n): ChatCompletionRequest {\n return {\n model: typeof parsed.model_id === \"string\" ? parsed.model_id : modelFallback,\n messages: [{ role: \"user\", content: matchText }],\n _endpointType: endpoint,\n _context: getContext(req),\n };\n}\n\nasync function missPath(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n path: string,\n method: string,\n body: string,\n skippedBySequenceOrTurn: number,\n proxyOptions?: ProxyOptions,\n): Promise<\"handled\" | \"miss\"> {\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 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 journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n return \"handled\";\n }\n\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"elevenlabs\",\n req.url ?? path,\n fixtures,\n defaults,\n body,\n proxyOptions,\n );\n if (outcome === \"handled_by_hook\") return \"handled\";\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return \"handled\";\n }\n }\n\n return \"miss\";\n}\n\nfunction writeNoMatch(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n defaults: HandlerDefaults,\n journal: Journal,\n path: string,\n method: string,\n): void {\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 journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n}\n\n/**\n * Builds the `beforeWriteResponse` observer for the voice routes. It NEVER\n * handles the response — it returns `false` so `proxyAndRecord` relays the\n * upstream bytes and the caller still journals the `\"relayed\"` outcome — it\n * only copies a successfully proxied voice object into the in-process store, so\n * a follow-up `GET /v1/voices/{id}` answers from memory exactly as it does\n * after an in-process create instead of hitting upstream a second time.\n *\n * It is a FACTORY OVER THE LOGGER, not a bare function, because the store has\n * exactly two things worth saying and neither can be said without one: the\n * AUTHORED overwrite of an already-stored `voice_id`, which\n * `rememberElevenLabsVoice` documents as made AUDIBLE rather than silent, and a\n * 2xx voice body carrying no string `voice_id`, which it reports by returning\n * `false`. Both are already reported on the in-process and fixture-replay paths\n * (see the `create` handler's `defaults.logger` calls); passing the observer a\n * bare function dropped both on EVERY proxy path, which is the one path where\n * the body came from somewhere aimock does not control.\n */\nfunction rememberProxiedVoice(logger: Logger): (captured: ProxyCapturedResponse) => false {\n return (captured: ProxyCapturedResponse): false => {\n if (captured.status < 200 || captured.status >= 300) return false;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(captured.body.toString(\"utf-8\"));\n } catch (err) {\n // ONLY a malformed upstream body is swallowed: a non-JSON 2xx carries no\n // voice to remember and the relay is unaffected. `JSON.parse` throws\n // `SyntaxError` and nothing else, so ANY other error came from a genuine\n // defect in this hook and must propagate — `proxyAndRecord` turns a\n // rejected hook into `beforeWriteResponse hook failed for <provider>`\n // (`src/recorder.ts`), which is a visible failure instead of a voice that\n // vanished under the label \"upstream sent HTML\".\n if (err instanceof SyntaxError) return false;\n throw err;\n }\n\n if (!isJsonObject(parsed)) return false;\n if (!rememberElevenLabsVoice(parsed, logger)) {\n logger.warn(\n `ElevenLabs relayed a ${captured.status} voice body from upstream that carries no ` +\n `string \\`voice_id\\` — the voice was NOT stored, so a follow-up ` +\n `GET /v1/voices/{id} will miss and re-hit upstream instead of replaying from ` +\n `memory. Upstream body: ${captured.body.toString(\"utf-8\").slice(0, 200)}`,\n );\n }\n return false;\n };\n}\n\nfunction buildSyntheticVoice(parsed: Record<string, unknown>): Record<string, unknown> {\n // AUTHORED: `voice_id === generated_voice_id`. Nothing in\n // `@elevenlabs/elevenlabs-js@2.68.0` says the created voice keeps the\n // preview's id — `api/resources/textToVoice/client/requests/\n // BodyCreateANewVoiceFromVoicePreviewV1TextToVoicePost.d.ts` documents\n // `generatedVoiceId` only as \"the generated_voice_id to create\", and the\n // returned `api/types/Voice.d.ts` relates `voiceId` to it nowhere. This is\n // aimock's choice so that a caller can GET the voice it just created; treat\n // it as a mock convention, not a wire fact, and do not build logic on it.\n const generatedVoiceId = String(parsed.generated_voice_id);\n const labels =\n parsed.labels && typeof parsed.labels === \"object\" && !Array.isArray(parsed.labels)\n ? (parsed.labels as Record<string, unknown>)\n : {};\n return {\n voice_id: generatedVoiceId,\n name: String(parsed.voice_name),\n category: \"generated\",\n description: String(parsed.voice_description),\n labels,\n preview_url: null,\n available_for_tiers: [],\n settings: null,\n sharing: null,\n high_quality_base_model_ids: [],\n samples: null,\n safety_control: null,\n voice_verification: {\n requires_verification: false,\n is_verified: false,\n verification_failures: [],\n verification_attempts_count: 0,\n },\n permission_on_resource: null,\n is_owner: true,\n is_legacy: false,\n is_mixed: false,\n };\n}\n\nexport async function handleElevenLabsVoiceDesign(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n): Promise<void> {\n const path = req.url ?? \"/v1/text-to-voice/design\";\n const method = req.method ?? \"POST\";\n\n const parsed = parseJsonObject(body, req, res, journal, path, method);\n if (!parsed) return;\n\n // ONLY a non-string (or absent) `voice_description` is MISSING. An empty\n // string is PRESENT AND TOO SHORT: a truthiness test here folded `\"\"` into\n // the missing case and answered aimock's house 400, while the real service\n // answers the same firsthand-observed 422 `string_too_short` it answers for\n // every other sub-minimum description (PROVENANCE block above). `\"\"` is the\n // shortest such description there is, so it is the one case where the house\n // 400 and the observed 422 disagree most visibly.\n const description =\n typeof parsed.voice_description === \"string\" ? parsed.voice_description : undefined;\n\n const syntheticReq = buildSyntheticReq(\n parsed,\n description ?? \"\",\n \"elevenlabs-voice-design\",\n \"eleven_multilingual_ttv_v2\",\n req,\n );\n\n if (description === undefined) {\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Missing required parameter: 'voice_description'\",\n type: \"invalid_request_error\",\n },\n }),\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 400, fixture: null },\n });\n return;\n }\n\n const testId = getTestId(req);\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n matchCounts,\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n service: \"elevenlabs-voice\",\n },\n fixture ? \"fixture\" : noFixtureSource(defaults, req.headers),\n defaults.registry,\n defaults.logger,\n )\n ) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n if (!fixture) {\n const outcome = await missPath(\n req,\n res,\n syntheticReq,\n fixtures,\n defaults,\n journal,\n path,\n method,\n body,\n skippedBySequenceOrTurn,\n );\n if (outcome === \"handled\") return;\n\n // THE LOCAL MINIMUM IS THE LAST RESORT, NOT THE FIRST GATE. It used to run\n // ahead of fixture matching and ahead of `missPath`, which meant a\n // registered short-description fixture could never be served and — worse —\n // record mode answered aimock's own AUTHORED `msg` instead of forwarding,\n // so the one response on this surface whose real shape was observed could\n // never be recorded. `missPath` has already refused (strict) or proxied and\n // recorded (record) by the time we get here, so this only fires when\n // nothing else can answer.\n // Code points, not `.length`: see `VOICE_DESCRIPTION_MIN_LENGTH`.\n if ([...description].length < VOICE_DESCRIPTION_MIN_LENGTH) {\n writeValidationError(\n res,\n [\"body\", \"voice_description\"],\n `String should have at least ${VOICE_DESCRIPTION_MIN_LENGTH} characters`,\n \"string_too_short\",\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 422, fixture: null },\n });\n return;\n }\n\n writeNoMatch(req, res, syntheticReq, defaults, journal, path, method);\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n return;\n }\n\n if (!isJSONResponse(response)) {\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response is not a JSON type for voice design\",\n type: \"server_error\",\n },\n }),\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n return;\n }\n\n const status = replayStatus(response);\n writeJson(res, status, response.json);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n}\n\nexport async function handleElevenLabsVoiceCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n): Promise<void> {\n const path = req.url ?? \"/v1/text-to-voice\";\n const method = req.method ?? \"POST\";\n\n const parsed = parseJsonObject(body, req, res, journal, path, method);\n if (!parsed) return;\n\n // ONLY a non-string (or absent) field is MISSING here, for the same reason\n // the design handler stopped folding `\"\"` into the missing case: an empty\n // string is PRESENT AND TOO SHORT, and the house 400 said the caller had\n // omitted a field it had actually sent. `voice_name` and `generated_voice_id`\n // follow the same rule as `voice_description`.\n const missing =\n typeof parsed.voice_name !== \"string\"\n ? \"voice_name\"\n : typeof parsed.voice_description !== \"string\"\n ? \"voice_description\"\n : typeof parsed.generated_voice_id !== \"string\"\n ? \"generated_voice_id\"\n : null;\n const tooShort =\n parsed.voice_name === \"\"\n ? \"voice_name\"\n : parsed.voice_description === \"\"\n ? \"voice_description\"\n : parsed.generated_voice_id === \"\"\n ? \"generated_voice_id\"\n : null;\n\n const syntheticReq = buildSyntheticReq(\n parsed,\n typeof parsed.generated_voice_id === \"string\" ? parsed.generated_voice_id : \"\",\n \"elevenlabs-voice\",\n \"eleven_multilingual_ttv_v2\",\n req,\n );\n\n if (missing) {\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Missing required parameter: '${missing}'`,\n type: \"invalid_request_error\",\n },\n }),\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 400, fixture: null },\n });\n return;\n }\n\n const testId = getTestId(req);\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n matchCounts,\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n service: \"elevenlabs-voice\",\n },\n fixture ? \"fixture\" : noFixtureSource(defaults, req.headers),\n defaults.registry,\n defaults.logger,\n )\n ) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n if (!fixture) {\n const outcome = await missPath(\n req,\n res,\n syntheticReq,\n fixtures,\n defaults,\n journal,\n path,\n method,\n body,\n skippedBySequenceOrTurn,\n { beforeWriteResponse: rememberProxiedVoice(defaults.logger) },\n );\n if (outcome === \"handled\") return;\n\n // THE LOCAL MINIMUM IS THE LAST RESORT, NOT THE FIRST GATE — same position\n // as the design handler's, for the same reasons: a registered fixture,\n // chaos, strict and the record proxy all get their turn first, so record\n // mode forwards and records the REAL 422 instead of authoring one.\n // An EMPTY string is the one length this route can answer without\n // authoring an unobserved bound: it is under ANY `min_length >= 1`, so\n // `string_too_short` holds whatever the real minimum on\n // `POST /v1/text-to-voice` turns out to be. THE 20-CODE-POINT BOUND IS\n // DELIBERATELY NOT ENFORCED HERE — that minimum was observed on\n // `/v1/text-to-voice/design` only (PROVENANCE block), and this route's\n // bounds have never been probed. The `msg` string is AUTHORED, like its\n // design sibling's.\n if (tooShort) {\n writeValidationError(\n res,\n [\"body\", tooShort],\n \"String should have at least 1 character\",\n \"string_too_short\",\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 422, fixture: null },\n });\n return;\n }\n\n const voice = buildSyntheticVoice(parsed);\n rememberElevenLabsVoice(voice, defaults.logger);\n writeJson(res, 200, voice);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n return;\n }\n\n if (!isJSONResponse(response)) {\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response is not a JSON type for voice create\",\n type: \"server_error\",\n },\n }),\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n return;\n }\n\n const status = replayStatus(response);\n\n // Store the voice under the `voice_id` the CLIENT is about to see (this same\n // body), so its follow-up `GET /v1/voices/{id}` by the id it received hits.\n // Only a 2xx replay creates a voice: a recorded 4xx/5xx `detail` body carries\n // no voice, and remembering it would resurrect the fabricated-success bug\n // from the other side.\n //\n // A 2xx body with no string `voice_id` is an AUTHORING error, not an\n // unservable fixture: the body is still what the recording says the client\n // gets, so serve it and name the defect, the way `src/byteplus-video.ts:469`\n // and `:479` name an envelope that is servable but will strand the client\n // later. (The 500 `server_error` treatment a few lines above — and in\n // `src/fal-audio.ts:1071` — is reserved for a response that CANNOT be served\n // at all, which this one can.)\n if (status >= 200 && status < 300) {\n const remembered =\n isJsonObject(response.json) && rememberElevenLabsVoice(response.json, defaults.logger);\n if (!remembered) {\n defaults.logger.warn(\n `ElevenLabs voice create fixture matched generated_voice_id ` +\n `\"${String(parsed.generated_voice_id)}\" but its json carries no string \\`voice_id\\` — ` +\n `the voice was NOT stored, so a follow-up GET /v1/voices/{id} will 404 with a ` +\n `\"voice not found\" message that points at the id instead of at the fixture. Add a ` +\n `string voice_id to the fixture's json`,\n );\n }\n }\n\n writeJson(res, status, response.json);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n}\n\n/**\n * The synthetic request the slot routes (GET/DELETE /v1/voices/{id}) match and\n * journal on. The voice id stands in for the user message so the entry carries\n * the one thing that identifies the request.\n *\n * The endpoint type is the CALLER's, not a constant: ONE ROUTE, ONE ENDPOINT\n * TYPE. GET declares \"elevenlabs-voice-get\" and DELETE declares\n * \"elevenlabs-voice-delete\", so neither route's fixtures can ever be confused\n * with the create route's (\"elevenlabs-voice\") — all three would otherwise\n * carry the same voice id as their match text and answer for one another.\n */\nfunction buildVoiceSlotReq(\n req: http.IncomingMessage,\n voiceId: string,\n endpointType: \"elevenlabs-voice-get\" | \"elevenlabs-voice-delete\",\n): ChatCompletionRequest {\n return {\n model: \"eleven_multilingual_ttv_v2\",\n messages: [{ role: \"user\", content: voiceId }],\n _endpointType: endpointType,\n _context: getContext(req),\n };\n}\n\n/**\n * Gate a slot route on chaos exactly as {@link handleElevenLabsVoiceDesign} and\n * {@link handleElevenLabsVoiceCreate} gate theirs: the same `applyChaosAsync`\n * call, awaited BEFORE anything is served, so a configured latency/drop/429\n * applies to a store hit as well as to a miss. It is called AFTER the fixture\n * match and is handed the fixture that matched, because chaos can be authored\n * ON a fixture (`Fixture.chaos`, folded in by `chaos.ts`): passing null here\n * dropped a fixture-level `chaos` block on the floor for these two routes\n * while design/create honoured the identical block. The journal source follows\n * the same fixture/no-fixture split design/create use, via the same\n * {@link noFixtureSource}: \"internal\" when this process answers the miss,\n * \"proxy\" when record mode has an ElevenLabs upstream and would forward it.\n *\n * `applyChaosAsync` reads the per-testId chaos scope off `req.url`, which is\n * why the raw url — not the fallback `path` — is what it is handed, matching\n * design/create. The context carries `service: \"elevenlabs-voice\"` like every\n * other journal write in this module, so a faulted request stays selectable by\n * `GET /__aimock/journal?service=elevenlabs-voice`.\n */\nasync function gateVoiceSlotChaos(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n fixture: Fixture | null,\n defaults: HandlerDefaults,\n journal: Journal,\n path: string,\n method: string,\n): Promise<ChaosAsyncOutcome> {\n return await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n service: \"elevenlabs-voice\",\n },\n fixture ? \"fixture\" : noFixtureSource(defaults, req.headers),\n defaults.registry,\n defaults.logger,\n );\n}\n\n/**\n * Refuse a slot-route miss under strict mode, mirroring {@link missPath}: the\n * refusal is LOGGED through the shared `strictNoMatchLogLine` and the client\n * message names strict mode, so a strict 503 is never mistaken for the route's\n * own 404.\n *\n * `skippedBySequenceOrTurn` is the count `selectFixtureForServing` handed the\n * caller. Both slot routes are fixture routes now, so a candidate fixture CAN\n * be skipped by sequence/turn state here exactly as on create — hardcoding 0\n * told every such refusal \"no fixture matched\" when the truth was \"a fixture\n * matched the content and was held back by its sequenceIndex\". When the count\n * is non-zero the client message switches to the shared\n * {@link strictNoMatchMessage} wording that names it; otherwise the\n * voice-specific message is kept, since naming the id is the more useful thing\n * to say when nothing matched at all.\n */\nfunction writeVoiceSlotStrictRefusal(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n defaults: HandlerDefaults,\n journal: Journal,\n path: string,\n method: string,\n voiceId: string,\n skippedBySequenceOrTurn: number,\n): void {\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message:\n skippedBySequenceOrTurn > 0\n ? strictNoMatchMessage(skippedBySequenceOrTurn)\n : `Strict mode: voice '${voiceId}' not found`,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n}\n\n/**\n * Proxy a slot-route miss upstream in record mode. Returns \"handled\" when the\n * upstream answer was relayed (or a hook took over), \"miss\" when no upstream is\n * configured for the provider — `proxyAndRecord` already warns on that path, so\n * the caller falls through to its own miss answer.\n */\nasync function proxyVoiceSlotMiss(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n defaults: HandlerDefaults,\n journal: Journal,\n path: string,\n method: string,\n fixtures: Fixture[] = [],\n proxyOptions?: ProxyOptions,\n): Promise<\"handled\" | \"miss\"> {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"elevenlabs\",\n req.url ?? path,\n fixtures,\n defaults,\n \"\",\n proxyOptions,\n );\n if (outcome === \"handled_by_hook\") return \"handled\";\n if (outcome === \"not_configured\") return \"miss\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return \"handled\";\n}\n\n/**\n * Replay a fixture that matched on a slot route (`GET`/`DELETE /v1/voices/{id}`).\n *\n * Both slot routes replay identically, so the body lives here once: resolve,\n * relay an error envelope under its own status, refuse a non-JSON response,\n * and otherwise write the recorded JSON under {@link replayStatus} — the\n * UPSTREAM status the recorder persisted, not a hardcoded 200. GET used to\n * hardcode 200, which turned every recorded ElevenLabs failure (a `detail`\n * body that `isErrorResponse()` never claims) into a fabricated success.\n *\n * Returns the status actually written, or `null` when the fixture could not be\n * served as a success (an error envelope was relayed, or the response was not\n * JSON at all). DELETE uses that to decide whether the local store entry is\n * gone.\n */\nasync function replayVoiceSlotFixture(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n fixture: Fixture,\n journal: Journal,\n path: string,\n method: string,\n routeLabel: string,\n): Promise<number | null> {\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n return null;\n }\n\n if (!isJSONResponse(response)) {\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: `Fixture response is not a JSON type for ${routeLabel}`,\n type: \"server_error\",\n },\n }),\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n return null;\n }\n\n const status = replayStatus(response);\n writeJson(res, status, response.json);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n return status;\n}\n\n/**\n * FIXTURE BEFORE STORE, on both slot routes.\n *\n * A registered or recorded fixture is explicit authoring intent and wins over\n * the in-process voice store, which is a replay CONVENIENCE populated as a\n * side effect of create. That is the order every other replay route in this\n * repo uses — fixture match first, internal state only on a miss — and without\n * it a fixture authored specifically for `GET /v1/voices/{id}` (a recorded 401,\n * say) was silently unreachable for any id that create had already stored.\n *\n * The shadowing is AUDIBLE rather than silent: when a fixture wins over a\n * present store entry this logs at debug, so a test whose store entry stopped\n * being served can see why.\n */\nfunction logVoiceSlotShadow(\n defaults: HandlerDefaults,\n method: string,\n path: string,\n voiceId: string,\n): void {\n defaults.logger.debug(\n `ElevenLabs ${method} ${path}: a matching fixture shadows the in-process voice ` +\n `store entry for '${voiceId}' — the fixture is being served`,\n );\n}\n\nexport async function handleElevenLabsVoiceGet(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n voiceId: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n): Promise<void> {\n const path = req.url ?? `/v1/voices/${voiceId}`;\n const method = req.method ?? \"GET\";\n\n // DISTINCT endpoint type from the create route on purpose. GET used to\n // declare \"elevenlabs-voice\", the same type `POST /v1/text-to-voice`\n // declares, with the voice_id as the match text on both sides — so a fixture\n // recorded on GET became a first-class candidate for create (and hijacked\n // it), while create's own fixtures could be handed to GET. One route, one\n // endpoint type; `src/router.ts` and `src/recorder.ts` both know this name.\n const syntheticReq = buildVoiceSlotReq(req, voiceId, \"elevenlabs-voice-get\");\n\n // GET is a first-class replay route, exactly like design and create: a\n // registered or previously recorded fixture serves it. Without this the\n // route could only ever answer from the in-process store, so nothing\n // recorded to disk was reachable on a later run. See\n // {@link logVoiceSlotShadow} for why the fixture is consulted BEFORE the\n // store. The match runs BEFORE the chaos gate — and the count is incremented\n // before it, as design/create do — so a fixture-level `chaos` block reaches\n // the gate; see {@link gateVoiceSlotChaos}.\n const testId = getTestId(req);\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n matchCounts,\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (await gateVoiceSlotChaos(req, res, syntheticReq, fixture, defaults, journal, path, method)) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n const stored = elevenLabsVoices.get(voiceId);\n\n if (fixture) {\n if (stored) logVoiceSlotShadow(defaults, method, path, voiceId);\n await replayVoiceSlotFixture(\n req,\n res,\n syntheticReq,\n fixture,\n journal,\n path,\n method,\n \"voice get\",\n );\n return;\n }\n\n const serveStored = (voice: Record<string, unknown>): void => {\n writeJson(res, 200, voice);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n };\n\n // IN RECORD MODE THE LOCAL STORE IS NOT AUTHORITATIVE, the same guard DELETE\n // carries. Answering a locally-known voice from memory kept the GET out of\n // the tape entirely — a proxied create left the voice in the store, so no\n // `elevenlabs-voice-get` fixture was ever recorded and the replay run, which\n // has only the tape, 404'd on the GET that \"worked\" while recording.\n if (stored && !defaults.record) {\n serveStored(stored);\n return;\n }\n\n // STRICT BEFORE RECORD, the precedence missPath() uses. A strict server\n // refuses the miss outright; it must not silently proxy it upstream.\n if (resolveStrictMode(defaults.strict, req.headers)) {\n writeVoiceSlotStrictRefusal(\n req,\n res,\n syntheticReq,\n defaults,\n journal,\n path,\n method,\n voiceId,\n skippedBySequenceOrTurn,\n );\n return;\n }\n\n if (defaults.record) {\n // The REAL fixtures array, not a throwaway `[]`: `persistFixture`\n // (`src/recorder.ts`) pushes the freshly recorded fixture into whatever\n // array it is handed, so passing a discarded literal made the live server\n // forget what it had just recorded and re-hit upstream on every\n // subsequent GET.\n const outcome = await proxyVoiceSlotMiss(\n req,\n res,\n syntheticReq,\n defaults,\n journal,\n path,\n method,\n fixtures,\n { beforeWriteResponse: rememberProxiedVoice(defaults.logger) },\n );\n if (outcome === \"handled\") return;\n // Record mode is on but NO ElevenLabs upstream is configured (`--record`\n // aimed at another provider): the proxy cannot forward, and the create\n // that handed this id out was synthesized locally on that same path. The\n // store is the only thing that knows the voice, so it answers.\n if (stored) {\n serveStored(stored);\n return;\n }\n }\n\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: `Voice '${voiceId}' not found`,\n type: \"invalid_request_error\",\n code: \"voice_not_found\",\n },\n }),\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 404, fixture: null, ...strictOverrideField(defaults.strict, req.headers) },\n });\n}\n\n/**\n * DELETE /v1/voices/{voice_id}. A first-class record/replay route, exactly\n * like GET: chaos, then a fixture match, then the in-process store, then\n * strict, then record, then the lenient answer.\n *\n * The success body is `{ status: \"ok\" }` — SECONDARY, from\n * `@elevenlabs/elevenlabs-js@2.68.0`: `voices.delete()` parses the response as\n * `DeleteVoiceResponseModel`, whose only wire field is `status: string`, and\n * whose doc comment reads \"The status of the voice deletion request. If the\n * request was successful, the status will be 'ok'.\" In record mode nothing is\n * authored at all — the upstream's own response is relayed AND recorded.\n *\n * IN RECORD MODE THE LOCAL STORE IS NOT AUTHORITATIVE. A locally-known voice\n * used to be deleted from the map and answered 200 without the request ever\n * leaving the process, so the real voice leaked upstream and the tape got a\n * hole exactly where the delete belonged. Record mode forwards regardless of\n * what the store knows; the store is a replay convenience, not a claim about\n * what exists upstream. It still evicts ONLY on a 2xx from upstream, the same\n * rule the fixture-replay branch applies: a refused delete must not lose the\n * local voice.\n *\n * A miss on a LENIENT server stays an idempotent 200: deleting an id that was\n * never saved is not an error on this surface.\n */\nexport async function handleElevenLabsVoiceDelete(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n voiceId: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n): Promise<void> {\n const path = req.url ?? `/v1/voices/${voiceId}`;\n const method = req.method ?? \"DELETE\";\n\n // Its OWN endpoint type, for the same reason GET has one: DELETE used to\n // declare \"elevenlabs-voice\" with the voice id as its match text — byte for\n // byte what `POST /v1/text-to-voice` declares — so a recorded DELETE\n // (`{\"status\":\"ok\"}`) was a first-class candidate for create and hijacked it\n // on the next run.\n const syntheticReq = buildVoiceSlotReq(req, voiceId, \"elevenlabs-voice-delete\");\n\n // Match (and count) BEFORE the chaos gate, exactly as GET and design/create\n // do, so a fixture-level `chaos` block reaches the gate.\n const testId = getTestId(req);\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n matchCounts,\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (await gateVoiceSlotChaos(req, res, syntheticReq, fixture, defaults, journal, path, method)) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n const stored = elevenLabsVoices.has(voiceId);\n\n if (fixture) {\n if (stored) logVoiceSlotShadow(defaults, method, path, voiceId);\n const status = await replayVoiceSlotFixture(\n req,\n res,\n syntheticReq,\n fixture,\n journal,\n path,\n method,\n \"voice delete\",\n );\n // Only a 2xx replay says the voice is gone. A replayed 4xx/5xx is the\n // recording saying the delete did NOT happen, so the local entry stays.\n if (status !== null && status >= 200 && status < 300) elevenLabsVoices.delete(voiceId);\n return;\n }\n\n if (stored && !defaults.record) {\n elevenLabsVoices.delete(voiceId);\n writeJson(res, 200, { status: \"ok\" });\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: {\n status: 200,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n return;\n }\n\n // STRICT BEFORE RECORD, the precedence missPath() and GET both use.\n if (resolveStrictMode(defaults.strict, req.headers)) {\n writeVoiceSlotStrictRefusal(\n req,\n res,\n syntheticReq,\n defaults,\n journal,\n path,\n method,\n voiceId,\n skippedBySequenceOrTurn,\n );\n return;\n }\n\n if (defaults.record) {\n // Evict ONLY on a 2xx from upstream, the same rule the fixture-replay\n // branch above applies: an upstream 4xx/5xx is upstream saying the delete\n // did NOT happen, and dropping the entry anyway lost a local voice to a\n // refusal. `beforeWriteResponse` observes the captured upstream status and\n // returns `false`, so `proxyAndRecord` still relays the bytes and the\n // caller still journals the `\"relayed\"` outcome.\n // The REAL fixtures array, for the same reason GET passes it: the\n // throwaway `[]` this used to pass meant every recorded DELETE was\n // discarded on the spot, never replayed, and every repeat re-hit upstream.\n const outcome = await proxyVoiceSlotMiss(\n req,\n res,\n syntheticReq,\n defaults,\n journal,\n path,\n method,\n fixtures,\n {\n beforeWriteResponse: (captured): false => {\n if (captured.status >= 200 && captured.status < 300) {\n elevenLabsVoices.delete(voiceId);\n }\n return false;\n },\n },\n );\n if (outcome === \"handled\") return;\n }\n\n elevenLabsVoices.delete(voiceId);\n writeJson(res, 200, { status: \"ok\" });\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 200, fixture: null, ...strictOverrideField(defaults.strict, req.headers) },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"elevenlabs-voice.cjs","names":["wouldProxyMiss","resolveRequestDecisions","renderError","flattenHeaders","isJsonObject","getContext","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","getTestId","selectFixtureForServing","applyChaosAsync","resolveResponse","isErrorResponse","isJSONResponse"],"sources":["../src/elevenlabs-voice.ts"],"sourcesContent":["import { renderError } from \"./response/errors.js\";\nimport { resolveRequestDecisions, strictOverrideField } from \"./response/policy.js\";\nimport type http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RawJSONResponse,\n VoiceDesignResponse,\n} from \"./types.js\";\nimport {\n isErrorResponse,\n isJSONResponse,\n isJsonObject,\n flattenHeaders,\n getContext,\n getTestId,\n resolveResponse,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n wouldProxyMiss,\n} from \"./helpers.js\";\nimport { releaseOneShotError } from \"./fixture-loader.js\";\nimport { selectFixtureForServing } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\nimport type { ProxyCapturedResponse, ProxyOptions } from \"./recorder.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaosAsync, type ChaosAsyncOutcome } from \"./chaos.js\";\n\n/**\n * ElevenLabs Voice Design record + replay: `POST /v1/text-to-voice/design`\n * (previews), `POST /v1/text-to-voice` (create a voice from a preview),\n * `GET /v1/voices/{voice_id}`, `DELETE /v1/voices/{voice_id}`.\n *\n * PROVENANCE OF EVERY WIRE FACT IN THIS FILE. Read this before changing a\n * field name, a status code, or a literal below.\n *\n * FIRSTHAND — observed by this repo against live `api.elevenlabs.io` on\n * 2026-09-15, with NO api key and with a deliberately invalid one. No\n * ElevenLabs api key is reachable from this machine (1Password holds an\n * `elevenlabs.io` web login, not a key), so only the pre-auth and auth layers\n * were ever exercised:\n * - `POST /v1/text-to-voice/design` with a >=20-char `voice_description` and\n * no credential -> 401\n * `{\"detail\":{\"type\":\"authentication_error\",\"code\":\"unauthorized\",\n * \"message\":\"Neither authorization header nor xi-api-key received, please\n * provide one.\",\"status\":\"needs_authorization\",\"request_id\":\"<hex>\"}}`.\n * Same envelope from `GET /v1/voices/{id}`; with a bogus `xi-api-key` the\n * message/status become `\"Invalid API key\"` / `\"invalid_api_key\"`.\n * - `voice_description` shorter than 20 chars -> 422 with a pydantic-style\n * `{\"detail\":[{\"type\":\"string_too_short\",\"loc\":[\"body\",\n * \"voice_description\"],...}]}`; `POST /v1/text-to-voice` reports missing\n * `voice_name` / `generated_voice_id` the same way. So the real service\n * validates the body BEFORE it authenticates. THIS HANDLER NOW ENFORCES\n * THAT 20-CODE-POINT MINIMUM on `POST /v1/text-to-voice/design`\n * (`VOICE_DESCRIPTION_MIN_LENGTH`) and answers with that same `422\n * {\"detail\":[...]}` envelope rather than aimock's house error shape — the\n * one response on this surface whose real shape was observed. The `msg`\n * STRING it fills in (\"String should have at least 20 characters\") is\n * AUTHORED: the observed body was recorded with `msg` elided, so only the\n * status, the `detail` array and the `loc`/`type` values are firsthand.\n * ORDER ON `POST /v1/text-to-voice/design`: missing-parameter 400 ->\n * fixture match (a registered short-description fixture still serves) ->\n * chaos -> strict refusal / record-mode proxy (so the REAL 422 is recorded\n * verbatim instead of pre-empted) -> this local 20-code-point 422 ->\n * no-match 404. `POST /v1/text-to-voice` follows the same order, with its\n * empty-string 422 in the same slot ahead of the synthesized voice.\n * The minimum is NOT enforced on `POST /v1/text-to-voice`, whose\n * `voice_description` bound has never been probed; that route rejects only\n * an EMPTY description, which is short under any `min_length >= 1`.\n * - `GET /v1/voices/aimock-nonexistent-voice` -> 400\n * `{\"detail\":{\"type\":\"invalid_request\",\"code\":\"bad_request\",\"message\":\"An\n * invalid ID has been received: ...\",\"status\":\"invalid_uid\",...}}`.\n * KNOWN DIVERGENCE, recorded rather than papered over: every real envelope\n * above is keyed `detail`, while this handler still emits aimock's house\n * `{ error: { message, type, code } }` shape — and aimock's own 400/404/503\n * codes — for EVERY failure except the `voice_description` minimum. That is\n * deliberate consistency with every other surface here, but it means those\n * ERROR envelopes are NOT wire-faithful to ElevenLabs. Making the whole\n * module speak `detail` is a FOLLOW-UP: it is a breaking change for every\n * existing consumer of this surface, and the shapes of the OTHER failures\n * (missing parameter, unknown voice, strict-mode refusal) have not been\n * observed, so they cannot be authored from the one case that has. A\n * RECORDED fixture is a second exception: it replays the upstream `detail`\n * body verbatim under the upstream status, so it is faithful by\n * construction.\n *\n * SECONDARY — the official client `@elevenlabs/elevenlabs-js`, pinned to the\n * exact published version `2.68.0` (`node_modules/@elevenlabs/elevenlabs-js/\n * package.json` -> `\"version\": \"2.68.0\"`). Every claim below was read out of\n * these files, at that version, and nowhere else:\n * - `api/resources/textToVoice/client/Client.d.ts` /`.js` — the routes, and\n * the `@throws` list for each (`UnprocessableEntityError` only);\n * - `api/resources/textToVoice/client/requests/VoiceDesignRequestModel.d.ts`\n * and `.../BodyCreateANewVoiceFromVoicePreviewV1TextToVoicePost.d.ts` —\n * the request bodies;\n * - `api/types/VoiceDesignPreviewResponse.d.ts`,\n * `api/types/VoicePreviewResponseModel.d.ts`, `api/types/Voice.d.ts`,\n * `api/types/HttpValidationError.d.ts`, `api/types/ValidationError.d.ts`,\n * `api/types/ValidationErrorLocItem.d.ts` — the response models;\n * - the matching `serialization/types/*.d.ts` `Raw` interfaces and\n * `serialization/types/*.js` schemas — the on-wire snake_case names and\n * each field's optional/nullable treatment;\n * - `api/errors/UnprocessableEntityError.js` (422) and\n * `api/errors/ConflictError.js` (409, never declared for these routes).\n * It pins:\n * - the four routes above, and that create-from-preview is `POST\n * /v1/text-to-voice` taking `voice_name`, `voice_description`,\n * `generated_voice_id`, optional `labels`;\n * - the design SUCCESS body `{ previews: [...], text }` where each preview is\n * `{ audio_base_64, generated_voice_id, media_type, duration_secs,\n * language? }` — `language` optional, the rest required;\n * - `model_id` values `eleven_multilingual_ttv_v2` / `eleven_ttv_v3`;\n * - the `Voice` object's wire fields. Every key `buildSyntheticVoice()`\n * emits (`voice_id`, `name`, `category`, `description`, `labels`,\n * `preview_url`, `available_for_tiers`, `settings`, `sharing`,\n * `high_quality_base_model_ids`, `samples`, `safety_control`,\n * `voice_verification` with its four inner keys, `permission_on_resource`,\n * `is_owner`, `is_legacy`, `is_mixed`) appears in `Voice.Raw` /\n * `VoiceVerificationResponse.Raw`, and `\"generated\"` is one of that SDK's\n * six `VoiceCategory` tokens;\n * - `DELETE /v1/voices/{id}` answering `{ status: \"ok\" }`.\n * ElevenLabs' own documentation site is a client-rendered SPA and was NOT\n * read; the SDK is the only document behind these shapes.\n *\n * UNVERIFIED / HAND-AUTHORED — name it, do not soften it:\n * - NO successful response from ElevenLabs has ever been observed by this\n * repo, for any of these four routes. There is no recorded fixture or tape\n * for this surface anywhere in the tree; the drift test in\n * `src/__tests__/drift/elevenlabs-voice.drift.ts` restates the same\n * hand-authored shape it checks, so it detects mock-vs-fixture divergence\n * only and can\n * never detect divergence from the real API.\n * - The VALUES `buildSyntheticVoice()` fills those fields with (`null`s, `[]`,\n * `is_owner: true`, `is_legacy`/`is_mixed` false, the zeroed\n * `voice_verification`) are aimock's choices for a just-created voice, not\n * anything observed. The SDK marks all of them optional and nullable, so\n * they are plausible, not confirmed.\n * - `voice_id === generated_voice_id` for a created voice. AUTHORED — see\n * `buildSyntheticVoice()`. The SDK relates the two nowhere; aimock picks\n * the alias so a caller can GET back what it just created. No code should\n * depend on it as a wire fact.\n * - Re-creating an already-created `generated_voice_id` overwrites and\n * answers 200. AUTHORED — see `rememberElevenLabsVoice()`. The real\n * behaviour is unknown and the SDK declares no `ConflictError` for the\n * route, so aimock warns through the module logger instead of inventing a\n * rejection.\n * - `Voice.Raw` carries further fields this handler never emits\n * (`fine_tuning`, `verified_languages`, `collection_ids`,\n * `created_at_unix`, `favorited_at_unix`, `is_bookmarked`,\n * `recording_quality`, `labelling_status`, `recording_quality_reason`). A\n * consumer reading them off a replayed voice gets `undefined`.\n * The standing rule still holds: THE MOCK NEVER AUTHORS A WIRE VALUE IT DID\n * NOT OBSERVE. This surface currently does, and this block is where that debt\n * is declared. Recording one real design + create round-trip against a funded\n * key would retire most of it.\n */\n\nconst VOICE_STORE_MAX = 10_000;\n\n/**\n * `POST /v1/text-to-voice/design` rejects a `voice_description` shorter than\n * this. FIRSTHAND: observed against live `api.elevenlabs.io` on 2026-09-15 (see\n * the PROVENANCE block above) — a sub-20-character description comes back 422\n * with a pydantic `string_too_short` entry, BEFORE the request is even\n * authenticated, so the bound is observable without a key.\n *\n * THE UNIT IS CODE POINTS, NOT UTF-16 CODE UNITS. The bound is pydantic's\n * `min_length`, which is `len(str)` in Python — a count of code points. A\n * JavaScript `String.length` counts UTF-16 code units instead, so it\n * DOUBLE-COUNTS every non-BMP character: a 19-emoji description is 19 code\n * points to the real service (422) and 38 `.length` to JavaScript (would have\n * passed here and then failed on the wire, which is the exact class of\n * divergence this mock exists to prevent). Measure with `[...s].length`, whose\n * string iterator yields code points. Grapheme clusters are deliberately NOT\n * the unit: Python does not count those either, so `Intl.Segmenter` would\n * re-introduce the same disagreement from the other side.\n */\nconst VOICE_DESCRIPTION_MIN_LENGTH = 20;\n\n/**\n * The real service answers body-validation failures with FastAPI/pydantic's\n * envelope: HTTP 422 and `{\"detail\":[{\"loc\":[...],\"msg\":...,\"type\":...}]}`.\n * The key set is pinned by `@elevenlabs/elevenlabs-js@2.68.0`\n * (`api/types/HttpValidationError.d.ts` -> `detail?: ValidationError[]`,\n * `api/types/ValidationError.d.ts` -> `{ loc, msg, type }`,\n * `serialization/types/ValidationError.d.ts` for the identical on-wire Raw),\n * and the status by `api/errors/UnprocessableEntityError.js`\n * (`statusCode: 422`), which is what that SDK throws for these routes.\n *\n * DELIBERATE DIVERGENCE FROM ITS SIBLINGS: every other validation failure in\n * this file answers aimock's house `400 {\"error\":{...}}`. This one does not,\n * because this is the only body-validation response on this surface whose real\n * shape this repo has actually observed. Aligning the rest is a follow-up, not\n * a licence to author the others' shapes from this one.\n */\nfunction writeValidationError(\n res: http.ServerResponse,\n loc: Array<string | number>,\n msg: string,\n type: string,\n): void {\n writeErrorResponse(res, 422, JSON.stringify({ detail: [{ loc, msg, type }] }));\n}\n\n/**\n * Voices created via POST /v1/text-to-voice, keyed by voice_id.\n *\n * MODULE-GLOBAL, deliberately: it is the same shape every other bounded store\n * here uses (`FalJobMap` in `./fal-audio.ts`, `FalQueueStateMap` in `./fal.ts`,\n * and the four video job maps), so one process shares one voice store across\n * every `LLMock` instance. `performFullReset` (`./server.ts`) clears it\n * alongside those siblings, which is what `POST /__aimock/reset` and\n * `LLMock.reset()` both route through. `stop()` does NOT clear it — no sibling\n * store is cleared on `stop()` either; reset is the isolation barrier, not\n * server teardown. A test file that creates voices must therefore reset (or\n * call `clearElevenLabsVoices()`) between tests, or a voice saved by one test\n * is still visible to the next.\n */\nconst elevenLabsVoices = new Map<string, Record<string, unknown>>();\n\nexport function clearElevenLabsVoices(): void {\n elevenLabsVoices.clear();\n}\n\n/**\n * Store a created voice under its own `voice_id` — the id the client sees in\n * the body we are about to write, so a follow-up `GET /v1/voices/{id}` by that\n * id hits.\n *\n * Returns `false` when the body carries no string `voice_id` and nothing was\n * stored, so the caller can say so instead of dropping the voice silently.\n *\n * AUTHORED: a second create for a `voice_id` already in the store OVERWRITES\n * the first, and still answers 200. Whether the real\n * `POST /v1/text-to-voice` accepts an already-consumed `generated_voice_id` is\n * UNKNOWN to this repo — no successful create has ever been observed, and\n * `@elevenlabs/elevenlabs-js@2.68.0` gives no signal either way: its\n * `api/resources/textToVoice/client/Client.d.ts` declares only\n * `@throws UnprocessableEntityError` for `create`, and never `ConflictError`\n * (`api/errors/ConflictError.js`), so authoring a 409 here would be inventing a\n * status nothing supports. Overwrite-and-200 is aimock's choice; it is made\n * AUDIBLE via `logger.warn` rather than silent, so a test that overwrites a\n * voice by accident can see it.\n *\n * EVICTION IS FIFO AND SILENT, by insertion order — the same rule every\n * sibling store applies (see `FalJobMap.set`, `src/fal-audio.ts:107-116`, and\n * the identical block in `fal.ts` / `openrouter-video.ts` / `grok-video.ts` /\n * `byteplus-video.ts`). Once the map exceeds `VOICE_STORE_MAX`, the\n * oldest-inserted entries are deleted with no log line and no error: a later\n * `GET /v1/voices/{id}` for an evicted voice is indistinguishable from one for\n * a voice that never existed (404, or 503 under strict). It is NOT LRU —\n * reading a voice does not refresh it, and re-saving an existing `voice_id`\n * keeps its ORIGINAL insertion slot, because `Map.set` on a present key does\n * not reorder. The cap exists to bound a long-lived mock process, not to model\n * anything ElevenLabs does; at 10k voices per process no test approaches it.\n */\nexport function rememberElevenLabsVoice(voice: Record<string, unknown>, logger?: Logger): boolean {\n const voiceId = typeof voice.voice_id === \"string\" ? voice.voice_id : undefined;\n if (!voiceId) return false;\n if (elevenLabsVoices.has(voiceId)) {\n logger?.warn(\n `ElevenLabs voice '${voiceId}' already existed and was overwritten; aimock does not know whether the real API rejects a reused generated_voice_id`,\n );\n }\n elevenLabsVoices.set(voiceId, voice);\n if (elevenLabsVoices.size > VOICE_STORE_MAX) {\n const excess = elevenLabsVoices.size - VOICE_STORE_MAX;\n const iter = elevenLabsVoices.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) elevenLabsVoices.delete(next.value);\n }\n }\n return true;\n}\n\n/**\n * The status a recorded `{ json, status }` replay must answer with.\n *\n * `proxyAndRecord` persists the upstream status alongside the verbatim body for\n * this surface (`src/recorder.ts:901` — `{ json: parsedResponse, status:\n * upstreamStatus }`), and `RawJSONResponse.status` (`src/types.ts:476`) is the\n * field it lands in. Replaying every such fixture as 200 turns every recorded\n * ElevenLabs error — all of which carry a `detail` body, not aimock's house\n * error envelope, so `isErrorResponse()` never claims them — into a fabricated\n * success. `src/fal-audio.ts:1061` reads the same field the same way.\n *\n * Only `status` round-trips: that recorder branch stores no headers for a\n * verbatim-JSON recording, so there is no recorded header to honour here.\n */\nfunction replayStatus(response: RawJSONResponse): number {\n return response.status ?? 200;\n}\n\nexport function voiceDesignToJson(response: VoiceDesignResponse): RawJSONResponse {\n const previews = response.previews.map((preview, index) => {\n const entry: Record<string, unknown> = {\n generated_voice_id: preview.generated_voice_id || `aimock-preview-${index}`,\n audio_base_64: preview.audio_base_64 ?? \"\",\n media_type: preview.media_type ?? \"audio/mpeg\",\n duration_secs: preview.duration_secs ?? 0,\n };\n // `language` is OMITTED, never null, when the caller did not supply one:\n // the SDK models it `language?: string` (absent-or-string, never null) in\n // `api/types/VoicePreviewResponseModel.d.ts`, and its runtime schema\n // (`serialization/types/VoicePreviewResponseModel.js`,\n // `core.serialization.string().optional()`) parses an incoming `null` away\n // to `undefined`. A `null` here is therefore a value no SDK consumer can\n // ever observe from the real API.\n if (typeof preview.language === \"string\") entry.language = preview.language;\n return entry;\n });\n return {\n json: {\n previews,\n text: response.text ?? \"\",\n },\n };\n}\n\nfunction writeJson(res: http.ServerResponse, status: number, payload: unknown): void {\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(payload));\n}\n\n/**\n * The journal `source` for a chaos fault on a request no fixture answers — the\n * same rule `src/server.ts` applies, via the shared {@link wouldProxyMiss}:\n * the miss is aimock's own answer, \"internal\", unless record mode has an\n * ElevenLabs upstream to forward it to AND strict mode (server default or the\n * per-request `X-AIMock-Strict` header) is not refusing it first. Both halves\n * used to be hardcoded — design/create said \"proxy\" with no record config at\n * all, and the slot routes said \"internal\" while record mode was about to\n * proxy the very same miss.\n */\nfunction noFixtureSource(\n defaults: HandlerDefaults,\n headers: http.IncomingHttpHeaders,\n): \"proxy\" | \"internal\" {\n return wouldProxyMiss(\n !resolveRequestDecisions(defaults.strict, headers).upstream,\n defaults.record,\n \"elevenlabs\",\n )\n ? \"proxy\"\n : \"internal\";\n}\n\nfunction parseJsonObject(\n body: string,\n req: http.IncomingMessage,\n res: http.ServerResponse,\n journal: Journal,\n path: string,\n method: string,\n): Record<string, unknown> | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n writeErrorResponse(\n res,\n 400,\n renderError(\"openai\", \"validation\", {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n }).body,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: null,\n response: { status: 400, fixture: null },\n });\n return null;\n }\n\n if (!isJsonObject(parsed)) {\n writeErrorResponse(\n res,\n 400,\n renderError(\"openai\", \"validation\", {\n message: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n }).body,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: null,\n response: { status: 400, fixture: null },\n });\n return null;\n }\n\n return parsed;\n}\n\nfunction buildSyntheticReq(\n parsed: Record<string, unknown>,\n matchText: string,\n endpoint: \"elevenlabs-voice-design\" | \"elevenlabs-voice\",\n modelFallback: string,\n req: http.IncomingMessage,\n): ChatCompletionRequest {\n return {\n model: typeof parsed.model_id === \"string\" ? parsed.model_id : modelFallback,\n messages: [{ role: \"user\", content: matchText }],\n _endpointType: endpoint,\n _context: getContext(req),\n };\n}\n\nasync function missPath(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n path: string,\n method: string,\n body: string,\n skippedBySequenceOrTurn: number,\n proxyOptions?: ProxyOptions,\n): Promise<\"handled\" | \"miss\"> {\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 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 journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n return \"handled\";\n }\n\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"elevenlabs\",\n req.url ?? path,\n fixtures,\n defaults,\n body,\n proxyOptions,\n );\n if (outcome === \"handled_by_hook\") return \"handled\";\n if (outcome !== \"not_configured\") {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return \"handled\";\n }\n }\n\n return \"miss\";\n}\n\nfunction writeNoMatch(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n defaults: HandlerDefaults,\n journal: Journal,\n path: string,\n method: string,\n): void {\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 journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n}\n\n/**\n * Builds the `beforeWriteResponse` observer for the voice routes. It NEVER\n * handles the response — it returns `false` so `proxyAndRecord` relays the\n * upstream bytes and the caller still journals the `\"relayed\"` outcome — it\n * only copies a successfully proxied voice object into the in-process store, so\n * a follow-up `GET /v1/voices/{id}` answers from memory exactly as it does\n * after an in-process create instead of hitting upstream a second time.\n *\n * It is a FACTORY OVER THE LOGGER, not a bare function, because the store has\n * exactly two things worth saying and neither can be said without one: the\n * AUTHORED overwrite of an already-stored `voice_id`, which\n * `rememberElevenLabsVoice` documents as made AUDIBLE rather than silent, and a\n * 2xx voice body carrying no string `voice_id`, which it reports by returning\n * `false`. Both are already reported on the in-process and fixture-replay paths\n * (see the `create` handler's `defaults.logger` calls); passing the observer a\n * bare function dropped both on EVERY proxy path, which is the one path where\n * the body came from somewhere aimock does not control.\n */\nfunction rememberProxiedVoice(logger: Logger): (captured: ProxyCapturedResponse) => false {\n return (captured: ProxyCapturedResponse): false => {\n if (captured.status < 200 || captured.status >= 300) return false;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(captured.body.toString(\"utf-8\"));\n } catch (err) {\n // ONLY a malformed upstream body is swallowed: a non-JSON 2xx carries no\n // voice to remember and the relay is unaffected. `JSON.parse` throws\n // `SyntaxError` and nothing else, so ANY other error came from a genuine\n // defect in this hook and must propagate — `proxyAndRecord` turns a\n // rejected hook into `beforeWriteResponse hook failed for <provider>`\n // (`src/recorder.ts`), which is a visible failure instead of a voice that\n // vanished under the label \"upstream sent HTML\".\n if (err instanceof SyntaxError) return false;\n throw err;\n }\n\n if (!isJsonObject(parsed)) return false;\n if (!rememberElevenLabsVoice(parsed, logger)) {\n logger.warn(\n `ElevenLabs relayed a ${captured.status} voice body from upstream that carries no ` +\n `string \\`voice_id\\` — the voice was NOT stored, so a follow-up ` +\n `GET /v1/voices/{id} will miss and re-hit upstream instead of replaying from ` +\n `memory. Upstream body: ${captured.body.toString(\"utf-8\").slice(0, 200)}`,\n );\n }\n return false;\n };\n}\n\nfunction buildSyntheticVoice(parsed: Record<string, unknown>): Record<string, unknown> {\n // AUTHORED: `voice_id === generated_voice_id`. Nothing in\n // `@elevenlabs/elevenlabs-js@2.68.0` says the created voice keeps the\n // preview's id — `api/resources/textToVoice/client/requests/\n // BodyCreateANewVoiceFromVoicePreviewV1TextToVoicePost.d.ts` documents\n // `generatedVoiceId` only as \"the generated_voice_id to create\", and the\n // returned `api/types/Voice.d.ts` relates `voiceId` to it nowhere. This is\n // aimock's choice so that a caller can GET the voice it just created; treat\n // it as a mock convention, not a wire fact, and do not build logic on it.\n const generatedVoiceId = String(parsed.generated_voice_id);\n const labels =\n parsed.labels && typeof parsed.labels === \"object\" && !Array.isArray(parsed.labels)\n ? (parsed.labels as Record<string, unknown>)\n : {};\n return {\n voice_id: generatedVoiceId,\n name: String(parsed.voice_name),\n category: \"generated\",\n description: String(parsed.voice_description),\n labels,\n preview_url: null,\n available_for_tiers: [],\n settings: null,\n sharing: null,\n high_quality_base_model_ids: [],\n samples: null,\n safety_control: null,\n voice_verification: {\n requires_verification: false,\n is_verified: false,\n verification_failures: [],\n verification_attempts_count: 0,\n },\n permission_on_resource: null,\n is_owner: true,\n is_legacy: false,\n is_mixed: false,\n };\n}\n\nexport async function handleElevenLabsVoiceDesign(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n): Promise<void> {\n const path = req.url ?? \"/v1/text-to-voice/design\";\n const method = req.method ?? \"POST\";\n\n const parsed = parseJsonObject(body, req, res, journal, path, method);\n if (!parsed) return;\n\n // ONLY a non-string (or absent) `voice_description` is MISSING. An empty\n // string is PRESENT AND TOO SHORT: a truthiness test here folded `\"\"` into\n // the missing case and answered aimock's house 400, while the real service\n // answers the same firsthand-observed 422 `string_too_short` it answers for\n // every other sub-minimum description (PROVENANCE block above). `\"\"` is the\n // shortest such description there is, so it is the one case where the house\n // 400 and the observed 422 disagree most visibly.\n const description =\n typeof parsed.voice_description === \"string\" ? parsed.voice_description : undefined;\n\n const syntheticReq = buildSyntheticReq(\n parsed,\n description ?? \"\",\n \"elevenlabs-voice-design\",\n \"eleven_multilingual_ttv_v2\",\n req,\n );\n\n if (description === undefined) {\n writeErrorResponse(\n res,\n 400,\n renderError(\"openai\", \"validation\", {\n message: \"Missing required parameter: 'voice_description'\",\n type: \"invalid_request_error\",\n }).body,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 400, fixture: null },\n });\n return;\n }\n\n const testId = getTestId(req);\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n matchCounts,\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n service: \"elevenlabs-voice\",\n },\n fixture ? \"fixture\" : noFixtureSource(defaults, req.headers),\n defaults.registry,\n defaults.logger,\n )\n ) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n if (!fixture) {\n const outcome = await missPath(\n req,\n res,\n syntheticReq,\n fixtures,\n defaults,\n journal,\n path,\n method,\n body,\n skippedBySequenceOrTurn,\n );\n if (outcome === \"handled\") return;\n\n // THE LOCAL MINIMUM IS THE LAST RESORT, NOT THE FIRST GATE. It used to run\n // ahead of fixture matching and ahead of `missPath`, which meant a\n // registered short-description fixture could never be served and — worse —\n // record mode answered aimock's own AUTHORED `msg` instead of forwarding,\n // so the one response on this surface whose real shape was observed could\n // never be recorded. `missPath` has already refused (strict) or proxied and\n // recorded (record) by the time we get here, so this only fires when\n // nothing else can answer.\n // Code points, not `.length`: see `VOICE_DESCRIPTION_MIN_LENGTH`.\n if ([...description].length < VOICE_DESCRIPTION_MIN_LENGTH) {\n writeValidationError(\n res,\n [\"body\", \"voice_description\"],\n `String should have at least ${VOICE_DESCRIPTION_MIN_LENGTH} characters`,\n \"string_too_short\",\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 422, fixture: null },\n });\n return;\n }\n\n writeNoMatch(req, res, syntheticReq, defaults, journal, path, method);\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n writeErrorResponse(res, status, renderError(\"openai\", \"fixture\", response.error).body, {\n retryAfter: response.retryAfter,\n });\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n return;\n }\n\n if (!isJSONResponse(response)) {\n writeErrorResponse(\n res,\n 500,\n renderError(\"openai\", \"unknown-response\", {\n message: \"Fixture response is not a JSON type for voice design\",\n type: \"server_error\",\n }).body,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n return;\n }\n\n const status = replayStatus(response);\n writeJson(res, status, response.json);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n}\n\nexport async function handleElevenLabsVoiceCreate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n): Promise<void> {\n const path = req.url ?? \"/v1/text-to-voice\";\n const method = req.method ?? \"POST\";\n\n const parsed = parseJsonObject(body, req, res, journal, path, method);\n if (!parsed) return;\n\n // ONLY a non-string (or absent) field is MISSING here, for the same reason\n // the design handler stopped folding `\"\"` into the missing case: an empty\n // string is PRESENT AND TOO SHORT, and the house 400 said the caller had\n // omitted a field it had actually sent. `voice_name` and `generated_voice_id`\n // follow the same rule as `voice_description`.\n const missing =\n typeof parsed.voice_name !== \"string\"\n ? \"voice_name\"\n : typeof parsed.voice_description !== \"string\"\n ? \"voice_description\"\n : typeof parsed.generated_voice_id !== \"string\"\n ? \"generated_voice_id\"\n : null;\n const tooShort =\n parsed.voice_name === \"\"\n ? \"voice_name\"\n : parsed.voice_description === \"\"\n ? \"voice_description\"\n : parsed.generated_voice_id === \"\"\n ? \"generated_voice_id\"\n : null;\n\n const syntheticReq = buildSyntheticReq(\n parsed,\n typeof parsed.generated_voice_id === \"string\" ? parsed.generated_voice_id : \"\",\n \"elevenlabs-voice\",\n \"eleven_multilingual_ttv_v2\",\n req,\n );\n\n if (missing) {\n writeErrorResponse(\n res,\n 400,\n renderError(\"openai\", \"validation\", {\n message: `Missing required parameter: '${missing}'`,\n type: \"invalid_request_error\",\n }).body,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 400, fixture: null },\n });\n return;\n }\n\n const testId = getTestId(req);\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n matchCounts,\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n service: \"elevenlabs-voice\",\n },\n fixture ? \"fixture\" : noFixtureSource(defaults, req.headers),\n defaults.registry,\n defaults.logger,\n )\n ) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n if (!fixture) {\n const outcome = await missPath(\n req,\n res,\n syntheticReq,\n fixtures,\n defaults,\n journal,\n path,\n method,\n body,\n skippedBySequenceOrTurn,\n { beforeWriteResponse: rememberProxiedVoice(defaults.logger) },\n );\n if (outcome === \"handled\") return;\n\n // THE LOCAL MINIMUM IS THE LAST RESORT, NOT THE FIRST GATE — same position\n // as the design handler's, for the same reasons: a registered fixture,\n // chaos, strict and the record proxy all get their turn first, so record\n // mode forwards and records the REAL 422 instead of authoring one.\n // An EMPTY string is the one length this route can answer without\n // authoring an unobserved bound: it is under ANY `min_length >= 1`, so\n // `string_too_short` holds whatever the real minimum on\n // `POST /v1/text-to-voice` turns out to be. THE 20-CODE-POINT BOUND IS\n // DELIBERATELY NOT ENFORCED HERE — that minimum was observed on\n // `/v1/text-to-voice/design` only (PROVENANCE block), and this route's\n // bounds have never been probed. The `msg` string is AUTHORED, like its\n // design sibling's.\n if (tooShort) {\n writeValidationError(\n res,\n [\"body\", tooShort],\n \"String should have at least 1 character\",\n \"string_too_short\",\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 422, fixture: null },\n });\n return;\n }\n\n const voice = buildSyntheticVoice(parsed);\n rememberElevenLabsVoice(voice, defaults.logger);\n writeJson(res, 200, voice);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n writeErrorResponse(res, status, renderError(\"openai\", \"fixture\", response.error).body, {\n retryAfter: response.retryAfter,\n });\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n return;\n }\n\n if (!isJSONResponse(response)) {\n writeErrorResponse(\n res,\n 500,\n renderError(\"openai\", \"unknown-response\", {\n message: \"Fixture response is not a JSON type for voice create\",\n type: \"server_error\",\n }).body,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n return;\n }\n\n const status = replayStatus(response);\n\n // Store the voice under the `voice_id` the CLIENT is about to see (this same\n // body), so its follow-up `GET /v1/voices/{id}` by the id it received hits.\n // Only a 2xx replay creates a voice: a recorded 4xx/5xx `detail` body carries\n // no voice, and remembering it would resurrect the fabricated-success bug\n // from the other side.\n //\n // A 2xx body with no string `voice_id` is an AUTHORING error, not an\n // unservable fixture: the body is still what the recording says the client\n // gets, so serve it and name the defect, the way `src/byteplus-video.ts:469`\n // and `:479` name an envelope that is servable but will strand the client\n // later. (The 500 `server_error` treatment a few lines above — and in\n // `src/fal-audio.ts:1071` — is reserved for a response that CANNOT be served\n // at all, which this one can.)\n if (status >= 200 && status < 300) {\n const remembered =\n isJsonObject(response.json) && rememberElevenLabsVoice(response.json, defaults.logger);\n if (!remembered) {\n defaults.logger.warn(\n `ElevenLabs voice create fixture matched generated_voice_id ` +\n `\"${String(parsed.generated_voice_id)}\" but its json carries no string \\`voice_id\\` — ` +\n `the voice was NOT stored, so a follow-up GET /v1/voices/{id} will 404 with a ` +\n `\"voice not found\" message that points at the id instead of at the fixture. Add a ` +\n `string voice_id to the fixture's json`,\n );\n }\n }\n\n writeJson(res, status, response.json);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n}\n\n/**\n * The synthetic request the slot routes (GET/DELETE /v1/voices/{id}) match and\n * journal on. The voice id stands in for the user message so the entry carries\n * the one thing that identifies the request.\n *\n * The endpoint type is the CALLER's, not a constant: ONE ROUTE, ONE ENDPOINT\n * TYPE. GET declares \"elevenlabs-voice-get\" and DELETE declares\n * \"elevenlabs-voice-delete\", so neither route's fixtures can ever be confused\n * with the create route's (\"elevenlabs-voice\") — all three would otherwise\n * carry the same voice id as their match text and answer for one another.\n */\nfunction buildVoiceSlotReq(\n req: http.IncomingMessage,\n voiceId: string,\n endpointType: \"elevenlabs-voice-get\" | \"elevenlabs-voice-delete\",\n): ChatCompletionRequest {\n return {\n model: \"eleven_multilingual_ttv_v2\",\n messages: [{ role: \"user\", content: voiceId }],\n _endpointType: endpointType,\n _context: getContext(req),\n };\n}\n\n/**\n * Gate a slot route on chaos exactly as {@link handleElevenLabsVoiceDesign} and\n * {@link handleElevenLabsVoiceCreate} gate theirs: the same `applyChaosAsync`\n * call, awaited BEFORE anything is served, so a configured latency/drop/429\n * applies to a store hit as well as to a miss. It is called AFTER the fixture\n * match and is handed the fixture that matched, because chaos can be authored\n * ON a fixture (`Fixture.chaos`, folded in by `chaos.ts`): passing null here\n * dropped a fixture-level `chaos` block on the floor for these two routes\n * while design/create honoured the identical block. The journal source follows\n * the same fixture/no-fixture split design/create use, via the same\n * {@link noFixtureSource}: \"internal\" when this process answers the miss,\n * \"proxy\" when record mode has an ElevenLabs upstream and would forward it.\n *\n * `applyChaosAsync` reads the per-testId chaos scope off `req.url`, which is\n * why the raw url — not the fallback `path` — is what it is handed, matching\n * design/create. The context carries `service: \"elevenlabs-voice\"` like every\n * other journal write in this module, so a faulted request stays selectable by\n * `GET /__aimock/journal?service=elevenlabs-voice`.\n */\nasync function gateVoiceSlotChaos(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n fixture: Fixture | null,\n defaults: HandlerDefaults,\n journal: Journal,\n path: string,\n method: string,\n): Promise<ChaosAsyncOutcome> {\n return await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n service: \"elevenlabs-voice\",\n },\n fixture ? \"fixture\" : noFixtureSource(defaults, req.headers),\n defaults.registry,\n defaults.logger,\n );\n}\n\n/**\n * Refuse a slot-route miss under strict mode, mirroring {@link missPath}: the\n * refusal is LOGGED through the shared `strictNoMatchLogLine` and the client\n * message names strict mode, so a strict 503 is never mistaken for the route's\n * own 404.\n *\n * `skippedBySequenceOrTurn` is the count `selectFixtureForServing` handed the\n * caller. Both slot routes are fixture routes now, so a candidate fixture CAN\n * be skipped by sequence/turn state here exactly as on create — hardcoding 0\n * told every such refusal \"no fixture matched\" when the truth was \"a fixture\n * matched the content and was held back by its sequenceIndex\". When the count\n * is non-zero the client message switches to the shared\n * {@link strictNoMatchMessage} wording that names it; otherwise the\n * voice-specific message is kept, since naming the id is the more useful thing\n * to say when nothing matched at all.\n */\nfunction writeVoiceSlotStrictRefusal(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n defaults: HandlerDefaults,\n journal: Journal,\n path: string,\n method: string,\n voiceId: string,\n skippedBySequenceOrTurn: number,\n): void {\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n writeErrorResponse(\n res,\n 503,\n renderError(\"openai\", \"strict-miss\", {\n message:\n skippedBySequenceOrTurn > 0\n ? strictNoMatchMessage(skippedBySequenceOrTurn)\n : `Strict mode: voice '${voiceId}' not found`,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n }).body,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n}\n\n/**\n * Proxy a slot-route miss upstream in record mode. Returns \"handled\" when the\n * upstream answer was relayed (or a hook took over), \"miss\" when no upstream is\n * configured for the provider — `proxyAndRecord` already warns on that path, so\n * the caller falls through to its own miss answer.\n */\nasync function proxyVoiceSlotMiss(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n defaults: HandlerDefaults,\n journal: Journal,\n path: string,\n method: string,\n fixtures: Fixture[] = [],\n proxyOptions?: ProxyOptions,\n): Promise<\"handled\" | \"miss\"> {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"elevenlabs\",\n req.url ?? path,\n fixtures,\n defaults,\n \"\",\n proxyOptions,\n );\n if (outcome === \"handled_by_hook\") return \"handled\";\n if (outcome === \"not_configured\") return \"miss\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return \"handled\";\n}\n\n/**\n * Replay a fixture that matched on a slot route (`GET`/`DELETE /v1/voices/{id}`).\n *\n * Both slot routes replay identically, so the body lives here once: resolve,\n * relay an error envelope under its own status, refuse a non-JSON response,\n * and otherwise write the recorded JSON under {@link replayStatus} — the\n * UPSTREAM status the recorder persisted, not a hardcoded 200. GET used to\n * hardcode 200, which turned every recorded ElevenLabs failure (a `detail`\n * body that `isErrorResponse()` never claims) into a fabricated success.\n *\n * Returns the status actually written, or `null` when the fixture could not be\n * served as a success (an error envelope was relayed, or the response was not\n * JSON at all). DELETE uses that to decide whether the local store entry is\n * gone.\n */\nasync function replayVoiceSlotFixture(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n syntheticReq: ChatCompletionRequest,\n fixture: Fixture,\n journal: Journal,\n path: string,\n method: string,\n routeLabel: string,\n): Promise<number | null> {\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n writeErrorResponse(res, status, renderError(\"openai\", \"fixture\", response.error).body, {\n retryAfter: response.retryAfter,\n });\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n return null;\n }\n\n if (!isJSONResponse(response)) {\n writeErrorResponse(\n res,\n 500,\n renderError(\"openai\", \"unknown-response\", {\n message: `Fixture response is not a JSON type for ${routeLabel}`,\n type: \"server_error\",\n }).body,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n return null;\n }\n\n const status = replayStatus(response);\n writeJson(res, status, response.json);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status, fixture },\n });\n return status;\n}\n\n/**\n * FIXTURE BEFORE STORE, on both slot routes.\n *\n * A registered or recorded fixture is explicit authoring intent and wins over\n * the in-process voice store, which is a replay CONVENIENCE populated as a\n * side effect of create. That is the order every other replay route in this\n * repo uses — fixture match first, internal state only on a miss — and without\n * it a fixture authored specifically for `GET /v1/voices/{id}` (a recorded 401,\n * say) was silently unreachable for any id that create had already stored.\n *\n * The shadowing is AUDIBLE rather than silent: when a fixture wins over a\n * present store entry this logs at debug, so a test whose store entry stopped\n * being served can see why.\n */\nfunction logVoiceSlotShadow(\n defaults: HandlerDefaults,\n method: string,\n path: string,\n voiceId: string,\n): void {\n defaults.logger.debug(\n `ElevenLabs ${method} ${path}: a matching fixture shadows the in-process voice ` +\n `store entry for '${voiceId}' — the fixture is being served`,\n );\n}\n\nexport async function handleElevenLabsVoiceGet(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n voiceId: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n): Promise<void> {\n const path = req.url ?? `/v1/voices/${voiceId}`;\n const method = req.method ?? \"GET\";\n\n // DISTINCT endpoint type from the create route on purpose. GET used to\n // declare \"elevenlabs-voice\", the same type `POST /v1/text-to-voice`\n // declares, with the voice_id as the match text on both sides — so a fixture\n // recorded on GET became a first-class candidate for create (and hijacked\n // it), while create's own fixtures could be handed to GET. One route, one\n // endpoint type; `src/router.ts` and `src/recorder.ts` both know this name.\n const syntheticReq = buildVoiceSlotReq(req, voiceId, \"elevenlabs-voice-get\");\n\n // GET is a first-class replay route, exactly like design and create: a\n // registered or previously recorded fixture serves it. Without this the\n // route could only ever answer from the in-process store, so nothing\n // recorded to disk was reachable on a later run. See\n // {@link logVoiceSlotShadow} for why the fixture is consulted BEFORE the\n // store. The match runs BEFORE the chaos gate — and the count is incremented\n // before it, as design/create do — so a fixture-level `chaos` block reaches\n // the gate; see {@link gateVoiceSlotChaos}.\n const testId = getTestId(req);\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n matchCounts,\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (await gateVoiceSlotChaos(req, res, syntheticReq, fixture, defaults, journal, path, method)) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n const stored = elevenLabsVoices.get(voiceId);\n\n if (fixture) {\n if (stored) logVoiceSlotShadow(defaults, method, path, voiceId);\n await replayVoiceSlotFixture(\n req,\n res,\n syntheticReq,\n fixture,\n journal,\n path,\n method,\n \"voice get\",\n );\n return;\n }\n\n const serveStored = (voice: Record<string, unknown>): void => {\n writeJson(res, 200, voice);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n };\n\n // IN RECORD MODE THE LOCAL STORE IS NOT AUTHORITATIVE, the same guard DELETE\n // carries. Answering a locally-known voice from memory kept the GET out of\n // the tape entirely — a proxied create left the voice in the store, so no\n // `elevenlabs-voice-get` fixture was ever recorded and the replay run, which\n // has only the tape, 404'd on the GET that \"worked\" while recording.\n if (stored && !defaults.record) {\n serveStored(stored);\n return;\n }\n\n // STRICT BEFORE RECORD, the precedence missPath() uses. A strict server\n // refuses the miss outright; it must not silently proxy it upstream.\n if (resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {\n writeVoiceSlotStrictRefusal(\n req,\n res,\n syntheticReq,\n defaults,\n journal,\n path,\n method,\n voiceId,\n skippedBySequenceOrTurn,\n );\n return;\n }\n\n if (defaults.record) {\n // The REAL fixtures array, not a throwaway `[]`: `persistFixture`\n // (`src/recorder.ts`) pushes the freshly recorded fixture into whatever\n // array it is handed, so passing a discarded literal made the live server\n // forget what it had just recorded and re-hit upstream on every\n // subsequent GET.\n const outcome = await proxyVoiceSlotMiss(\n req,\n res,\n syntheticReq,\n defaults,\n journal,\n path,\n method,\n fixtures,\n { beforeWriteResponse: rememberProxiedVoice(defaults.logger) },\n );\n if (outcome === \"handled\") return;\n // Record mode is on but NO ElevenLabs upstream is configured (`--record`\n // aimed at another provider): the proxy cannot forward, and the create\n // that handed this id out was synthesized locally on that same path. The\n // store is the only thing that knows the voice, so it answers.\n if (stored) {\n serveStored(stored);\n return;\n }\n }\n\n writeErrorResponse(\n res,\n 404,\n renderError(\"openai\", \"fixture-miss\", {\n message: `Voice '${voiceId}' not found`,\n type: \"invalid_request_error\",\n code: \"voice_not_found\",\n }).body,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 404, fixture: null, ...strictOverrideField(defaults.strict, req.headers) },\n });\n}\n\n/**\n * DELETE /v1/voices/{voice_id}. A first-class record/replay route, exactly\n * like GET: chaos, then a fixture match, then the in-process store, then\n * strict, then record, then the lenient answer.\n *\n * The success body is `{ status: \"ok\" }` — SECONDARY, from\n * `@elevenlabs/elevenlabs-js@2.68.0`: `voices.delete()` parses the response as\n * `DeleteVoiceResponseModel`, whose only wire field is `status: string`, and\n * whose doc comment reads \"The status of the voice deletion request. If the\n * request was successful, the status will be 'ok'.\" In record mode nothing is\n * authored at all — the upstream's own response is relayed AND recorded.\n *\n * IN RECORD MODE THE LOCAL STORE IS NOT AUTHORITATIVE. A locally-known voice\n * used to be deleted from the map and answered 200 without the request ever\n * leaving the process, so the real voice leaked upstream and the tape got a\n * hole exactly where the delete belonged. Record mode forwards regardless of\n * what the store knows; the store is a replay convenience, not a claim about\n * what exists upstream. It still evicts ONLY on a 2xx from upstream, the same\n * rule the fixture-replay branch applies: a refused delete must not lose the\n * local voice.\n *\n * A miss on a LENIENT server stays an idempotent 200: deleting an id that was\n * never saved is not an error on this surface.\n */\nexport async function handleElevenLabsVoiceDelete(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n voiceId: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n): Promise<void> {\n const path = req.url ?? `/v1/voices/${voiceId}`;\n const method = req.method ?? \"DELETE\";\n\n // Its OWN endpoint type, for the same reason GET has one: DELETE used to\n // declare \"elevenlabs-voice\" with the voice id as its match text — byte for\n // byte what `POST /v1/text-to-voice` declares — so a recorded DELETE\n // (`{\"status\":\"ok\"}`) was a first-class candidate for create and hijacked it\n // on the next run.\n const syntheticReq = buildVoiceSlotReq(req, voiceId, \"elevenlabs-voice-delete\");\n\n // Match (and count) BEFORE the chaos gate, exactly as GET and design/create\n // do, so a fixture-level `chaos` block reaches the gate.\n const testId = getTestId(req);\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n const { fixture, skippedBySequenceOrTurn } = selectFixtureForServing(\n fixtures,\n syntheticReq,\n matchCounts,\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (await gateVoiceSlotChaos(req, res, syntheticReq, fixture, defaults, journal, path, method)) {\n releaseOneShotError(fixtures, fixture);\n return;\n }\n\n const stored = elevenLabsVoices.has(voiceId);\n\n if (fixture) {\n if (stored) logVoiceSlotShadow(defaults, method, path, voiceId);\n const status = await replayVoiceSlotFixture(\n req,\n res,\n syntheticReq,\n fixture,\n journal,\n path,\n method,\n \"voice delete\",\n );\n // Only a 2xx replay says the voice is gone. A replayed 4xx/5xx is the\n // recording saying the delete did NOT happen, so the local entry stays.\n if (status !== null && status >= 200 && status < 300) elevenLabsVoices.delete(voiceId);\n return;\n }\n\n if (stored && !defaults.record) {\n elevenLabsVoices.delete(voiceId);\n writeJson(res, 200, { status: \"ok\" });\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: {\n status: 200,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n return;\n }\n\n // STRICT BEFORE RECORD, the precedence missPath() and GET both use.\n if (resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {\n writeVoiceSlotStrictRefusal(\n req,\n res,\n syntheticReq,\n defaults,\n journal,\n path,\n method,\n voiceId,\n skippedBySequenceOrTurn,\n );\n return;\n }\n\n if (defaults.record) {\n // Evict ONLY on a 2xx from upstream, the same rule the fixture-replay\n // branch above applies: an upstream 4xx/5xx is upstream saying the delete\n // did NOT happen, and dropping the entry anyway lost a local voice to a\n // refusal. `beforeWriteResponse` observes the captured upstream status and\n // returns `false`, so `proxyAndRecord` still relays the bytes and the\n // caller still journals the `\"relayed\"` outcome.\n // The REAL fixtures array, for the same reason GET passes it: the\n // throwaway `[]` this used to pass meant every recorded DELETE was\n // discarded on the spot, never replayed, and every repeat re-hit upstream.\n const outcome = await proxyVoiceSlotMiss(\n req,\n res,\n syntheticReq,\n defaults,\n journal,\n path,\n method,\n fixtures,\n {\n beforeWriteResponse: (captured): false => {\n if (captured.status >= 200 && captured.status < 300) {\n elevenLabsVoices.delete(voiceId);\n }\n return false;\n },\n },\n );\n if (outcome === \"handled\") return;\n }\n\n elevenLabsVoices.delete(voiceId);\n writeJson(res, 200, { status: \"ok\" });\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n service: \"elevenlabs-voice\",\n body: syntheticReq,\n response: { status: 200, fixture: null, ...strictOverrideField(defaults.strict, req.headers) },\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"elevenlabs-voice.d.cts","names":[],"sources":["../src/elevenlabs-voice.ts"],"sourcesContent":[],"mappings":";;;;;iBAgOgB,qBAAA,CAAA;AAAhB;AA0EA;;;;;AAiUA;;;;;;;;;AA6LA;;;;;;;;;AAqgBA;;;;;;;;;AA8KsB,iBAjrCN,iBAAA,CAirCiC,QAAA,EAjrCL,mBAirCK,CAAA,EAjrCiB,eAirCjB;AAAA,iBAh3B3B,2BAAA,CAg3B2B,GAAA,EA/2B1C,IAAA,CAAK,eA+2BqC,EAAA,GAAA,EA92B1C,IAAA,CAAK,cA82BqC,EAAA,IAAA,EAAA,MAAA,EAAA,QAAA,EA52BrC,OA42BqC,EAAA,EAAA,QAAA,EA32BrC,eA22BqC,EAAA,OAAA,EA12BtC,OA02BsC,CAAA,EAz2B9C,OAy2B8C,CAAA,IAAA,CAAA;AAC1C,iBAprBe,2BAAA,CAorBV,GAAA,EAnrBL,IAAA,CAAK,eAmrBA,EAAA,GAAA,EAlrBL,IAAA,CAAK,cAkrBA,EAAA,IAAA,EAAA,MAAA,EAAA,QAAA,EAhrBA,OAgrBA,EAAA,EAAA,QAAA,EA/qBA,eA+qBA,EAAA,OAAA,EA9qBD,OA8qBC,CAAA,EA7qBT,OA6qBS,CAAA,IAAA,CAAA;AACL,iBAhLe,wBAAA,CAgLV,GAAA,EA/KL,IAAA,CAAK,eA+KA,EAAA,GAAA,EA9KL,IAAA,CAAK,cA8KA,EAAA,OAAA,EAAA,MAAA,EAAA,QAAA,EA5KA,OA4KA,EAAA,EAAA,QAAA,EA3KA,eA2KA,EAAA,OAAA,EA1KD,OA0KC,CAAA,EAzKT,OAyKS,CAAA,IAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBAFU,2BAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,2CAEA,qBACA,0BACD,UACR"}
1
+ {"version":3,"file":"elevenlabs-voice.d.cts","names":[],"sources":["../src/elevenlabs-voice.ts"],"sourcesContent":[],"mappings":";;;;;iBA+NgB,qBAAA,CAAA;AAAhB;AA0EA;;;;;AA6TA;;;;;;;;;AAyLA;;;;;;;;;AA6fA;;;;;;;;;AA4KsB,iBA/pCN,iBAAA,CA+pCiC,QAAA,EA/pCL,mBA+pCK,CAAA,EA/pCiB,eA+pCjB;AAAA,iBAl2B3B,2BAAA,CAk2B2B,GAAA,EAj2B1C,IAAA,CAAK,eAi2BqC,EAAA,GAAA,EAh2B1C,IAAA,CAAK,cAg2BqC,EAAA,IAAA,EAAA,MAAA,EAAA,QAAA,EA91BrC,OA81BqC,EAAA,EAAA,QAAA,EA71BrC,eA61BqC,EAAA,OAAA,EA51BtC,OA41BsC,CAAA,EA31B9C,OA21B8C,CAAA,IAAA,CAAA;AAC1C,iBA1qBe,2BAAA,CA0qBV,GAAA,EAzqBL,IAAA,CAAK,eAyqBA,EAAA,GAAA,EAxqBL,IAAA,CAAK,cAwqBA,EAAA,IAAA,EAAA,MAAA,EAAA,QAAA,EAtqBA,OAsqBA,EAAA,EAAA,QAAA,EArqBA,eAqqBA,EAAA,OAAA,EApqBD,OAoqBC,CAAA,EAnqBT,OAmqBS,CAAA,IAAA,CAAA;AACL,iBA9Ke,wBAAA,CA8KV,GAAA,EA7KL,IAAA,CAAK,eA6KA,EAAA,GAAA,EA5KL,IAAA,CAAK,cA4KA,EAAA,OAAA,EAAA,MAAA,EAAA,QAAA,EA1KA,OA0KA,EAAA,EAAA,QAAA,EAzKA,eAyKA,EAAA,OAAA,EAxKD,OAwKC,CAAA,EAvKT,OAuKS,CAAA,IAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBAFU,2BAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,2CAEA,qBACA,0BACD,UACR"}
@@ -1 +1 @@
1
- {"version":3,"file":"elevenlabs-voice.d.ts","names":[],"sources":["../src/elevenlabs-voice.ts"],"sourcesContent":[],"mappings":";;;;;iBAgOgB,qBAAA,CAAA;AAAhB;AA0EA;;;;;AAiUA;;;;;;;;;AA6LA;;;;;;;;;AAqgBA;;;;;;;;;AA8KsB,iBAjrCN,iBAAA,CAirCiC,QAAA,EAjrCL,mBAirCK,CAAA,EAjrCiB,eAirCjB;AAAA,iBAh3B3B,2BAAA,CAg3B2B,GAAA,EA/2B1C,IAAA,CAAK,eA+2BqC,EAAA,GAAA,EA92B1C,IAAA,CAAK,cA82BqC,EAAA,IAAA,EAAA,MAAA,EAAA,QAAA,EA52BrC,OA42BqC,EAAA,EAAA,QAAA,EA32BrC,eA22BqC,EAAA,OAAA,EA12BtC,OA02BsC,CAAA,EAz2B9C,OAy2B8C,CAAA,IAAA,CAAA;AAC1C,iBAprBe,2BAAA,CAorBV,GAAA,EAnrBL,IAAA,CAAK,eAmrBA,EAAA,GAAA,EAlrBL,IAAA,CAAK,cAkrBA,EAAA,IAAA,EAAA,MAAA,EAAA,QAAA,EAhrBA,OAgrBA,EAAA,EAAA,QAAA,EA/qBA,eA+qBA,EAAA,OAAA,EA9qBD,OA8qBC,CAAA,EA7qBT,OA6qBS,CAAA,IAAA,CAAA;AACL,iBAhLe,wBAAA,CAgLV,GAAA,EA/KL,IAAA,CAAK,eA+KA,EAAA,GAAA,EA9KL,IAAA,CAAK,cA8KA,EAAA,OAAA,EAAA,MAAA,EAAA,QAAA,EA5KA,OA4KA,EAAA,EAAA,QAAA,EA3KA,eA2KA,EAAA,OAAA,EA1KD,OA0KC,CAAA,EAzKT,OAyKS,CAAA,IAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBAFU,2BAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,2CAEA,qBACA,0BACD,UACR"}
1
+ {"version":3,"file":"elevenlabs-voice.d.ts","names":[],"sources":["../src/elevenlabs-voice.ts"],"sourcesContent":[],"mappings":";;;;;iBA+NgB,qBAAA,CAAA;AAAhB;AA0EA;;;;;AA6TA;;;;;;;;;AAyLA;;;;;;;;;AA6fA;;;;;;;;;AA4KsB,iBA/pCN,iBAAA,CA+pCiC,QAAA,EA/pCL,mBA+pCK,CAAA,EA/pCiB,eA+pCjB;AAAA,iBAl2B3B,2BAAA,CAk2B2B,GAAA,EAj2B1C,IAAA,CAAK,eAi2BqC,EAAA,GAAA,EAh2B1C,IAAA,CAAK,cAg2BqC,EAAA,IAAA,EAAA,MAAA,EAAA,QAAA,EA91BrC,OA81BqC,EAAA,EAAA,QAAA,EA71BrC,eA61BqC,EAAA,OAAA,EA51BtC,OA41BsC,CAAA,EA31B9C,OA21B8C,CAAA,IAAA,CAAA;AAC1C,iBA1qBe,2BAAA,CA0qBV,GAAA,EAzqBL,IAAA,CAAK,eAyqBA,EAAA,GAAA,EAxqBL,IAAA,CAAK,cAwqBA,EAAA,IAAA,EAAA,MAAA,EAAA,QAAA,EAtqBA,OAsqBA,EAAA,EAAA,QAAA,EArqBA,eAqqBA,EAAA,OAAA,EApqBD,OAoqBC,CAAA,EAnqBT,OAmqBS,CAAA,IAAA,CAAA;AACL,iBA9Ke,wBAAA,CA8KV,GAAA,EA7KL,IAAA,CAAK,eA6KA,EAAA,GAAA,EA5KL,IAAA,CAAK,cA4KA,EAAA,OAAA,EAAA,MAAA,EAAA,QAAA,EA1KA,OA0KA,EAAA,EAAA,QAAA,EAzKA,eAyKA,EAAA,OAAA,EAxKD,OAwKC,CAAA,EAvKT,OAuKS,CAAA,IAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBAFU,2BAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,2CAEA,qBACA,0BACD,UACR"}
@@ -1,4 +1,6 @@
1
- import { flattenHeaders, getContext, getTestId, isErrorResponse, isJSONResponse, isJsonObject, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField, wouldProxyMiss } from "./helpers.js";
1
+ import { renderError } from "./response/errors.js";
2
+ import { resolveRequestDecisions, strictOverrideField } from "./response/policy.js";
3
+ import { flattenHeaders, getContext, getTestId, isErrorResponse, isJSONResponse, isJsonObject, resolveResponse, strictNoMatchLogLine, strictNoMatchMessage, wouldProxyMiss } from "./helpers.js";
2
4
  import { writeErrorResponse } from "./sse-writer.js";
3
5
  import { applyChaosAsync } from "./chaos.js";
4
6
  import { releaseOneShotError } from "./fixture-loader.js";
@@ -289,19 +291,18 @@ function writeJson(res, status, payload) {
289
291
  * proxy the very same miss.
290
292
  */
291
293
  function noFixtureSource(defaults, headers) {
292
- return wouldProxyMiss(resolveStrictMode(defaults.strict, headers), defaults.record, "elevenlabs") ? "proxy" : "internal";
294
+ return wouldProxyMiss(!resolveRequestDecisions(defaults.strict, headers).upstream, defaults.record, "elevenlabs") ? "proxy" : "internal";
293
295
  }
294
296
  function parseJsonObject(body, req, res, journal, path, method) {
295
297
  let parsed;
296
298
  try {
297
299
  parsed = JSON.parse(body);
298
300
  } catch (parseErr) {
299
- const detail = parseErr instanceof Error ? parseErr.message : "unknown";
300
- writeErrorResponse(res, 400, JSON.stringify({ error: {
301
- message: `Malformed JSON: ${detail}`,
301
+ writeErrorResponse(res, 400, renderError("openai", "validation", {
302
+ message: `Malformed JSON: ${parseErr instanceof Error ? parseErr.message : "unknown"}`,
302
303
  type: "invalid_request_error",
303
304
  code: "invalid_json"
304
- } }));
305
+ }).body);
305
306
  journal.add({
306
307
  method,
307
308
  path,
@@ -316,10 +317,10 @@ function parseJsonObject(body, req, res, journal, path, method) {
316
317
  return null;
317
318
  }
318
319
  if (!isJsonObject(parsed)) {
319
- writeErrorResponse(res, 400, JSON.stringify({ error: {
320
+ writeErrorResponse(res, 400, renderError("openai", "validation", {
320
321
  message: "Request body must be a JSON object",
321
322
  type: "invalid_request_error"
322
- } }));
323
+ }).body);
323
324
  journal.add({
324
325
  method,
325
326
  path,
@@ -347,14 +348,14 @@ function buildSyntheticReq(parsed, matchText, endpoint, modelFallback, req) {
347
348
  };
348
349
  }
349
350
  async function missPath(req, res, syntheticReq, fixtures, defaults, journal, path, method, body, skippedBySequenceOrTurn, proxyOptions) {
350
- if (resolveStrictMode(defaults.strict, req.headers)) {
351
+ if (resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
351
352
  const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);
352
353
  defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));
353
- writeErrorResponse(res, 503, JSON.stringify({ error: {
354
+ writeErrorResponse(res, 503, renderError("openai", "strict-miss", {
354
355
  message: strictMessage,
355
356
  type: "invalid_request_error",
356
357
  code: "no_fixture_match"
357
- } }));
358
+ }).body);
358
359
  journal.add({
359
360
  method,
360
361
  path,
@@ -391,11 +392,11 @@ async function missPath(req, res, syntheticReq, fixtures, defaults, journal, pat
391
392
  return "miss";
392
393
  }
393
394
  function writeNoMatch(req, res, syntheticReq, defaults, journal, path, method) {
394
- writeErrorResponse(res, 404, JSON.stringify({ error: {
395
+ writeErrorResponse(res, 404, renderError("openai", "fixture-miss", {
395
396
  message: "No fixture matched",
396
397
  type: "invalid_request_error",
397
398
  code: "no_fixture_match"
398
- } }));
399
+ }).body);
399
400
  journal.add({
400
401
  method,
401
402
  path,
@@ -478,10 +479,10 @@ async function handleElevenLabsVoiceDesign(req, res, body, fixtures, defaults, j
478
479
  const description = typeof parsed.voice_description === "string" ? parsed.voice_description : void 0;
479
480
  const syntheticReq = buildSyntheticReq(parsed, description ?? "", "elevenlabs-voice-design", "eleven_multilingual_ttv_v2", req);
480
481
  if (description === void 0) {
481
- writeErrorResponse(res, 400, JSON.stringify({ error: {
482
+ writeErrorResponse(res, 400, renderError("openai", "validation", {
482
483
  message: "Missing required parameter: 'voice_description'",
483
484
  type: "invalid_request_error"
484
- } }));
485
+ }).body);
485
486
  journal.add({
486
487
  method,
487
488
  path,
@@ -531,7 +532,7 @@ async function handleElevenLabsVoiceDesign(req, res, body, fixtures, defaults, j
531
532
  const response = await resolveResponse(fixture, syntheticReq);
532
533
  if (isErrorResponse(response)) {
533
534
  const status = response.status ?? 500;
534
- writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
535
+ writeErrorResponse(res, status, renderError("openai", "fixture", response.error).body, { retryAfter: response.retryAfter });
535
536
  journal.add({
536
537
  method,
537
538
  path,
@@ -546,10 +547,10 @@ async function handleElevenLabsVoiceDesign(req, res, body, fixtures, defaults, j
546
547
  return;
547
548
  }
548
549
  if (!isJSONResponse(response)) {
549
- writeErrorResponse(res, 500, JSON.stringify({ error: {
550
+ writeErrorResponse(res, 500, renderError("openai", "unknown-response", {
550
551
  message: "Fixture response is not a JSON type for voice design",
551
552
  type: "server_error"
552
- } }));
553
+ }).body);
553
554
  journal.add({
554
555
  method,
555
556
  path,
@@ -586,10 +587,10 @@ async function handleElevenLabsVoiceCreate(req, res, body, fixtures, defaults, j
586
587
  const tooShort = parsed.voice_name === "" ? "voice_name" : parsed.voice_description === "" ? "voice_description" : parsed.generated_voice_id === "" ? "generated_voice_id" : null;
587
588
  const syntheticReq = buildSyntheticReq(parsed, typeof parsed.generated_voice_id === "string" ? parsed.generated_voice_id : "", "elevenlabs-voice", "eleven_multilingual_ttv_v2", req);
588
589
  if (missing) {
589
- writeErrorResponse(res, 400, JSON.stringify({ error: {
590
+ writeErrorResponse(res, 400, renderError("openai", "validation", {
590
591
  message: `Missing required parameter: '${missing}'`,
591
592
  type: "invalid_request_error"
592
- } }));
593
+ }).body);
593
594
  journal.add({
594
595
  method,
595
596
  path,
@@ -652,7 +653,7 @@ async function handleElevenLabsVoiceCreate(req, res, body, fixtures, defaults, j
652
653
  const response = await resolveResponse(fixture, syntheticReq);
653
654
  if (isErrorResponse(response)) {
654
655
  const status = response.status ?? 500;
655
- writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
656
+ writeErrorResponse(res, status, renderError("openai", "fixture", response.error).body, { retryAfter: response.retryAfter });
656
657
  journal.add({
657
658
  method,
658
659
  path,
@@ -667,10 +668,10 @@ async function handleElevenLabsVoiceCreate(req, res, body, fixtures, defaults, j
667
668
  return;
668
669
  }
669
670
  if (!isJSONResponse(response)) {
670
- writeErrorResponse(res, 500, JSON.stringify({ error: {
671
+ writeErrorResponse(res, 500, renderError("openai", "unknown-response", {
671
672
  message: "Fixture response is not a JSON type for voice create",
672
673
  type: "server_error"
673
- } }));
674
+ }).body);
674
675
  journal.add({
675
676
  method,
676
677
  path,
@@ -769,11 +770,11 @@ async function gateVoiceSlotChaos(req, res, syntheticReq, fixture, defaults, jou
769
770
  */
770
771
  function writeVoiceSlotStrictRefusal(req, res, syntheticReq, defaults, journal, path, method, voiceId, skippedBySequenceOrTurn) {
771
772
  defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));
772
- writeErrorResponse(res, 503, JSON.stringify({ error: {
773
+ writeErrorResponse(res, 503, renderError("openai", "strict-miss", {
773
774
  message: skippedBySequenceOrTurn > 0 ? strictNoMatchMessage(skippedBySequenceOrTurn) : `Strict mode: voice '${voiceId}' not found`,
774
775
  type: "invalid_request_error",
775
776
  code: "no_fixture_match"
776
- } }));
777
+ }).body);
777
778
  journal.add({
778
779
  method,
779
780
  path,
@@ -830,7 +831,7 @@ async function replayVoiceSlotFixture(req, res, syntheticReq, fixture, journal,
830
831
  const response = await resolveResponse(fixture, syntheticReq);
831
832
  if (isErrorResponse(response)) {
832
833
  const status = response.status ?? 500;
833
- writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
834
+ writeErrorResponse(res, status, renderError("openai", "fixture", response.error).body, { retryAfter: response.retryAfter });
834
835
  journal.add({
835
836
  method,
836
837
  path,
@@ -845,10 +846,10 @@ async function replayVoiceSlotFixture(req, res, syntheticReq, fixture, journal,
845
846
  return null;
846
847
  }
847
848
  if (!isJSONResponse(response)) {
848
- writeErrorResponse(res, 500, JSON.stringify({ error: {
849
+ writeErrorResponse(res, 500, renderError("openai", "unknown-response", {
849
850
  message: `Fixture response is not a JSON type for ${routeLabel}`,
850
851
  type: "server_error"
851
- } }));
852
+ }).body);
852
853
  journal.add({
853
854
  method,
854
855
  path,
@@ -929,7 +930,7 @@ async function handleElevenLabsVoiceGet(req, res, voiceId, fixtures, defaults, j
929
930
  serveStored(stored);
930
931
  return;
931
932
  }
932
- if (resolveStrictMode(defaults.strict, req.headers)) {
933
+ if (resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
933
934
  writeVoiceSlotStrictRefusal(req, res, syntheticReq, defaults, journal, path, method, voiceId, skippedBySequenceOrTurn);
934
935
  return;
935
936
  }
@@ -940,11 +941,11 @@ async function handleElevenLabsVoiceGet(req, res, voiceId, fixtures, defaults, j
940
941
  return;
941
942
  }
942
943
  }
943
- writeErrorResponse(res, 404, JSON.stringify({ error: {
944
+ writeErrorResponse(res, 404, renderError("openai", "fixture-miss", {
944
945
  message: `Voice '${voiceId}' not found`,
945
946
  type: "invalid_request_error",
946
947
  code: "voice_not_found"
947
- } }));
948
+ }).body);
948
949
  journal.add({
949
950
  method,
950
951
  path,
@@ -1017,7 +1018,7 @@ async function handleElevenLabsVoiceDelete(req, res, voiceId, fixtures, defaults
1017
1018
  });
1018
1019
  return;
1019
1020
  }
1020
- if (resolveStrictMode(defaults.strict, req.headers)) {
1021
+ if (resolveRequestDecisions(defaults.strict, req.headers).fixtureMiss) {
1021
1022
  writeVoiceSlotStrictRefusal(req, res, syntheticReq, defaults, journal, path, method, voiceId, skippedBySequenceOrTurn);
1022
1023
  return;
1023
1024
  }