whale-code 6.5.4 → 6.5.6

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 (853) hide show
  1. package/README.md +39 -31
  2. package/bin/{swagmanager-mcp.js → whale-code.js} +17 -2
  3. package/dist/cli/app.js +148 -72
  4. package/dist/cli/app.js.map +1 -0
  5. package/dist/cli/chat/AgentSelector.js +105 -10
  6. package/dist/cli/chat/AgentSelector.js.map +1 -0
  7. package/dist/cli/chat/ChatApp.d.ts +31 -0
  8. package/dist/cli/chat/ChatApp.js +539 -286
  9. package/dist/cli/chat/ChatApp.js.map +1 -0
  10. package/dist/cli/chat/ChatInput.js +1088 -770
  11. package/dist/cli/chat/ChatInput.js.map +1 -0
  12. package/dist/cli/chat/MarkdownText.js +39 -14
  13. package/dist/cli/chat/MarkdownText.js.map +1 -0
  14. package/dist/cli/chat/MemoryManager.js +181 -46
  15. package/dist/cli/chat/MemoryManager.js.map +1 -0
  16. package/dist/cli/chat/MessageList.d.ts +2 -3
  17. package/dist/cli/chat/MessageList.js +186 -45
  18. package/dist/cli/chat/MessageList.js.map +1 -0
  19. package/dist/cli/chat/ModelSelector.js +282 -63
  20. package/dist/cli/chat/ModelSelector.js.map +1 -0
  21. package/dist/cli/chat/NodeManager.js +165 -75
  22. package/dist/cli/chat/NodeManager.js.map +1 -0
  23. package/dist/cli/chat/NodeSelector.js +171 -30
  24. package/dist/cli/chat/NodeSelector.js.map +1 -0
  25. package/dist/cli/chat/PlanApproval.js +281 -57
  26. package/dist/cli/chat/PlanApproval.js.map +1 -0
  27. package/dist/cli/chat/RewindViewer.js +559 -144
  28. package/dist/cli/chat/RewindViewer.js.map +1 -0
  29. package/dist/cli/chat/SessionManager.js +137 -30
  30. package/dist/cli/chat/SessionManager.js.map +1 -0
  31. package/dist/cli/chat/SlashMenu.js +293 -164
  32. package/dist/cli/chat/SlashMenu.js.map +1 -0
  33. package/dist/cli/chat/StatusBar.js +172 -9
  34. package/dist/cli/chat/StatusBar.js.map +1 -0
  35. package/dist/cli/chat/StoreSelector.js +147 -18
  36. package/dist/cli/chat/StoreSelector.js.map +1 -0
  37. package/dist/cli/chat/StreamingText.d.ts +1 -5
  38. package/dist/cli/chat/StreamingText.js +22 -7
  39. package/dist/cli/chat/StreamingText.js.map +1 -0
  40. package/dist/cli/chat/SubagentPanel.d.ts +1 -2
  41. package/dist/cli/chat/SubagentPanel.js +612 -72
  42. package/dist/cli/chat/SubagentPanel.js.map +1 -0
  43. package/dist/cli/chat/TeamPanel.d.ts +1 -0
  44. package/dist/cli/chat/TeamPanel.js +230 -30
  45. package/dist/cli/chat/TeamPanel.js.map +1 -0
  46. package/dist/cli/chat/ThemeSelector.js +84 -24
  47. package/dist/cli/chat/ThemeSelector.js.map +1 -0
  48. package/dist/cli/chat/ToolIndicator.js +1476 -371
  49. package/dist/cli/chat/ToolIndicator.js.map +1 -0
  50. package/dist/cli/chat/hooks/useAgentLoop.d.ts +1 -0
  51. package/dist/cli/chat/hooks/useAgentLoop.js +481 -367
  52. package/dist/cli/chat/hooks/useAgentLoop.js.map +1 -0
  53. package/dist/cli/chat/hooks/useSlashCommands.d.ts +3 -14
  54. package/dist/cli/chat/hooks/useSlashCommands.js +744 -572
  55. package/dist/cli/chat/hooks/useSlashCommands.js.map +1 -0
  56. package/dist/cli/commands/config-cmd.js +56 -57
  57. package/dist/cli/commands/config-cmd.js.map +1 -0
  58. package/dist/cli/commands/db.js +184 -169
  59. package/dist/cli/commands/db.js.map +1 -0
  60. package/dist/cli/commands/doctor.js +212 -122
  61. package/dist/cli/commands/doctor.js.map +1 -0
  62. package/dist/cli/commands/init.js +211 -244
  63. package/dist/cli/commands/init.js.map +1 -0
  64. package/dist/cli/commands/mcp.js +127 -122
  65. package/dist/cli/commands/mcp.js.map +1 -0
  66. package/dist/cli/login/LoginApp.js +355 -141
  67. package/dist/cli/login/LoginApp.js.map +1 -0
  68. package/dist/cli/print-mode.js +196 -177
  69. package/dist/cli/print-mode.js.map +1 -0
  70. package/dist/cli/serve-mode.js +615 -530
  71. package/dist/cli/serve-mode.js.map +1 -0
  72. package/dist/cli/services/agent-config.d.ts +29 -0
  73. package/dist/cli/services/agent-config.js +91 -0
  74. package/dist/cli/services/agent-config.js.map +1 -0
  75. package/dist/cli/services/agent-definitions.d.ts +4 -1
  76. package/dist/cli/services/agent-definitions.js +97 -56
  77. package/dist/cli/services/agent-definitions.js.map +1 -0
  78. package/dist/cli/services/agent-events.js +225 -162
  79. package/dist/cli/services/agent-events.js.map +1 -0
  80. package/dist/cli/services/agent-loop.js +978 -669
  81. package/dist/cli/services/agent-loop.js.map +1 -0
  82. package/dist/cli/services/agent-worker-base.d.ts +35 -5
  83. package/dist/cli/services/agent-worker-base.js +337 -153
  84. package/dist/cli/services/agent-worker-base.js.map +1 -0
  85. package/dist/cli/services/api-retry.js +69 -64
  86. package/dist/cli/services/api-retry.js.map +1 -0
  87. package/dist/cli/services/auth-service.d.ts +3 -3
  88. package/dist/cli/services/auth-service.js +209 -132
  89. package/dist/cli/services/auth-service.js.map +1 -0
  90. package/dist/cli/services/background-processes.js +343 -267
  91. package/dist/cli/services/background-processes.js.map +1 -0
  92. package/dist/cli/services/browser-auth.d.ts +2 -2
  93. package/dist/cli/services/browser-auth.js +159 -118
  94. package/dist/cli/services/browser-auth.js.map +1 -0
  95. package/dist/cli/services/claude-md-loader.js +40 -36
  96. package/dist/cli/services/claude-md-loader.js.map +1 -0
  97. package/dist/cli/services/config-store.d.ts +9 -4
  98. package/dist/cli/services/config-store.js +164 -117
  99. package/dist/cli/services/config-store.js.map +1 -0
  100. package/dist/cli/services/debug-log.d.ts +1 -1
  101. package/dist/cli/services/debug-log.js +34 -35
  102. package/dist/cli/services/debug-log.js.map +1 -0
  103. package/dist/cli/services/env-detect.d.ts +7 -0
  104. package/dist/cli/services/env-detect.js +9 -0
  105. package/dist/cli/services/env-detect.js.map +1 -0
  106. package/dist/cli/services/error-logger.d.ts +2 -3
  107. package/dist/cli/services/error-logger.js +189 -180
  108. package/dist/cli/services/error-logger.js.map +1 -0
  109. package/dist/cli/services/file-history.d.ts +1 -1
  110. package/dist/cli/services/file-history.js +50 -54
  111. package/dist/cli/services/file-history.js.map +1 -0
  112. package/dist/cli/services/format-server-response.js +332 -372
  113. package/dist/cli/services/format-server-response.js.map +1 -0
  114. package/dist/cli/services/git-context.js +61 -45
  115. package/dist/cli/services/git-context.js.map +1 -0
  116. package/dist/cli/services/hooks.d.ts +2 -2
  117. package/dist/cli/services/hooks.js +195 -180
  118. package/dist/cli/services/hooks.js.map +1 -0
  119. package/dist/cli/services/ink-incremental.d.ts +19 -0
  120. package/dist/cli/services/ink-incremental.js +59 -0
  121. package/dist/cli/services/ink-incremental.js.map +1 -0
  122. package/dist/cli/services/ink-resize-fix.js +54 -44
  123. package/dist/cli/services/ink-resize-fix.js.map +1 -0
  124. package/dist/cli/services/ink-sync-output.d.ts +12 -0
  125. package/dist/cli/services/ink-sync-output.js +16 -0
  126. package/dist/cli/services/ink-sync-output.js.map +1 -0
  127. package/dist/cli/services/interactive-tools.js +268 -212
  128. package/dist/cli/services/interactive-tools.js.map +1 -0
  129. package/dist/cli/services/keybinding-manager.d.ts +11 -1
  130. package/dist/cli/services/keybinding-manager.js +126 -63
  131. package/dist/cli/services/keybinding-manager.js.map +1 -0
  132. package/dist/cli/services/local-tools.d.ts +1 -1
  133. package/dist/cli/services/local-tools.js +939 -656
  134. package/dist/cli/services/local-tools.js.map +1 -0
  135. package/dist/cli/services/lsp-manager.js +757 -594
  136. package/dist/cli/services/lsp-manager.js.map +1 -0
  137. package/dist/cli/services/mcp-client.d.ts +1 -1
  138. package/dist/cli/services/mcp-client.js +173 -134
  139. package/dist/cli/services/mcp-client.js.map +1 -0
  140. package/dist/cli/services/memory-manager.js +53 -40
  141. package/dist/cli/services/memory-manager.js.map +1 -0
  142. package/dist/cli/services/model-manager.js +55 -40
  143. package/dist/cli/services/model-manager.js.map +1 -0
  144. package/dist/cli/services/model-router.js +115 -85
  145. package/dist/cli/services/model-router.js.map +1 -0
  146. package/dist/cli/services/paths.d.ts +30 -0
  147. package/dist/cli/services/paths.js +81 -0
  148. package/dist/cli/services/paths.js.map +1 -0
  149. package/dist/cli/services/permission-modes.js +32 -25
  150. package/dist/cli/services/permission-modes.js.map +1 -0
  151. package/dist/cli/services/rewind.js +182 -168
  152. package/dist/cli/services/rewind.js.map +1 -0
  153. package/dist/cli/services/ripgrep.js +115 -115
  154. package/dist/cli/services/ripgrep.js.map +1 -0
  155. package/dist/cli/services/sandbox.d.ts +1 -1
  156. package/dist/cli/services/sandbox.js +58 -37
  157. package/dist/cli/services/sandbox.js.map +1 -0
  158. package/dist/cli/services/server-tools.js +738 -565
  159. package/dist/cli/services/server-tools.js.map +1 -0
  160. package/dist/cli/services/session-persistence.js +69 -74
  161. package/dist/cli/services/session-persistence.js.map +1 -0
  162. package/dist/cli/services/subagent-worker.js +42 -27
  163. package/dist/cli/services/subagent-worker.js.map +1 -0
  164. package/dist/cli/services/subagent.d.ts +2 -0
  165. package/dist/cli/services/subagent.js +606 -430
  166. package/dist/cli/services/subagent.js.map +1 -0
  167. package/dist/cli/services/system-prompt.js +86 -78
  168. package/dist/cli/services/system-prompt.js.map +1 -0
  169. package/dist/cli/services/task-decomposer.d.ts +1 -1
  170. package/dist/cli/services/task-decomposer.js +172 -139
  171. package/dist/cli/services/task-decomposer.js.map +1 -0
  172. package/dist/cli/services/team-lead.d.ts +2 -2
  173. package/dist/cli/services/team-lead.js +727 -529
  174. package/dist/cli/services/team-lead.js.map +1 -0
  175. package/dist/cli/services/team-state.js +319 -319
  176. package/dist/cli/services/team-state.js.map +1 -0
  177. package/dist/cli/services/teammate.d.ts +8 -2
  178. package/dist/cli/services/teammate.js +862 -560
  179. package/dist/cli/services/teammate.js.map +1 -0
  180. package/dist/cli/services/telemetry.d.ts +6 -1
  181. package/dist/cli/services/telemetry.js +180 -157
  182. package/dist/cli/services/telemetry.js.map +1 -0
  183. package/dist/cli/services/tools/agent-tools.d.ts +3 -3
  184. package/dist/cli/services/tools/agent-tools.js +480 -322
  185. package/dist/cli/services/tools/agent-tools.js.map +1 -0
  186. package/dist/cli/services/tools/file-ops.js +563 -450
  187. package/dist/cli/services/tools/file-ops.js.map +1 -0
  188. package/dist/cli/services/tools/search-tools.js +231 -162
  189. package/dist/cli/services/tools/search-tools.js.map +1 -0
  190. package/dist/cli/services/tools/shell-exec.js +197 -151
  191. package/dist/cli/services/tools/shell-exec.js.map +1 -0
  192. package/dist/cli/services/tools/task-manager.js +206 -173
  193. package/dist/cli/services/tools/task-manager.js.map +1 -0
  194. package/dist/cli/services/tools/web-tools.js +388 -341
  195. package/dist/cli/services/tools/web-tools.js.map +1 -0
  196. package/dist/cli/setup/SetupApp.d.ts +2 -2
  197. package/dist/cli/setup/SetupApp.js +608 -160
  198. package/dist/cli/setup/SetupApp.js.map +1 -0
  199. package/dist/cli/shared/ErrorBoundary.d.ts +22 -0
  200. package/dist/cli/shared/ErrorBoundary.js +73 -0
  201. package/dist/cli/shared/ErrorBoundary.js.map +1 -0
  202. package/dist/cli/shared/MatrixIntro.js +66 -69
  203. package/dist/cli/shared/MatrixIntro.js.map +1 -0
  204. package/dist/cli/shared/SpinnerSlot.d.ts +14 -0
  205. package/dist/cli/shared/SpinnerSlot.js +63 -0
  206. package/dist/cli/shared/SpinnerSlot.js.map +1 -0
  207. package/dist/cli/shared/Theme.d.ts +1 -1
  208. package/dist/cli/shared/Theme.js +136 -92
  209. package/dist/cli/shared/Theme.js.map +1 -0
  210. package/dist/cli/shared/WhaleBanner.js +99 -11
  211. package/dist/cli/shared/WhaleBanner.js.map +1 -0
  212. package/dist/cli/shared/markdown.d.ts +3 -1
  213. package/dist/cli/shared/markdown.js +736 -674
  214. package/dist/cli/shared/markdown.js.map +1 -0
  215. package/dist/cli/shared/marked-terminal.d.js +2 -0
  216. package/dist/cli/shared/marked-terminal.d.js.map +1 -0
  217. package/dist/cli/shared/theme-manager.js +99 -90
  218. package/dist/cli/shared/theme-manager.js.map +1 -0
  219. package/dist/cli/shared/theme-presets.js +256 -254
  220. package/dist/cli/shared/theme-presets.js.map +1 -0
  221. package/dist/cli/status/StatusApp.js +235 -86
  222. package/dist/cli/status/StatusApp.js.map +1 -0
  223. package/dist/cli/stores/StoreApp.js +275 -65
  224. package/dist/cli/stores/StoreApp.js.map +1 -0
  225. package/dist/index.d.ts +2 -2
  226. package/dist/index.js +509 -396
  227. package/dist/index.js.map +1 -0
  228. package/dist/local-agent/connection.d.ts +2 -2
  229. package/dist/local-agent/connection.js +352 -293
  230. package/dist/local-agent/connection.js.map +1 -0
  231. package/dist/local-agent/discovery.js +259 -122
  232. package/dist/local-agent/discovery.js.map +1 -0
  233. package/dist/local-agent/executor.js +216 -193
  234. package/dist/local-agent/executor.js.map +1 -0
  235. package/dist/local-agent/index.d.ts +2 -2
  236. package/dist/local-agent/index.js +156 -156
  237. package/dist/local-agent/index.js.map +1 -0
  238. package/dist/node/adapters/base.js +18 -8
  239. package/dist/node/adapters/base.js.map +1 -0
  240. package/dist/node/adapters/discord.js +286 -275
  241. package/dist/node/adapters/discord.js.map +1 -0
  242. package/dist/node/adapters/email.js +189 -202
  243. package/dist/node/adapters/email.js.map +1 -0
  244. package/dist/node/adapters/imessage.js +145 -142
  245. package/dist/node/adapters/imessage.js.map +1 -0
  246. package/dist/node/adapters/slack.js +237 -236
  247. package/dist/node/adapters/slack.js.map +1 -0
  248. package/dist/node/adapters/sms.js +149 -151
  249. package/dist/node/adapters/sms.js.map +1 -0
  250. package/dist/node/adapters/telegram.js +88 -92
  251. package/dist/node/adapters/telegram.js.map +1 -0
  252. package/dist/node/adapters/webchat.js +160 -136
  253. package/dist/node/adapters/webchat.js.map +1 -0
  254. package/dist/node/adapters/whatsapp.js +212 -215
  255. package/dist/node/adapters/whatsapp.js.map +1 -0
  256. package/dist/node/cli.js +884 -653
  257. package/dist/node/cli.js.map +1 -0
  258. package/dist/node/config.js +20 -18
  259. package/dist/node/config.js.map +1 -0
  260. package/dist/node/gateway-client.js +191 -181
  261. package/dist/node/gateway-client.js.map +1 -0
  262. package/dist/node/portal/clipboard.js +161 -130
  263. package/dist/node/portal/clipboard.js.map +1 -0
  264. package/dist/node/portal/discovery.js +51 -45
  265. package/dist/node/portal/discovery.js.map +1 -0
  266. package/dist/node/portal/forward.js +64 -58
  267. package/dist/node/portal/forward.js.map +1 -0
  268. package/dist/node/portal/index.js +246 -221
  269. package/dist/node/portal/index.js.map +1 -0
  270. package/dist/node/portal/multiplexer.js +192 -182
  271. package/dist/node/portal/multiplexer.js.map +1 -0
  272. package/dist/node/portal/permissions.js +102 -70
  273. package/dist/node/portal/permissions.js.map +1 -0
  274. package/dist/node/portal/protocol.js +153 -116
  275. package/dist/node/portal/protocol.js.map +1 -0
  276. package/dist/node/portal/screen.js +80 -69
  277. package/dist/node/portal/screen.js.map +1 -0
  278. package/dist/node/portal/session.js +124 -117
  279. package/dist/node/portal/session.js.map +1 -0
  280. package/dist/node/portal/shell.js +140 -113
  281. package/dist/node/portal/shell.js.map +1 -0
  282. package/dist/node/portal/stream.js +77 -75
  283. package/dist/node/portal/stream.js.map +1 -0
  284. package/dist/node/portal/transfer.js +190 -167
  285. package/dist/node/portal/transfer.js.map +1 -0
  286. package/dist/node/portal/ui.js +124 -99
  287. package/dist/node/portal/ui.js.map +1 -0
  288. package/dist/node/remote-desktop/compile-helper.js +50 -45
  289. package/dist/node/remote-desktop/compile-helper.js.map +1 -0
  290. package/dist/node/remote-desktop/index.js +215 -187
  291. package/dist/node/remote-desktop/index.js.map +1 -0
  292. package/dist/node/remote-desktop/protocol.js +45 -29
  293. package/dist/node/remote-desktop/protocol.js.map +1 -0
  294. package/dist/node/runtime.js +493 -410
  295. package/dist/node/runtime.js.map +1 -0
  296. package/dist/server/handlers/__test-utils__/test-db.js +39 -89
  297. package/dist/server/handlers/__test-utils__/test-db.js.map +1 -0
  298. package/dist/server/handlers/analytics.js +467 -261
  299. package/dist/server/handlers/analytics.js.map +1 -0
  300. package/dist/server/handlers/api-docs.d.ts +6 -0
  301. package/dist/server/handlers/api-docs.js +1613 -0
  302. package/dist/server/handlers/api-docs.js.map +1 -0
  303. package/dist/server/handlers/api-keys.js +295 -232
  304. package/dist/server/handlers/api-keys.js.map +1 -0
  305. package/dist/server/handlers/billing.js +330 -239
  306. package/dist/server/handlers/billing.js.map +1 -0
  307. package/dist/server/handlers/browser.js +468 -395
  308. package/dist/server/handlers/browser.js.map +1 -0
  309. package/dist/server/handlers/catalog.js +1377 -978
  310. package/dist/server/handlers/catalog.js.map +1 -0
  311. package/dist/server/handlers/clickhouse.js +157 -109
  312. package/dist/server/handlers/clickhouse.js.map +1 -0
  313. package/dist/server/handlers/comms.d.ts +0 -53
  314. package/dist/server/handlers/comms.js +1443 -970
  315. package/dist/server/handlers/comms.js.map +1 -0
  316. package/dist/server/handlers/creations.js +461 -394
  317. package/dist/server/handlers/creations.js.map +1 -0
  318. package/dist/server/handlers/crm.js +1082 -791
  319. package/dist/server/handlers/crm.js.map +1 -0
  320. package/dist/server/handlers/discovery.js +251 -232
  321. package/dist/server/handlers/discovery.js.map +1 -0
  322. package/dist/server/handlers/embeddings.js +241 -164
  323. package/dist/server/handlers/embeddings.js.map +1 -0
  324. package/dist/server/handlers/enrichment.js +887 -718
  325. package/dist/server/handlers/enrichment.js.map +1 -0
  326. package/dist/server/handlers/image-gen.js +467 -376
  327. package/dist/server/handlers/image-gen.js.map +1 -0
  328. package/dist/server/handlers/inventory.js +797 -424
  329. package/dist/server/handlers/inventory.js.map +1 -0
  330. package/dist/server/handlers/kali.js +272 -230
  331. package/dist/server/handlers/kali.js.map +1 -0
  332. package/dist/server/handlers/llm-providers.js +803 -580
  333. package/dist/server/handlers/llm-providers.js.map +1 -0
  334. package/dist/server/handlers/local-agent.js +133 -105
  335. package/dist/server/handlers/local-agent.js.map +1 -0
  336. package/dist/server/handlers/media.js +1179 -857
  337. package/dist/server/handlers/media.js.map +1 -0
  338. package/dist/server/handlers/meta-ads.js +2669 -2093
  339. package/dist/server/handlers/meta-ads.js.map +1 -0
  340. package/dist/server/handlers/nodes.js +1321 -913
  341. package/dist/server/handlers/nodes.js.map +1 -0
  342. package/dist/server/handlers/operations.js +183 -157
  343. package/dist/server/handlers/operations.js.map +1 -0
  344. package/dist/server/handlers/platform.js +346 -210
  345. package/dist/server/handlers/platform.js.map +1 -0
  346. package/dist/server/handlers/remove-bg.js +118 -86
  347. package/dist/server/handlers/remove-bg.js.map +1 -0
  348. package/dist/server/handlers/storefront.js +586 -446
  349. package/dist/server/handlers/storefront.js.map +1 -0
  350. package/dist/server/handlers/supply-chain.js +546 -326
  351. package/dist/server/handlers/supply-chain.js.map +1 -0
  352. package/dist/server/handlers/transcription.js +106 -97
  353. package/dist/server/handlers/transcription.js.map +1 -0
  354. package/dist/server/handlers/video-gen.js +593 -424
  355. package/dist/server/handlers/video-gen.js.map +1 -0
  356. package/dist/server/handlers/voice.js +1458 -1017
  357. package/dist/server/handlers/voice.js.map +1 -0
  358. package/dist/server/handlers/workflow-steps.js +2837 -2116
  359. package/dist/server/handlers/workflow-steps.js.map +1 -0
  360. package/dist/server/handlers/workflows.js +1630 -933
  361. package/dist/server/handlers/workflows.js.map +1 -0
  362. package/dist/server/index.js +3166 -2390
  363. package/dist/server/index.js.map +1 -0
  364. package/dist/server/lib/batch-client.js +471 -409
  365. package/dist/server/lib/batch-client.js.map +1 -0
  366. package/dist/server/lib/clickhouse-buffer.js +118 -104
  367. package/dist/server/lib/clickhouse-buffer.js.map +1 -0
  368. package/dist/server/lib/clickhouse-client.js +107 -107
  369. package/dist/server/lib/clickhouse-client.js.map +1 -0
  370. package/dist/server/lib/coa-renderer.js +1786 -356
  371. package/dist/server/lib/coa-renderer.js.map +1 -0
  372. package/dist/server/lib/code-worker-pool.js +227 -177
  373. package/dist/server/lib/code-worker-pool.js.map +1 -0
  374. package/dist/server/lib/code-worker.js +174 -164
  375. package/dist/server/lib/code-worker.js.map +1 -0
  376. package/dist/server/lib/compaction-service.d.ts +2 -12
  377. package/dist/server/lib/compaction-service.js +74 -184
  378. package/dist/server/lib/compaction-service.js.map +1 -0
  379. package/dist/server/lib/logger.js +36 -24
  380. package/dist/server/lib/logger.js.map +1 -0
  381. package/dist/server/lib/otel.js +101 -80
  382. package/dist/server/lib/otel.js.map +1 -0
  383. package/dist/server/lib/pdf-renderer.d.ts +1 -1
  384. package/dist/server/lib/pdf-renderer.js +954 -776
  385. package/dist/server/lib/pdf-renderer.js.map +1 -0
  386. package/dist/server/lib/prompt-sanitizer.js +188 -108
  387. package/dist/server/lib/prompt-sanitizer.js.map +1 -0
  388. package/dist/server/lib/provider-capabilities.js +136 -138
  389. package/dist/server/lib/provider-capabilities.js.map +1 -0
  390. package/dist/server/lib/provider-failover.js +190 -168
  391. package/dist/server/lib/provider-failover.js.map +1 -0
  392. package/dist/server/lib/rate-limiter.js +186 -117
  393. package/dist/server/lib/rate-limiter.js.map +1 -0
  394. package/dist/server/lib/react-pdf-layout.js +551 -382
  395. package/dist/server/lib/react-pdf-layout.js.map +1 -0
  396. package/dist/server/lib/server-agent-loop.d.ts +9 -0
  397. package/dist/server/lib/server-agent-loop.js +906 -624
  398. package/dist/server/lib/server-agent-loop.js.map +1 -0
  399. package/dist/server/lib/server-subagent.d.ts +2 -0
  400. package/dist/server/lib/server-subagent.js +260 -162
  401. package/dist/server/lib/server-subagent.js.map +1 -0
  402. package/dist/server/lib/session-checkpoint.js +105 -96
  403. package/dist/server/lib/session-checkpoint.js.map +1 -0
  404. package/dist/server/lib/ssrf-guard.js +193 -184
  405. package/dist/server/lib/ssrf-guard.js.map +1 -0
  406. package/dist/server/lib/supabase-client.js +94 -82
  407. package/dist/server/lib/supabase-client.js.map +1 -0
  408. package/dist/server/lib/template-resolver.js +154 -176
  409. package/dist/server/lib/template-resolver.js.map +1 -0
  410. package/dist/server/lib/utils.js +242 -133
  411. package/dist/server/lib/utils.js.map +1 -0
  412. package/dist/server/local-agent-gateway.d.ts +2 -2
  413. package/dist/server/local-agent-gateway.js +785 -627
  414. package/dist/server/local-agent-gateway.js.map +1 -0
  415. package/dist/server/providers/anthropic.js +254 -176
  416. package/dist/server/providers/anthropic.js.map +1 -0
  417. package/dist/server/providers/bedrock.js +221 -162
  418. package/dist/server/providers/bedrock.js.map +1 -0
  419. package/dist/server/providers/gemini.js +548 -418
  420. package/dist/server/providers/gemini.js.map +1 -0
  421. package/dist/server/providers/openai.js +571 -437
  422. package/dist/server/providers/openai.js.map +1 -0
  423. package/dist/server/providers/registry.js +23 -18
  424. package/dist/server/providers/registry.js.map +1 -0
  425. package/dist/server/providers/shared.js +123 -95
  426. package/dist/server/providers/shared.js.map +1 -0
  427. package/dist/server/providers/types.js +1 -11
  428. package/dist/server/providers/types.js.map +1 -0
  429. package/dist/server/proxy-handlers.js +209 -165
  430. package/dist/server/proxy-handlers.js.map +1 -0
  431. package/dist/server/tool-router.d.ts +13 -0
  432. package/dist/server/tool-router.js +960 -598
  433. package/dist/server/tool-router.js.map +1 -0
  434. package/dist/server/validation.js +248 -188
  435. package/dist/server/validation.js.map +1 -0
  436. package/dist/server/worker.js +202 -133
  437. package/dist/server/worker.js.map +1 -0
  438. package/dist/setup.d.ts +2 -2
  439. package/dist/setup.js +151 -147
  440. package/dist/setup.js.map +1 -0
  441. package/dist/shared/agent-core.d.ts +191 -24
  442. package/dist/shared/agent-core.js +971 -462
  443. package/dist/shared/agent-core.js.map +1 -0
  444. package/dist/shared/anthropic-types.js +1 -6
  445. package/dist/shared/anthropic-types.js.map +1 -0
  446. package/dist/shared/api-client.d.ts +17 -9
  447. package/dist/shared/api-client.js +419 -327
  448. package/dist/shared/api-client.js.map +1 -0
  449. package/dist/shared/compaction.d.ts +36 -0
  450. package/dist/shared/compaction.js +138 -0
  451. package/dist/shared/compaction.js.map +1 -0
  452. package/dist/shared/constants.js +67 -64
  453. package/dist/shared/constants.js.map +1 -0
  454. package/dist/shared/sse-parser.js +221 -219
  455. package/dist/shared/sse-parser.js.map +1 -0
  456. package/dist/shared/tool-dispatch.d.ts +4 -2
  457. package/dist/shared/tool-dispatch.js +226 -165
  458. package/dist/shared/tool-dispatch.js.map +1 -0
  459. package/dist/shared/types.js +1 -6
  460. package/dist/shared/types.js.map +1 -0
  461. package/dist/types/cli-highlight.d.js +2 -0
  462. package/dist/types/cli-highlight.d.js.map +1 -0
  463. package/dist/types/diff.d.js +2 -0
  464. package/dist/types/diff.d.js.map +1 -0
  465. package/dist/types/pdf-parse.d.js +2 -0
  466. package/dist/types/pdf-parse.d.js.map +1 -0
  467. package/dist/updater.d.ts +1 -1
  468. package/dist/updater.js +118 -92
  469. package/dist/updater.js.map +1 -0
  470. package/dist/webchat/widget.js +227 -380
  471. package/dist/webchat/widget.js.map +1 -0
  472. package/package.json +22 -10
  473. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  474. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  475. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  476. package/vendor/ink/build/apply-styles.js +175 -0
  477. package/vendor/ink/build/build-layout.js +77 -0
  478. package/vendor/ink/build/calculate-wrapped-text.js +53 -0
  479. package/vendor/ink/build/colorize.d.ts +3 -0
  480. package/vendor/ink/build/colorize.js +48 -0
  481. package/vendor/ink/build/colorize.js.map +1 -0
  482. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  483. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  484. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  485. package/vendor/ink/build/components/App.d.ts +18 -0
  486. package/vendor/ink/build/components/App.js +351 -0
  487. package/vendor/ink/build/components/App.js.map +1 -0
  488. package/vendor/ink/build/components/AppContext.d.ts +15 -0
  489. package/vendor/ink/build/components/AppContext.js +11 -0
  490. package/vendor/ink/build/components/AppContext.js.map +1 -0
  491. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  492. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  493. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  494. package/vendor/ink/build/components/Box.d.ts +117 -0
  495. package/vendor/ink/build/components/Box.js +34 -0
  496. package/vendor/ink/build/components/Box.js.map +1 -0
  497. package/vendor/ink/build/components/Color.js +62 -0
  498. package/vendor/ink/build/components/Cursor.d.ts +83 -0
  499. package/vendor/ink/build/components/Cursor.js +53 -0
  500. package/vendor/ink/build/components/Cursor.js.map +1 -0
  501. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  502. package/vendor/ink/build/components/CursorContext.js +8 -0
  503. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  504. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  505. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  506. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  507. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  508. package/vendor/ink/build/components/ErrorOverview.js +84 -0
  509. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  510. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  511. package/vendor/ink/build/components/FocusContext.js +17 -0
  512. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  513. package/vendor/ink/build/components/Newline.d.ts +13 -0
  514. package/vendor/ink/build/components/Newline.js +8 -0
  515. package/vendor/ink/build/components/Newline.js.map +1 -0
  516. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  517. package/vendor/ink/build/components/Spacer.js +11 -0
  518. package/vendor/ink/build/components/Spacer.js.map +1 -0
  519. package/vendor/ink/build/components/Static.d.ts +24 -0
  520. package/vendor/ink/build/components/Static.js +28 -0
  521. package/vendor/ink/build/components/Static.js.map +1 -0
  522. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  523. package/vendor/ink/build/components/StderrContext.js +13 -0
  524. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  525. package/vendor/ink/build/components/StdinContext.d.ts +22 -0
  526. package/vendor/ink/build/components/StdinContext.js +19 -0
  527. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  528. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  529. package/vendor/ink/build/components/StdoutContext.js +13 -0
  530. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  531. package/vendor/ink/build/components/Text.d.ts +55 -0
  532. package/vendor/ink/build/components/Text.js +50 -0
  533. package/vendor/ink/build/components/Text.js.map +1 -0
  534. package/vendor/ink/build/components/Transform.d.ts +16 -0
  535. package/vendor/ink/build/components/Transform.js +15 -0
  536. package/vendor/ink/build/components/Transform.js.map +1 -0
  537. package/vendor/ink/build/cursor-helpers.d.ts +38 -0
  538. package/vendor/ink/build/cursor-helpers.js +56 -0
  539. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  540. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  541. package/vendor/ink/build/devtools-window-polyfill.js +65 -0
  542. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  543. package/vendor/ink/build/devtools.d.ts +1 -0
  544. package/vendor/ink/build/devtools.js +11 -0
  545. package/vendor/ink/build/devtools.js.map +1 -0
  546. package/vendor/ink/build/dom.d.ts +56 -0
  547. package/vendor/ink/build/dom.js +124 -0
  548. package/vendor/ink/build/dom.js.map +1 -0
  549. package/vendor/ink/build/experimental/apply-style.js +140 -0
  550. package/vendor/ink/build/experimental/dom.js +123 -0
  551. package/vendor/ink/build/experimental/output.js +91 -0
  552. package/vendor/ink/build/experimental/reconciler.js +141 -0
  553. package/vendor/ink/build/experimental/renderer.js +81 -0
  554. package/vendor/ink/build/get-max-width.d.ts +3 -0
  555. package/vendor/ink/build/get-max-width.js +10 -0
  556. package/vendor/ink/build/get-max-width.js.map +1 -0
  557. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  558. package/vendor/ink/build/hooks/use-app.js +8 -0
  559. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  560. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  561. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  562. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  563. package/vendor/ink/build/hooks/use-focus-manager.d.ts +28 -0
  564. package/vendor/ink/build/hooks/use-focus-manager.js +17 -0
  565. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  566. package/vendor/ink/build/hooks/use-focus.d.ts +29 -0
  567. package/vendor/ink/build/hooks/use-focus.js +42 -0
  568. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  569. package/vendor/ink/build/hooks/use-input.d.ts +131 -0
  570. package/vendor/ink/build/hooks/use-input.js +124 -0
  571. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  572. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +5 -0
  573. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +11 -0
  574. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  575. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  576. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  577. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  578. package/vendor/ink/build/hooks/use-stdin.d.ts +5 -0
  579. package/vendor/ink/build/hooks/use-stdin.js +8 -0
  580. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  581. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  582. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  583. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  584. package/vendor/ink/build/hooks/useInput.js +38 -0
  585. package/vendor/ink/build/index.d.ts +34 -0
  586. package/vendor/ink/build/index.js +20 -0
  587. package/vendor/ink/build/index.js.map +1 -0
  588. package/vendor/ink/build/ink.d.ts +90 -0
  589. package/vendor/ink/build/ink.js +654 -0
  590. package/vendor/ink/build/ink.js.map +1 -0
  591. package/vendor/ink/build/input-parser.d.ts +7 -0
  592. package/vendor/ink/build/input-parser.js +154 -0
  593. package/vendor/ink/build/input-parser.js.map +1 -0
  594. package/vendor/ink/build/instance.js +205 -0
  595. package/vendor/ink/build/instances.d.ts +3 -0
  596. package/vendor/ink/build/instances.js +8 -0
  597. package/vendor/ink/build/instances.js.map +1 -0
  598. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  599. package/vendor/ink/build/kitty-keyboard.js +32 -0
  600. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  601. package/vendor/ink/build/layout.d.ts +7 -0
  602. package/vendor/ink/build/layout.js +33 -0
  603. package/vendor/ink/build/layout.js.map +1 -0
  604. package/vendor/ink/build/log-update.d.ts +19 -0
  605. package/vendor/ink/build/log-update.js +243 -0
  606. package/vendor/ink/build/log-update.js.map +1 -0
  607. package/vendor/ink/build/measure-element.d.ts +16 -0
  608. package/vendor/ink/build/measure-element.js +9 -0
  609. package/vendor/ink/build/measure-element.js.map +1 -0
  610. package/vendor/ink/build/measure-text.d.ts +6 -0
  611. package/vendor/ink/build/measure-text.js +21 -0
  612. package/vendor/ink/build/measure-text.js.map +1 -0
  613. package/vendor/ink/build/options.d.ts +52 -0
  614. package/vendor/ink/build/options.js +2 -0
  615. package/vendor/ink/build/options.js.map +1 -0
  616. package/vendor/ink/build/output.d.ts +35 -0
  617. package/vendor/ink/build/output.js +183 -0
  618. package/vendor/ink/build/output.js.map +1 -0
  619. package/vendor/ink/build/parse-keypress.d.ts +22 -0
  620. package/vendor/ink/build/parse-keypress.js +493 -0
  621. package/vendor/ink/build/parse-keypress.js.map +1 -0
  622. package/vendor/ink/build/reconciler.d.ts +4 -0
  623. package/vendor/ink/build/reconciler.js +274 -0
  624. package/vendor/ink/build/reconciler.js.map +1 -0
  625. package/vendor/ink/build/render-background.d.ts +4 -0
  626. package/vendor/ink/build/render-background.js +25 -0
  627. package/vendor/ink/build/render-background.js.map +1 -0
  628. package/vendor/ink/build/render-border.d.ts +4 -0
  629. package/vendor/ink/build/render-border.js +73 -0
  630. package/vendor/ink/build/render-border.js.map +1 -0
  631. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  632. package/vendor/ink/build/render-node-to-output.js +147 -0
  633. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  634. package/vendor/ink/build/render-to-string.d.ts +38 -0
  635. package/vendor/ink/build/render-to-string.js +115 -0
  636. package/vendor/ink/build/render-to-string.js.map +1 -0
  637. package/vendor/ink/build/render.d.ts +121 -0
  638. package/vendor/ink/build/render.js +55 -0
  639. package/vendor/ink/build/render.js.map +1 -0
  640. package/vendor/ink/build/renderer.d.ts +8 -0
  641. package/vendor/ink/build/renderer.js +55 -0
  642. package/vendor/ink/build/renderer.js.map +1 -0
  643. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  644. package/vendor/ink/build/sanitize-ansi.js +27 -0
  645. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  646. package/vendor/ink/build/screen-reader-update.d.ts +13 -0
  647. package/vendor/ink/build/screen-reader-update.js +38 -0
  648. package/vendor/ink/build/screen-reader-update.js.map +1 -0
  649. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  650. package/vendor/ink/build/squash-text-nodes.js +36 -0
  651. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  652. package/vendor/ink/build/styles.d.ts +240 -0
  653. package/vendor/ink/build/styles.js +232 -0
  654. package/vendor/ink/build/styles.js.map +1 -0
  655. package/vendor/ink/build/utils.d.ts +2 -0
  656. package/vendor/ink/build/utils.js +4 -0
  657. package/vendor/ink/build/utils.js.map +1 -0
  658. package/vendor/ink/build/wrap-text.d.ts +3 -0
  659. package/vendor/ink/build/wrap-text.js +31 -0
  660. package/vendor/ink/build/wrap-text.js.map +1 -0
  661. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  662. package/vendor/ink/build/write-synchronized.js +7 -0
  663. package/vendor/ink/build/write-synchronized.js.map +1 -0
  664. package/vendor/ink/license +10 -0
  665. package/vendor/ink/node_modules/@types/node/LICENSE +21 -0
  666. package/vendor/ink/node_modules/@types/node/README.md +15 -0
  667. package/vendor/ink/node_modules/@types/node/assert/strict.d.ts +105 -0
  668. package/vendor/ink/node_modules/@types/node/assert.d.ts +955 -0
  669. package/vendor/ink/node_modules/@types/node/async_hooks.d.ts +623 -0
  670. package/vendor/ink/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  671. package/vendor/ink/node_modules/@types/node/buffer.d.ts +1810 -0
  672. package/vendor/ink/node_modules/@types/node/child_process.d.ts +1428 -0
  673. package/vendor/ink/node_modules/@types/node/cluster.d.ts +486 -0
  674. package/vendor/ink/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  675. package/vendor/ink/node_modules/@types/node/console.d.ts +151 -0
  676. package/vendor/ink/node_modules/@types/node/constants.d.ts +20 -0
  677. package/vendor/ink/node_modules/@types/node/crypto.d.ts +4065 -0
  678. package/vendor/ink/node_modules/@types/node/dgram.d.ts +564 -0
  679. package/vendor/ink/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  680. package/vendor/ink/node_modules/@types/node/dns/promises.d.ts +503 -0
  681. package/vendor/ink/node_modules/@types/node/dns.d.ts +922 -0
  682. package/vendor/ink/node_modules/@types/node/domain.d.ts +166 -0
  683. package/vendor/ink/node_modules/@types/node/events.d.ts +1054 -0
  684. package/vendor/ink/node_modules/@types/node/fs/promises.d.ts +1329 -0
  685. package/vendor/ink/node_modules/@types/node/fs.d.ts +4676 -0
  686. package/vendor/ink/node_modules/@types/node/globals.d.ts +150 -0
  687. package/vendor/ink/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  688. package/vendor/ink/node_modules/@types/node/http.d.ts +2167 -0
  689. package/vendor/ink/node_modules/@types/node/http2.d.ts +2480 -0
  690. package/vendor/ink/node_modules/@types/node/https.d.ts +405 -0
  691. package/vendor/ink/node_modules/@types/node/index.d.ts +115 -0
  692. package/vendor/ink/node_modules/@types/node/inspector/promises.d.ts +41 -0
  693. package/vendor/ink/node_modules/@types/node/inspector.d.ts +224 -0
  694. package/vendor/ink/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  695. package/vendor/ink/node_modules/@types/node/module.d.ts +819 -0
  696. package/vendor/ink/node_modules/@types/node/net.d.ts +933 -0
  697. package/vendor/ink/node_modules/@types/node/os.d.ts +507 -0
  698. package/vendor/ink/node_modules/@types/node/package.json +155 -0
  699. package/vendor/ink/node_modules/@types/node/path/posix.d.ts +8 -0
  700. package/vendor/ink/node_modules/@types/node/path/win32.d.ts +8 -0
  701. package/vendor/ink/node_modules/@types/node/path.d.ts +187 -0
  702. package/vendor/ink/node_modules/@types/node/perf_hooks.d.ts +643 -0
  703. package/vendor/ink/node_modules/@types/node/process.d.ts +2156 -0
  704. package/vendor/ink/node_modules/@types/node/punycode.d.ts +117 -0
  705. package/vendor/ink/node_modules/@types/node/querystring.d.ts +152 -0
  706. package/vendor/ink/node_modules/@types/node/quic.d.ts +910 -0
  707. package/vendor/ink/node_modules/@types/node/readline/promises.d.ts +161 -0
  708. package/vendor/ink/node_modules/@types/node/readline.d.ts +541 -0
  709. package/vendor/ink/node_modules/@types/node/repl.d.ts +415 -0
  710. package/vendor/ink/node_modules/@types/node/sea.d.ts +162 -0
  711. package/vendor/ink/node_modules/@types/node/sqlite.d.ts +955 -0
  712. package/vendor/ink/node_modules/@types/node/stream/consumers.d.ts +38 -0
  713. package/vendor/ink/node_modules/@types/node/stream/promises.d.ts +211 -0
  714. package/vendor/ink/node_modules/@types/node/stream/web.d.ts +296 -0
  715. package/vendor/ink/node_modules/@types/node/stream.d.ts +1760 -0
  716. package/vendor/ink/node_modules/@types/node/string_decoder.d.ts +67 -0
  717. package/vendor/ink/node_modules/@types/node/test/reporters.d.ts +96 -0
  718. package/vendor/ink/node_modules/@types/node/test.d.ts +2240 -0
  719. package/vendor/ink/node_modules/@types/node/timers/promises.d.ts +108 -0
  720. package/vendor/ink/node_modules/@types/node/timers.d.ts +159 -0
  721. package/vendor/ink/node_modules/@types/node/tls.d.ts +1198 -0
  722. package/vendor/ink/node_modules/@types/node/trace_events.d.ts +197 -0
  723. package/vendor/ink/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  724. package/vendor/ink/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  725. package/vendor/ink/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  726. package/vendor/ink/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  727. package/vendor/ink/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  728. package/vendor/ink/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  729. package/vendor/ink/node_modules/@types/node/tty.d.ts +250 -0
  730. package/vendor/ink/node_modules/@types/node/url.d.ts +519 -0
  731. package/vendor/ink/node_modules/@types/node/util/types.d.ts +558 -0
  732. package/vendor/ink/node_modules/@types/node/util.d.ts +1662 -0
  733. package/vendor/ink/node_modules/@types/node/v8.d.ts +983 -0
  734. package/vendor/ink/node_modules/@types/node/vm.d.ts +1208 -0
  735. package/vendor/ink/node_modules/@types/node/wasi.d.ts +202 -0
  736. package/vendor/ink/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  737. package/vendor/ink/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  738. package/vendor/ink/node_modules/@types/node/web-globals/console.d.ts +9 -0
  739. package/vendor/ink/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  740. package/vendor/ink/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  741. package/vendor/ink/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  742. package/vendor/ink/node_modules/@types/node/web-globals/events.d.ts +106 -0
  743. package/vendor/ink/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
  744. package/vendor/ink/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  745. package/vendor/ink/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  746. package/vendor/ink/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  747. package/vendor/ink/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  748. package/vendor/ink/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  749. package/vendor/ink/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  750. package/vendor/ink/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  751. package/vendor/ink/node_modules/@types/node/web-globals/url.d.ts +24 -0
  752. package/vendor/ink/node_modules/@types/node/worker_threads.d.ts +717 -0
  753. package/vendor/ink/node_modules/@types/node/zlib.d.ts +618 -0
  754. package/vendor/ink/node_modules/node-pty/LICENSE +69 -0
  755. package/vendor/ink/node_modules/node-pty/README.md +164 -0
  756. package/vendor/ink/node_modules/node-pty/binding.gyp +150 -0
  757. package/vendor/ink/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  758. package/vendor/ink/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  759. package/vendor/ink/node_modules/node-pty/lib/index.js +52 -0
  760. package/vendor/ink/node_modules/node-pty/lib/interfaces.js +7 -0
  761. package/vendor/ink/node_modules/node-pty/lib/shared/conout.js +11 -0
  762. package/vendor/ink/node_modules/node-pty/lib/terminal.js +190 -0
  763. package/vendor/ink/node_modules/node-pty/lib/types.js +7 -0
  764. package/vendor/ink/node_modules/node-pty/lib/unixTerminal.js +349 -0
  765. package/vendor/ink/node_modules/node-pty/lib/utils.js +39 -0
  766. package/vendor/ink/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  767. package/vendor/ink/node_modules/node-pty/lib/windowsPtyAgent.js +287 -0
  768. package/vendor/ink/node_modules/node-pty/lib/windowsTerminal.js +201 -0
  769. package/vendor/ink/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  770. package/vendor/ink/node_modules/node-pty/package.json +65 -0
  771. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  772. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  773. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  774. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  775. package/vendor/ink/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  776. package/vendor/ink/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  777. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  778. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  779. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  780. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  781. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  782. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  783. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  784. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  785. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  786. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  787. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  788. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  789. package/vendor/ink/node_modules/node-pty/scripts/post-install.js +76 -0
  790. package/vendor/ink/node_modules/node-pty/scripts/prebuild.js +34 -0
  791. package/vendor/ink/node_modules/node-pty/src/unix/pty.cc +875 -0
  792. package/vendor/ink/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  793. package/vendor/ink/node_modules/node-pty/src/win/conpty.cc +582 -0
  794. package/vendor/ink/node_modules/node-pty/src/win/conpty.h +41 -0
  795. package/vendor/ink/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  796. package/vendor/ink/node_modules/node-pty/src/win/path_util.cc +95 -0
  797. package/vendor/ink/node_modules/node-pty/src/win/path_util.h +26 -0
  798. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  799. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  800. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  801. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  802. package/vendor/ink/node_modules/node-pty/typings/node-pty.d.ts +215 -0
  803. package/vendor/ink/node_modules/undici-types/LICENSE +21 -0
  804. package/vendor/ink/node_modules/undici-types/README.md +6 -0
  805. package/vendor/ink/node_modules/undici-types/agent.d.ts +32 -0
  806. package/vendor/ink/node_modules/undici-types/api.d.ts +43 -0
  807. package/vendor/ink/node_modules/undici-types/balanced-pool.d.ts +30 -0
  808. package/vendor/ink/node_modules/undici-types/cache-interceptor.d.ts +173 -0
  809. package/vendor/ink/node_modules/undici-types/cache.d.ts +36 -0
  810. package/vendor/ink/node_modules/undici-types/client-stats.d.ts +15 -0
  811. package/vendor/ink/node_modules/undici-types/client.d.ts +108 -0
  812. package/vendor/ink/node_modules/undici-types/connector.d.ts +34 -0
  813. package/vendor/ink/node_modules/undici-types/content-type.d.ts +21 -0
  814. package/vendor/ink/node_modules/undici-types/cookies.d.ts +30 -0
  815. package/vendor/ink/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  816. package/vendor/ink/node_modules/undici-types/dispatcher.d.ts +276 -0
  817. package/vendor/ink/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  818. package/vendor/ink/node_modules/undici-types/errors.d.ts +161 -0
  819. package/vendor/ink/node_modules/undici-types/eventsource.d.ts +66 -0
  820. package/vendor/ink/node_modules/undici-types/fetch.d.ts +211 -0
  821. package/vendor/ink/node_modules/undici-types/formdata.d.ts +108 -0
  822. package/vendor/ink/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  823. package/vendor/ink/node_modules/undici-types/global-origin.d.ts +7 -0
  824. package/vendor/ink/node_modules/undici-types/h2c-client.d.ts +73 -0
  825. package/vendor/ink/node_modules/undici-types/handlers.d.ts +15 -0
  826. package/vendor/ink/node_modules/undici-types/header.d.ts +160 -0
  827. package/vendor/ink/node_modules/undici-types/index.d.ts +88 -0
  828. package/vendor/ink/node_modules/undici-types/interceptors.d.ts +73 -0
  829. package/vendor/ink/node_modules/undici-types/mock-agent.d.ts +68 -0
  830. package/vendor/ink/node_modules/undici-types/mock-call-history.d.ts +111 -0
  831. package/vendor/ink/node_modules/undici-types/mock-client.d.ts +27 -0
  832. package/vendor/ink/node_modules/undici-types/mock-errors.d.ts +12 -0
  833. package/vendor/ink/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  834. package/vendor/ink/node_modules/undici-types/mock-pool.d.ts +27 -0
  835. package/vendor/ink/node_modules/undici-types/package.json +55 -0
  836. package/vendor/ink/node_modules/undici-types/patch.d.ts +29 -0
  837. package/vendor/ink/node_modules/undici-types/pool-stats.d.ts +19 -0
  838. package/vendor/ink/node_modules/undici-types/pool.d.ts +41 -0
  839. package/vendor/ink/node_modules/undici-types/proxy-agent.d.ts +29 -0
  840. package/vendor/ink/node_modules/undici-types/readable.d.ts +68 -0
  841. package/vendor/ink/node_modules/undici-types/retry-agent.d.ts +8 -0
  842. package/vendor/ink/node_modules/undici-types/retry-handler.d.ts +125 -0
  843. package/vendor/ink/node_modules/undici-types/round-robin-pool.d.ts +41 -0
  844. package/vendor/ink/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  845. package/vendor/ink/node_modules/undici-types/util.d.ts +18 -0
  846. package/vendor/ink/node_modules/undici-types/utility.d.ts +7 -0
  847. package/vendor/ink/node_modules/undici-types/webidl.d.ts +341 -0
  848. package/vendor/ink/node_modules/undici-types/websocket.d.ts +186 -0
  849. package/vendor/ink/package.json +201 -0
  850. package/vendor/ink/readme.md +2636 -0
  851. package/bin/swag-agent.js +0 -9
  852. package/dist/server/lib/pg-rate-limiter.d.ts +0 -21
  853. package/dist/server/lib/pg-rate-limiter.js +0 -86
@@ -12,9 +12,11 @@
12
12
  // composition_plan support on music_compose, 192kbps music output
13
13
  // v2: DRY helpers, credential caching, voice name resolution, batch TTS,
14
14
  // retry with backoff, updated models (eleven_v3, scribe_v2)
15
+
15
16
  // ============================================================================
16
17
  // CONSTANTS
17
18
  // ============================================================================
19
+
18
20
  const ELEVENLABS_BASE = "https://api.elevenlabs.io/v1";
19
21
  const DEFAULT_VOICE_ID = "21m00Tcm4TlvDq8ikWAM"; // Rachel
20
22
  const DEFAULT_TTS_MODEL = "eleven_v3";
@@ -27,1120 +29,1559 @@ const MAX_RETRIES = 2;
27
29
  const RETRY_DELAY_MS = 1000;
28
30
  const VOICE_CACHE_TTL = 600_000; // 10 min
29
31
  const KEY_CACHE_TTL = 300_000; // 5 min
32
+
30
33
  // ============================================================================
31
34
  // CREDENTIAL CACHING — 5-min TTL, avoids decrypt RPC on every call
32
35
  // ============================================================================
36
+
33
37
  const keyCache = new Map();
34
38
  async function getElevenLabsKey(sb, storeId) {
35
- const cached = keyCache.get(storeId);
36
- if (cached && Date.now() - cached.fetchedAt < KEY_CACHE_TTL)
37
- return cached.key;
38
- const { data, error } = await sb.rpc("decrypt_secret", {
39
- p_name: "ELEVENLABS_API_KEY",
40
- p_store_id: storeId,
41
- });
42
- if (error || !data)
43
- return null;
44
- const key = data;
45
- keyCache.set(storeId, { key, fetchedAt: Date.now() });
46
- if (keyCache.size > 50) {
47
- // Evict oldest entry by fetchedAt, not insertion order
48
- let oldestKey;
49
- let oldestTime = Infinity;
50
- for (const [k, v] of keyCache) {
51
- if (v.fetchedAt < oldestTime) {
52
- oldestTime = v.fetchedAt;
53
- oldestKey = k;
54
- }
55
- }
56
- if (oldestKey)
57
- keyCache.delete(oldestKey);
39
+ const cached = keyCache.get(storeId);
40
+ if (cached && Date.now() - cached.fetchedAt < KEY_CACHE_TTL) return cached.key;
41
+ const {
42
+ data,
43
+ error
44
+ } = await sb.rpc("decrypt_secret", {
45
+ p_name: "ELEVENLABS_API_KEY",
46
+ p_store_id: storeId
47
+ });
48
+ if (error || !data) return null;
49
+ const key = data;
50
+ keyCache.set(storeId, {
51
+ key,
52
+ fetchedAt: Date.now()
53
+ });
54
+ if (keyCache.size > 50) {
55
+ // Evict oldest entry by fetchedAt, not insertion order
56
+ let oldestKey;
57
+ let oldestTime = Infinity;
58
+ for (const [k, v] of keyCache) {
59
+ if (v.fetchedAt < oldestTime) {
60
+ oldestTime = v.fetchedAt;
61
+ oldestKey = k;
62
+ }
58
63
  }
59
- return key;
64
+ if (oldestKey) keyCache.delete(oldestKey);
65
+ }
66
+ return key;
60
67
  }
68
+
61
69
  // ============================================================================
62
70
  // VOICE NAME RESOLUTION — accept name or ID, cache voice list 10 min
63
71
  // ============================================================================
72
+
64
73
  const voiceListCache = new Map();
74
+
65
75
  /** Popular voice presets for quick lookup without API call */
66
76
  const VOICE_PRESETS = {
67
- rachel: "21m00Tcm4TlvDq8ikWAM",
68
- daniel: "onwK4e9ZLuTAKqWW03F9",
69
- roger: "CwhRBWXzGAHq8TQ4Fs17",
70
- sarah: "EXAVITQu4vr4xnSDxMaL",
71
- adam: "pNInz6obpgDQGcFmaJgB",
72
- charlie: "IKne3meq5aSn9XLyUdCD",
73
- emily: "LcfcDJNUP1GQjkzn1xUU",
74
- josh: "TxGEqnHWrfWFTfGW9XjX",
77
+ rachel: "21m00Tcm4TlvDq8ikWAM",
78
+ daniel: "onwK4e9ZLuTAKqWW03F9",
79
+ roger: "CwhRBWXzGAHq8TQ4Fs17",
80
+ sarah: "EXAVITQu4vr4xnSDxMaL",
81
+ adam: "pNInz6obpgDQGcFmaJgB",
82
+ charlie: "IKne3meq5aSn9XLyUdCD",
83
+ emily: "LcfcDJNUP1GQjkzn1xUU",
84
+ josh: "TxGEqnHWrfWFTfGW9XjX"
75
85
  };
76
86
  async function resolveVoiceId(nameOrId, apiKey, storeId) {
77
- // Already a voice ID (UUID-like or 20+ char alphanumeric)?
78
- if (/^[a-zA-Z0-9]{20,}$/.test(nameOrId))
79
- return nameOrId;
80
- // Check presets first (instant, no API call)
81
- const preset = VOICE_PRESETS[nameOrId.toLowerCase()];
82
- if (preset)
83
- return preset;
84
- // Fetch and cache voice list from ElevenLabs
85
- let voices = voiceListCache.get(storeId);
86
- if (!voices || Date.now() - voices.fetchedAt > VOICE_CACHE_TTL) {
87
- try {
88
- const resp = await fetch(`${ELEVENLABS_BASE}/voices`, {
89
- headers: { "xi-api-key": apiKey },
90
- });
91
- if (resp.ok) {
92
- const data = await resp.json();
93
- const list = (data.voices || []).map((v) => ({ voice_id: v.voice_id, name: v.name }));
94
- voices = { voices: list, fetchedAt: Date.now() };
95
- voiceListCache.set(storeId, voices);
96
- if (voiceListCache.size > 50) {
97
- let oldestKey;
98
- let oldestTime = Infinity;
99
- for (const [k, v] of voiceListCache) {
100
- if (v.fetchedAt < oldestTime) {
101
- oldestTime = v.fetchedAt;
102
- oldestKey = k;
103
- }
104
- }
105
- if (oldestKey)
106
- voiceListCache.delete(oldestKey);
107
- }
87
+ // Already a voice ID (UUID-like or 20+ char alphanumeric)?
88
+ if (/^[a-zA-Z0-9]{20,}$/.test(nameOrId)) return nameOrId;
89
+
90
+ // Check presets first (instant, no API call)
91
+ const preset = VOICE_PRESETS[nameOrId.toLowerCase()];
92
+ if (preset) return preset;
93
+
94
+ // Fetch and cache voice list from ElevenLabs
95
+ let voices = voiceListCache.get(storeId);
96
+ if (!voices || Date.now() - voices.fetchedAt > VOICE_CACHE_TTL) {
97
+ try {
98
+ const resp = await fetch(`${ELEVENLABS_BASE}/voices`, {
99
+ headers: {
100
+ "xi-api-key": apiKey
101
+ }
102
+ });
103
+ if (resp.ok) {
104
+ const data = await resp.json();
105
+ const list = (data.voices || []).map(v => ({
106
+ voice_id: v.voice_id,
107
+ name: v.name
108
+ }));
109
+ voices = {
110
+ voices: list,
111
+ fetchedAt: Date.now()
112
+ };
113
+ voiceListCache.set(storeId, voices);
114
+ if (voiceListCache.size > 50) {
115
+ let oldestKey;
116
+ let oldestTime = Infinity;
117
+ for (const [k, v] of voiceListCache) {
118
+ if (v.fetchedAt < oldestTime) {
119
+ oldestTime = v.fetchedAt;
120
+ oldestKey = k;
108
121
  }
122
+ }
123
+ if (oldestKey) voiceListCache.delete(oldestKey);
109
124
  }
110
- catch { /* fall through to fuzzy match on stale cache */ }
111
- }
112
- if (voices) {
113
- const lower = nameOrId.toLowerCase();
114
- // Exact match
115
- const exact = voices.voices.find(v => v.name.toLowerCase() === lower);
116
- if (exact)
117
- return exact.voice_id;
118
- // Starts-with match
119
- const starts = voices.voices.find(v => v.name.toLowerCase().startsWith(lower));
120
- if (starts)
121
- return starts.voice_id;
122
- // Contains match
123
- const contains = voices.voices.find(v => v.name.toLowerCase().includes(lower));
124
- if (contains)
125
- return contains.voice_id;
126
- }
127
- // Fall back — treat as voice ID (let ElevenLabs return 404 if invalid)
128
- return nameOrId;
125
+ }
126
+ } catch {/* fall through to fuzzy match on stale cache */}
127
+ }
128
+ if (voices) {
129
+ const lower = nameOrId.toLowerCase();
130
+ // Exact match
131
+ const exact = voices.voices.find(v => v.name.toLowerCase() === lower);
132
+ if (exact) return exact.voice_id;
133
+ // Starts-with match
134
+ const starts = voices.voices.find(v => v.name.toLowerCase().startsWith(lower));
135
+ if (starts) return starts.voice_id;
136
+ // Contains match
137
+ const contains = voices.voices.find(v => v.name.toLowerCase().includes(lower));
138
+ if (contains) return contains.voice_id;
139
+ }
140
+
141
+ // Fall back — treat as voice ID (let ElevenLabs return 404 if invalid)
142
+ return nameOrId;
129
143
  }
144
+
145
+ // ============================================================================
146
+ // STORAGE — upload audio to Supabase Storage + store_media
147
+ // ============================================================================
148
+
130
149
  async function uploadAudioAndRecord(sb, storeId, audioBuffer, label, ext, mimeType, aiTags) {
131
- const id = crypto.randomUUID();
132
- const fileName = `${id}.${ext}`;
133
- const storagePath = `ai-generated/${storeId.toUpperCase()}/audio/${fileName}`;
134
- const { error: uploadErr } = await sb.storage
135
- .from("store-media")
136
- .upload(storagePath, audioBuffer, { contentType: mimeType, upsert: true });
137
- if (uploadErr)
138
- throw new Error(`Storage upload failed: ${uploadErr.message}`);
139
- const { data: urlData } = sb.storage.from("store-media").getPublicUrl(storagePath);
140
- const fileUrl = urlData.publicUrl;
141
- const { data: mediaRow, error: mediaErr } = await sb
142
- .from("store_media")
143
- .insert({
144
- store_id: storeId, file_name: fileName, file_path: storagePath,
145
- file_url: fileUrl, file_size: audioBuffer.length, file_type: mimeType,
146
- category: "ai_generated", ai_tags: aiTags, ai_description: label, source: "elevenlabs",
147
- })
148
- .select("id")
149
- .single();
150
- if (mediaErr)
151
- console.error("[voice] store_media insert error:", mediaErr.message);
152
- return { file_url: fileUrl, file_name: fileName, file_size: audioBuffer.length, media_id: mediaRow?.id || id };
150
+ const id = crypto.randomUUID();
151
+ const fileName = `${id}.${ext}`;
152
+ const storagePath = `ai-generated/${storeId.toUpperCase()}/audio/${fileName}`;
153
+ const {
154
+ error: uploadErr
155
+ } = await sb.storage.from("store-media").upload(storagePath, audioBuffer, {
156
+ contentType: mimeType,
157
+ upsert: true
158
+ });
159
+ if (uploadErr) throw new Error(`Storage upload failed: ${uploadErr.message}`);
160
+ const {
161
+ data: urlData
162
+ } = sb.storage.from("store-media").getPublicUrl(storagePath);
163
+ const fileUrl = urlData.publicUrl;
164
+ const {
165
+ data: mediaRow,
166
+ error: mediaErr
167
+ } = await sb.from("store_media").insert({
168
+ store_id: storeId,
169
+ file_name: fileName,
170
+ file_path: storagePath,
171
+ file_url: fileUrl,
172
+ file_size: audioBuffer.length,
173
+ file_type: mimeType,
174
+ category: "ai_generated",
175
+ ai_tags: aiTags,
176
+ ai_description: label,
177
+ source: "elevenlabs"
178
+ }).select("id").single();
179
+ if (mediaErr) console.error("[voice] store_media insert error:", mediaErr.message);
180
+ return {
181
+ file_url: fileUrl,
182
+ file_name: fileName,
183
+ file_size: audioBuffer.length,
184
+ media_id: mediaRow?.id || id
185
+ };
153
186
  }
187
+
154
188
  // ============================================================================
155
189
  // DRY HELPERS — eliminate 90% of code duplication
156
190
  // ============================================================================
191
+
157
192
  function formatInfo(outputFormat) {
158
- if (outputFormat.startsWith("pcm"))
159
- return { ext: "pcm", mime: "audio/pcm" };
160
- if (outputFormat.startsWith("opus"))
161
- return { ext: "opus", mime: "audio/opus" };
162
- return { ext: "mp3", mime: "audio/mpeg" };
193
+ if (outputFormat.startsWith("pcm")) return {
194
+ ext: "pcm",
195
+ mime: "audio/pcm"
196
+ };
197
+ if (outputFormat.startsWith("opus")) return {
198
+ ext: "opus",
199
+ mime: "audio/opus"
200
+ };
201
+ return {
202
+ ext: "mp3",
203
+ mime: "audio/mpeg"
204
+ };
163
205
  }
206
+
164
207
  /** Fetch with retry for ElevenLabs API (handles 429/500/502/503) */
165
208
  async function fetchWithRetry(url, init) {
166
- let lastErr = null;
167
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
168
- try {
169
- const resp = await fetch(url, init);
170
- if (resp.ok || (resp.status < 500 && resp.status !== 429))
171
- return resp;
172
- if (attempt < MAX_RETRIES) {
173
- const delay = RETRY_DELAY_MS * Math.pow(2, attempt);
174
- await new Promise(r => setTimeout(r, delay));
175
- continue;
176
- }
177
- return resp; // final attempt, return whatever we got
178
- }
179
- catch (err) {
180
- lastErr = err;
181
- if (attempt < MAX_RETRIES) {
182
- await new Promise(r => setTimeout(r, RETRY_DELAY_MS * Math.pow(2, attempt)));
183
- }
184
- }
209
+ let lastErr = null;
210
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
211
+ try {
212
+ const resp = await fetch(url, init);
213
+ if (resp.ok || resp.status < 500 && resp.status !== 429) return resp;
214
+ if (attempt < MAX_RETRIES) {
215
+ const delay = RETRY_DELAY_MS * Math.pow(2, attempt);
216
+ await new Promise(r => setTimeout(r, delay));
217
+ continue;
218
+ }
219
+ return resp; // final attempt, return whatever we got
220
+ } catch (err) {
221
+ lastErr = err;
222
+ if (attempt < MAX_RETRIES) {
223
+ await new Promise(r => setTimeout(r, RETRY_DELAY_MS * Math.pow(2, attempt)));
224
+ }
185
225
  }
186
- throw lastErr || new Error("Fetch failed after retries");
226
+ }
227
+ throw lastErr || new Error("Fetch failed after retries");
187
228
  }
229
+
188
230
  /** API call → buffer → upload → standard result */
189
231
  async function generateAndUpload(sb, storeId, apiKey, url, body, label, tags, ext, mime, extraFields = {}) {
190
- try {
191
- const resp = await fetchWithRetry(url, {
192
- method: "POST",
193
- headers: { "xi-api-key": apiKey, "Content-Type": "application/json", Accept: "audio/mpeg" },
194
- body: JSON.stringify(body),
195
- });
196
- if (!resp.ok) {
197
- const errText = await resp.text();
198
- return { success: false, error: `ElevenLabs error ${resp.status}: ${errText}` };
199
- }
200
- const audioBuffer = Buffer.from(await resp.arrayBuffer());
201
- const upload = await uploadAudioAndRecord(sb, storeId, audioBuffer, label, ext, mime, tags);
202
- return {
203
- success: true,
204
- data: {
205
- file_url: upload.file_url,
206
- file_size: upload.file_size,
207
- media_id: upload.media_id,
208
- format: ext,
209
- ...extraFields,
210
- },
211
- };
212
- }
213
- catch (err) {
214
- return { success: false, error: `${label} failed: ${err}` };
232
+ try {
233
+ const resp = await fetchWithRetry(url, {
234
+ method: "POST",
235
+ headers: {
236
+ "xi-api-key": apiKey,
237
+ "Content-Type": "application/json",
238
+ Accept: "audio/mpeg"
239
+ },
240
+ body: JSON.stringify(body)
241
+ });
242
+ if (!resp.ok) {
243
+ const errText = await resp.text();
244
+ return {
245
+ success: false,
246
+ error: `ElevenLabs error ${resp.status}: ${errText}`
247
+ };
215
248
  }
249
+ const audioBuffer = Buffer.from(await resp.arrayBuffer());
250
+ const upload = await uploadAudioAndRecord(sb, storeId, audioBuffer, label, ext, mime, tags);
251
+ return {
252
+ success: true,
253
+ data: {
254
+ file_url: upload.file_url,
255
+ file_size: upload.file_size,
256
+ media_id: upload.media_id,
257
+ format: ext,
258
+ ...extraFields
259
+ }
260
+ };
261
+ } catch (err) {
262
+ return {
263
+ success: false,
264
+ error: `${label} failed: ${err}`
265
+ };
266
+ }
216
267
  }
268
+
217
269
  /** FormData-based API call → buffer → upload → standard result */
218
270
  async function processFormData(sb, storeId, apiKey, url, formData, label, tags, ext, mime, extraFields = {}) {
219
- try {
220
- const resp = await fetchWithRetry(url, {
221
- method: "POST",
222
- headers: { "xi-api-key": apiKey },
223
- body: formData,
224
- });
225
- if (!resp.ok) {
226
- const errText = await resp.text();
227
- return { success: false, error: `ElevenLabs error ${resp.status}: ${errText}` };
228
- }
229
- const audioBuffer = Buffer.from(await resp.arrayBuffer());
230
- const upload = await uploadAudioAndRecord(sb, storeId, audioBuffer, label, ext, mime, tags);
231
- return {
232
- success: true,
233
- data: {
234
- file_url: upload.file_url,
235
- file_size: upload.file_size,
236
- media_id: upload.media_id,
237
- format: ext,
238
- ...extraFields,
239
- },
240
- };
241
- }
242
- catch (err) {
243
- return { success: false, error: `${label} failed: ${err}` };
271
+ try {
272
+ const resp = await fetchWithRetry(url, {
273
+ method: "POST",
274
+ headers: {
275
+ "xi-api-key": apiKey
276
+ },
277
+ body: formData
278
+ });
279
+ if (!resp.ok) {
280
+ const errText = await resp.text();
281
+ return {
282
+ success: false,
283
+ error: `ElevenLabs error ${resp.status}: ${errText}`
284
+ };
244
285
  }
286
+ const audioBuffer = Buffer.from(await resp.arrayBuffer());
287
+ const upload = await uploadAudioAndRecord(sb, storeId, audioBuffer, label, ext, mime, tags);
288
+ return {
289
+ success: true,
290
+ data: {
291
+ file_url: upload.file_url,
292
+ file_size: upload.file_size,
293
+ media_id: upload.media_id,
294
+ format: ext,
295
+ ...extraFields
296
+ }
297
+ };
298
+ } catch (err) {
299
+ return {
300
+ success: false,
301
+ error: `${label} failed: ${err}`
302
+ };
303
+ }
245
304
  }
305
+
246
306
  /** Build FormData from base64 audio input */
247
307
  function audioFormData(audioBase64, fieldName = "file", fileName = "audio.mp3") {
248
- const buffer = Buffer.from(audioBase64, "base64");
249
- const blob = new Blob([buffer], { type: "audio/mpeg" });
250
- const formData = new FormData();
251
- formData.append(fieldName, blob, fileName);
252
- return formData;
308
+ const buffer = Buffer.from(audioBase64, "base64");
309
+ const blob = new Blob([buffer], {
310
+ type: "audio/mpeg"
311
+ });
312
+ const formData = new FormData();
313
+ formData.append(fieldName, blob, fileName);
314
+ return formData;
253
315
  }
316
+
254
317
  // ============================================================================
255
318
  // MAIN HANDLER
256
319
  // ============================================================================
320
+
257
321
  export async function handleVoice(sb, args, storeId) {
258
- const sid = storeId;
259
- const action = args.action;
260
- const apiKey = await getElevenLabsKey(sb, sid);
261
- if (!apiKey) {
262
- return { success: false, error: "ELEVENLABS_API_KEY not configured. Add it to user_tool_secrets for this store." };
263
- }
264
- const outputFormat = args.output_format || "mp3_44100_128";
265
- const { ext, mime } = formatInfo(outputFormat);
266
- switch (action) {
267
- // ── TEXT-TO-SPEECH ─────────────────────────────────────────────────
268
- case "speak": {
269
- const text = args.text;
270
- if (!text)
271
- return { success: false, error: "text parameter is required" };
272
- if (text.length > 5000)
273
- return { success: false, error: "Text too long (max 5000 characters). Split into smaller chunks or use batch action." };
274
- // Resolve voice by name or ID
275
- const voiceInput = (args.voice_name || args.voice_id || DEFAULT_VOICE_ID);
276
- const voiceId = await resolveVoiceId(voiceInput, apiKey, sid);
277
- const modelId = args.model_id || DEFAULT_TTS_MODEL;
278
- // Smart defaults: cloned/custom voices get higher similarity for accuracy
279
- const presetIds = new Set(Object.values(VOICE_PRESETS));
280
- const isCloned = !presetIds.has(voiceId) && voiceId !== DEFAULT_VOICE_ID;
281
- const defaultStability = isCloned ? 0.35 : 0.5;
282
- const defaultSimilarity = isCloned ? 0.9 : 0.75;
283
- const defaultStyle = isCloned ? 0.05 : 0;
284
- return generateAndUpload(sb, sid, apiKey, `${ELEVENLABS_BASE}/text-to-speech/${voiceId}?output_format=${outputFormat}`, {
285
- text,
286
- model_id: modelId,
287
- voice_settings: {
288
- stability: args.stability ?? defaultStability,
289
- similarity_boost: args.similarity_boost ?? defaultSimilarity,
290
- style: args.style ?? defaultStyle,
291
- use_speaker_boost: args.speaker_boost ?? true,
292
- },
293
- }, `TTS: ${text.substring(0, 80)}`, ["AI Generated", "Text-to-Speech", "ElevenLabs"], ext, mime, { voice_id: voiceId, model_id: modelId, text_length: text.length, cloned_voice: isCloned });
322
+ const sid = storeId;
323
+ const action = args.action;
324
+ const apiKey = await getElevenLabsKey(sb, sid);
325
+ if (!apiKey) {
326
+ return {
327
+ success: false,
328
+ error: "ELEVENLABS_API_KEY not configured. Add it to user_tool_secrets for this store."
329
+ };
330
+ }
331
+ const outputFormat = args.output_format || "mp3_44100_128";
332
+ const {
333
+ ext,
334
+ mime
335
+ } = formatInfo(outputFormat);
336
+ switch (action) {
337
+ // ── TEXT-TO-SPEECH ─────────────────────────────────────────────────
338
+ case "speak":
339
+ {
340
+ const text = args.text;
341
+ if (!text) return {
342
+ success: false,
343
+ error: "text parameter is required"
344
+ };
345
+ if (text.length > 5000) return {
346
+ success: false,
347
+ error: "Text too long (max 5000 characters). Split into smaller chunks or use batch action."
348
+ };
349
+
350
+ // Resolve voice by name or ID
351
+ const voiceInput = args.voice_name || args.voice_id || DEFAULT_VOICE_ID;
352
+ const voiceId = await resolveVoiceId(voiceInput, apiKey, sid);
353
+ const modelId = args.model_id || DEFAULT_TTS_MODEL;
354
+
355
+ // Smart defaults: cloned/custom voices get higher similarity for accuracy
356
+ const presetIds = new Set(Object.values(VOICE_PRESETS));
357
+ const isCloned = !presetIds.has(voiceId) && voiceId !== DEFAULT_VOICE_ID;
358
+ const defaultStability = isCloned ? 0.35 : 0.5;
359
+ const defaultSimilarity = isCloned ? 0.9 : 0.75;
360
+ const defaultStyle = isCloned ? 0.05 : 0;
361
+ return generateAndUpload(sb, sid, apiKey, `${ELEVENLABS_BASE}/text-to-speech/${voiceId}?output_format=${outputFormat}`, {
362
+ text,
363
+ model_id: modelId,
364
+ voice_settings: {
365
+ stability: args.stability ?? defaultStability,
366
+ similarity_boost: args.similarity_boost ?? defaultSimilarity,
367
+ style: args.style ?? defaultStyle,
368
+ use_speaker_boost: args.speaker_boost ?? true
369
+ }
370
+ }, `TTS: ${text.substring(0, 80)}`, ["AI Generated", "Text-to-Speech", "ElevenLabs"], ext, mime, {
371
+ voice_id: voiceId,
372
+ model_id: modelId,
373
+ text_length: text.length,
374
+ cloned_voice: isCloned
375
+ });
376
+ }
377
+
378
+ // ── BATCH TTS ──────────────────────────────────────────────────────
379
+ case "batch":
380
+ {
381
+ const items = args.items;
382
+ if (!items?.length) return {
383
+ success: false,
384
+ error: "items array is required (each with text, optional voice_name/voice_id)"
385
+ };
386
+ if (items.length > 10) return {
387
+ success: false,
388
+ error: "Maximum 10 items per batch"
389
+ };
390
+ const results = await Promise.allSettled(items.map(async item => {
391
+ const voiceInput = item.voice_name || item.voice_id || DEFAULT_VOICE_ID;
392
+ const voiceId = await resolveVoiceId(voiceInput, apiKey, sid);
393
+ const modelId = item.model_id || DEFAULT_TTS_MODEL;
394
+
395
+ // Smart defaults: cloned/custom voices get higher similarity for accuracy
396
+ const presetIds = new Set(Object.values(VOICE_PRESETS));
397
+ const isCloned = !presetIds.has(voiceId) && voiceId !== DEFAULT_VOICE_ID;
398
+ const batchStability = isCloned ? 0.35 : 0.5;
399
+ const batchSimilarity = isCloned ? 0.9 : 0.75;
400
+ const batchStyle = isCloned ? 0.05 : 0;
401
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/text-to-speech/${voiceId}?output_format=${outputFormat}`, {
402
+ method: "POST",
403
+ headers: {
404
+ "xi-api-key": apiKey,
405
+ "Content-Type": "application/json",
406
+ Accept: "audio/mpeg"
407
+ },
408
+ body: JSON.stringify({
409
+ text: item.text,
410
+ model_id: modelId,
411
+ voice_settings: {
412
+ stability: batchStability,
413
+ similarity_boost: batchSimilarity,
414
+ style: batchStyle,
415
+ use_speaker_boost: true
416
+ }
417
+ })
418
+ });
419
+ if (!resp.ok) throw new Error(`ElevenLabs error ${resp.status}`);
420
+ const audioBuffer = Buffer.from(await resp.arrayBuffer());
421
+ const upload = await uploadAudioAndRecord(sb, sid, audioBuffer, `TTS: ${item.text.substring(0, 80)}`, ext, mime, ["AI Generated", "Text-to-Speech", "ElevenLabs", "Batch"]);
422
+ return {
423
+ text: item.text.substring(0, 80),
424
+ voice_id: voiceId,
425
+ file_url: upload.file_url,
426
+ file_size: upload.file_size,
427
+ media_id: upload.media_id,
428
+ format: ext
429
+ };
430
+ }));
431
+ const clips = [];
432
+ const errors = [];
433
+ results.forEach((r, i) => {
434
+ if (r.status === "fulfilled") clips.push(r.value);else errors.push({
435
+ index: i,
436
+ text: items[i].text.substring(0, 40),
437
+ error: r.reason.message
438
+ });
439
+ });
440
+ return {
441
+ success: true,
442
+ data: {
443
+ generated: clips.length,
444
+ failed: errors.length,
445
+ total: items.length,
446
+ clips,
447
+ ...(errors.length > 0 ? {
448
+ errors
449
+ } : {})
450
+ }
451
+ };
452
+ }
453
+
454
+ // ── SPEECH-TO-TEXT ──────────────────────────────────────────────────
455
+ case "speech_to_text":
456
+ {
457
+ const audioBase64 = args.audio_base64;
458
+ if (!audioBase64) return {
459
+ success: false,
460
+ error: "audio_base64 parameter is required"
461
+ };
462
+ try {
463
+ const formData = audioFormData(audioBase64);
464
+ formData.append("model_id", args.model_id || DEFAULT_STT_MODEL);
465
+ if (args.language_code) formData.append("language_code", args.language_code);
466
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/speech-to-text`, {
467
+ method: "POST",
468
+ headers: {
469
+ "xi-api-key": apiKey
470
+ },
471
+ body: formData
472
+ });
473
+ if (!resp.ok) {
474
+ const errText = await resp.text();
475
+ return {
476
+ success: false,
477
+ error: `ElevenLabs STT error ${resp.status}: ${errText}`
478
+ };
479
+ }
480
+ return {
481
+ success: true,
482
+ data: await resp.json()
483
+ };
484
+ } catch (err) {
485
+ return {
486
+ success: false,
487
+ error: `STT failed: ${err}`
488
+ };
294
489
  }
295
- // ── BATCH TTS ──────────────────────────────────────────────────────
296
- case "batch": {
297
- const items = args.items;
298
- if (!items?.length)
299
- return { success: false, error: "items array is required (each with text, optional voice_name/voice_id)" };
300
- if (items.length > 10)
301
- return { success: false, error: "Maximum 10 items per batch" };
302
- const results = await Promise.allSettled(items.map(async (item) => {
303
- const voiceInput = item.voice_name || item.voice_id || DEFAULT_VOICE_ID;
304
- const voiceId = await resolveVoiceId(voiceInput, apiKey, sid);
305
- const modelId = item.model_id || DEFAULT_TTS_MODEL;
306
- // Smart defaults: cloned/custom voices get higher similarity for accuracy
307
- const presetIds = new Set(Object.values(VOICE_PRESETS));
308
- const isCloned = !presetIds.has(voiceId) && voiceId !== DEFAULT_VOICE_ID;
309
- const batchStability = isCloned ? 0.35 : 0.5;
310
- const batchSimilarity = isCloned ? 0.9 : 0.75;
311
- const batchStyle = isCloned ? 0.05 : 0;
312
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/text-to-speech/${voiceId}?output_format=${outputFormat}`, {
313
- method: "POST",
314
- headers: { "xi-api-key": apiKey, "Content-Type": "application/json", Accept: "audio/mpeg" },
315
- body: JSON.stringify({
316
- text: item.text,
317
- model_id: modelId,
318
- voice_settings: { stability: batchStability, similarity_boost: batchSimilarity, style: batchStyle, use_speaker_boost: true },
319
- }),
320
- });
321
- if (!resp.ok)
322
- throw new Error(`ElevenLabs error ${resp.status}`);
323
- const audioBuffer = Buffer.from(await resp.arrayBuffer());
324
- const upload = await uploadAudioAndRecord(sb, sid, audioBuffer, `TTS: ${item.text.substring(0, 80)}`, ext, mime, ["AI Generated", "Text-to-Speech", "ElevenLabs", "Batch"]);
325
- return {
326
- text: item.text.substring(0, 80),
327
- voice_id: voiceId,
328
- file_url: upload.file_url,
329
- file_size: upload.file_size,
330
- media_id: upload.media_id,
331
- format: ext,
332
- };
333
- }));
334
- const clips = [];
335
- const errors = [];
336
- results.forEach((r, i) => {
337
- if (r.status === "fulfilled")
338
- clips.push(r.value);
339
- else
340
- errors.push({ index: i, text: items[i].text.substring(0, 40), error: r.reason.message });
341
- });
490
+ }
491
+
492
+ // ── SOUND EFFECTS ──────────────────────────────────────────────────
493
+ case "sound_effects":
494
+ {
495
+ const text = args.text;
496
+ if (!text) return {
497
+ success: false,
498
+ error: "text parameter is required (describe the sound)"
499
+ };
500
+ const body = {
501
+ text,
502
+ model_id: DEFAULT_SFX_MODEL,
503
+ prompt_influence: args.prompt_influence ?? 0.3,
504
+ loop: args.loop ?? false
505
+ };
506
+ if (args.duration_seconds) body.duration_seconds = args.duration_seconds;
507
+ return generateAndUpload(sb, sid, apiKey, `${ELEVENLABS_BASE}/sound-generation?output_format=${outputFormat}`, body, `SFX: ${text.substring(0, 80)}`, ["AI Generated", "Sound Effect", "ElevenLabs"], ext, mime, {
508
+ prompt: text,
509
+ duration_seconds: args.duration_seconds || "auto",
510
+ loop: body.loop
511
+ });
512
+ }
513
+
514
+ // ── MUSIC PLAN ────────────────────────────────────────────────────
515
+ // FREE endpoint that converts a prompt into a structured composition plan
516
+ // with proper sections, lyrics per line, timing, and styles.
517
+ // The plan can then be fed to music_compose for much clearer vocals.
518
+ case "music_plan":
519
+ {
520
+ const prompt = args.prompt;
521
+ if (!prompt) return {
522
+ success: false,
523
+ error: "prompt parameter is required (describe the song)"
524
+ };
525
+ const planBody = {
526
+ prompt,
527
+ model_id: DEFAULT_MUSIC_MODEL
528
+ };
529
+ if (args.music_length_ms) planBody.music_length_ms = args.music_length_ms;
530
+ try {
531
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/music/plan`, {
532
+ method: "POST",
533
+ headers: {
534
+ "xi-api-key": apiKey,
535
+ "Content-Type": "application/json"
536
+ },
537
+ body: JSON.stringify(planBody)
538
+ });
539
+ if (!resp.ok) {
540
+ const errText = await resp.text();
342
541
  return {
343
- success: true,
344
- data: {
345
- generated: clips.length,
346
- failed: errors.length,
347
- total: items.length,
348
- clips,
349
- ...(errors.length > 0 ? { errors } : {}),
350
- },
542
+ success: false,
543
+ error: `ElevenLabs plan error ${resp.status}: ${errText}`
351
544
  };
545
+ }
546
+ const plan = await resp.json();
547
+ return {
548
+ success: true,
549
+ data: {
550
+ composition_plan: plan,
551
+ hint: "Pass this composition_plan to music_compose to generate the song. You can edit sections, lyrics, or styles before generating."
552
+ }
553
+ };
554
+ } catch (err) {
555
+ return {
556
+ success: false,
557
+ error: `Music plan failed: ${err}`
558
+ };
352
559
  }
353
- // ── SPEECH-TO-TEXT ──────────────────────────────────────────────────
354
- case "speech_to_text": {
355
- const audioBase64 = args.audio_base64;
356
- if (!audioBase64)
357
- return { success: false, error: "audio_base64 parameter is required" };
358
- try {
359
- const formData = audioFormData(audioBase64);
360
- formData.append("model_id", args.model_id || DEFAULT_STT_MODEL);
361
- if (args.language_code)
362
- formData.append("language_code", args.language_code);
363
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/speech-to-text`, {
364
- method: "POST",
365
- headers: { "xi-api-key": apiKey },
366
- body: formData,
367
- });
368
- if (!resp.ok) {
369
- const errText = await resp.text();
370
- return { success: false, error: `ElevenLabs STT error ${resp.status}: ${errText}` };
371
- }
372
- return { success: true, data: await resp.json() };
560
+ }
561
+
562
+ // ── MUSIC COMPOSE ──────────────────────────────────────────────────
563
+ case "music_compose":
564
+ {
565
+ const prompt = args.prompt;
566
+ const compositionPlan = args.composition_plan;
567
+ if (!prompt && !compositionPlan) return {
568
+ success: false,
569
+ error: "prompt or composition_plan is required"
570
+ };
571
+ const isInstrumental = args.force_instrumental ?? false;
572
+
573
+ // Use higher quality for music (192kbps vs default 128kbps)
574
+ const musicFormat = outputFormat === "mp3_44100_128" ? "mp3_44100_192" : outputFormat;
575
+ const musicFmt = formatInfo(musicFormat);
576
+
577
+ // If composition_plan provided, use it (from music_plan or manually built)
578
+ if (compositionPlan) {
579
+ // Clone to avoid mutating the caller's object
580
+ const plan = JSON.parse(JSON.stringify(compositionPlan));
581
+
582
+ // Inject vocal clarity styles when not instrumental
583
+ if (!isInstrumental) {
584
+ if (!plan.positive_global_styles) {
585
+ plan.positive_global_styles = ["clear vocals", "crisp pronunciation", "studio quality"];
373
586
  }
374
- catch (err) {
375
- return { success: false, error: `STT failed: ${err}` };
587
+ if (!plan.negative_global_styles) {
588
+ plan.negative_global_styles = ["distorted vocals", "mumbled lyrics", "lo-fi vocals"];
376
589
  }
590
+ }
591
+ const body = {
592
+ composition_plan: plan,
593
+ model_id: DEFAULT_MUSIC_MODEL,
594
+ force_instrumental: isInstrumental
595
+ };
596
+ return generateAndUpload(sb, sid, apiKey, `${ELEVENLABS_BASE}/music?output_format=${musicFormat}`, body, `Music (plan): ${JSON.stringify(plan).substring(0, 80)}`, ["AI Generated", "Music", "ElevenLabs", "Composition Plan"], musicFmt.ext, musicFmt.mime, {
597
+ method: "composition_plan",
598
+ instrumental: isInstrumental
599
+ });
377
600
  }
378
- // ── SOUND EFFECTS ──────────────────────────────────────────────────
379
- case "sound_effects": {
380
- const text = args.text;
381
- if (!text)
382
- return { success: false, error: "text parameter is required (describe the sound)" };
383
- const body = {
384
- text,
385
- model_id: DEFAULT_SFX_MODEL,
386
- prompt_influence: args.prompt_influence ?? 0.3,
387
- loop: args.loop ?? false,
388
- };
389
- if (args.duration_seconds)
390
- body.duration_seconds = args.duration_seconds;
391
- return generateAndUpload(sb, sid, apiKey, `${ELEVENLABS_BASE}/sound-generation?output_format=${outputFormat}`, body, `SFX: ${text.substring(0, 80)}`, ["AI Generated", "Sound Effect", "ElevenLabs"], ext, mime, { prompt: text, duration_seconds: args.duration_seconds || "auto", loop: body.loop });
601
+
602
+ // Simple prompt mode
603
+ const body = {
604
+ prompt: prompt,
605
+ model_id: DEFAULT_MUSIC_MODEL,
606
+ force_instrumental: isInstrumental
607
+ };
608
+ if (args.music_length_ms) body.music_length_ms = args.music_length_ms;
609
+ return generateAndUpload(sb, sid, apiKey, `${ELEVENLABS_BASE}/music?output_format=${musicFormat}`, body, `Music: ${prompt.substring(0, 80)}`, ["AI Generated", "Music", "ElevenLabs"], musicFmt.ext, musicFmt.mime, {
610
+ prompt,
611
+ music_length_ms: args.music_length_ms || "auto",
612
+ instrumental: isInstrumental
613
+ });
614
+ }
615
+
616
+ // ── MUSIC COMPOSE DETAILED ─────────────────────────────────────────
617
+ case "music_compose_detailed":
618
+ {
619
+ const prompt = args.prompt;
620
+ const compositionPlan = args.composition_plan;
621
+ if (!prompt && !compositionPlan) return {
622
+ success: false,
623
+ error: "prompt or composition_plan required"
624
+ };
625
+ const isInstrumental = args.force_instrumental ?? false;
626
+
627
+ // Use higher quality for music (192kbps vs default 128kbps)
628
+ const musicFormat = outputFormat === "mp3_44100_128" ? "mp3_44100_192" : outputFormat;
629
+ const musicFmt = formatInfo(musicFormat);
630
+
631
+ // Clone to avoid mutating the caller's object, then inject vocal clarity styles
632
+ let planToUse = compositionPlan;
633
+ if (compositionPlan && !isInstrumental) {
634
+ const plan = JSON.parse(JSON.stringify(compositionPlan));
635
+ if (!plan.positive_global_styles) {
636
+ plan.positive_global_styles = ["clear vocals", "crisp pronunciation", "studio quality"];
637
+ }
638
+ if (!plan.negative_global_styles) {
639
+ plan.negative_global_styles = ["distorted vocals", "mumbled lyrics", "lo-fi vocals"];
640
+ }
641
+ planToUse = plan;
392
642
  }
393
- // ── MUSIC PLAN ────────────────────────────────────────────────────
394
- // FREE endpoint that converts a prompt into a structured composition plan
395
- // with proper sections, lyrics per line, timing, and styles.
396
- // The plan can then be fed to music_compose for much clearer vocals.
397
- case "music_plan": {
398
- const prompt = args.prompt;
399
- if (!prompt)
400
- return { success: false, error: "prompt parameter is required (describe the song)" };
401
- const planBody = { prompt, model_id: DEFAULT_MUSIC_MODEL };
402
- if (args.music_length_ms)
403
- planBody.music_length_ms = args.music_length_ms;
643
+ const body = {
644
+ model_id: DEFAULT_MUSIC_MODEL,
645
+ force_instrumental: isInstrumental,
646
+ with_timestamps: args.with_timestamps ?? false
647
+ };
648
+ if (prompt) body.prompt = prompt;
649
+ if (planToUse) body.composition_plan = planToUse;
650
+ if (args.music_length_ms) body.music_length_ms = args.music_length_ms;
651
+ return generateAndUpload(sb, sid, apiKey, `${ELEVENLABS_BASE}/music/detailed?output_format=${musicFormat}`, body, `Music (detailed): ${(prompt || "composition plan").substring(0, 80)}`, ["AI Generated", "Music", "ElevenLabs", "Detailed"], musicFmt.ext, musicFmt.mime, {
652
+ prompt: prompt || "(composition plan)",
653
+ with_timestamps: body.with_timestamps
654
+ });
655
+ }
656
+
657
+ // ── STEM SEPARATION ────────────────────────────────────────────────
658
+ case "stem_separation":
659
+ {
660
+ const audioBase64 = args.audio_base64;
661
+ if (!audioBase64) return {
662
+ success: false,
663
+ error: "audio_base64 parameter is required"
664
+ };
665
+ const stemVariation = args.stem_variation || "six_stems_v1";
666
+ const formData = audioFormData(audioBase64);
667
+ formData.append("stem_variation_id", stemVariation);
668
+ return processFormData(sb, sid, apiKey, `${ELEVENLABS_BASE}/music/stem-separation?output_format=${outputFormat}`, formData, `Stem separation (${stemVariation})`, ["AI Generated", "Stem Separation", "ElevenLabs"], "zip", "application/zip", {
669
+ stem_variation: stemVariation,
670
+ stems: stemVariation === "two_stems_v1" ? ["vocals", "accompaniment"] : ["vocals", "drums", "bass", "piano", "guitar", "other"]
671
+ });
672
+ }
673
+
674
+ // ── VOICE CHANGER ──────────────────────────────────────────────────
675
+ case "voice_changer":
676
+ {
677
+ const audioBase64 = args.audio_base64;
678
+ if (!audioBase64) return {
679
+ success: false,
680
+ error: "audio_base64 parameter is required"
681
+ };
682
+ const voiceInput = args.voice_name || args.voice_id;
683
+ if (!voiceInput) return {
684
+ success: false,
685
+ error: "voice_id or voice_name parameter is required"
686
+ };
687
+ const voiceId = await resolveVoiceId(voiceInput, apiKey, sid);
688
+ const modelId = args.model_id || DEFAULT_STS_MODEL;
689
+ const formData = audioFormData(audioBase64, "audio");
690
+ formData.append("model_id", modelId);
691
+ formData.append("remove_background_noise", String(args.remove_background_noise ?? false));
692
+ if (args.voice_settings) formData.append("voice_settings", JSON.stringify(args.voice_settings));
693
+ return processFormData(sb, sid, apiKey, `${ELEVENLABS_BASE}/speech-to-speech/${voiceId}?output_format=${outputFormat}`, formData, `Voice change to ${voiceId}`, ["AI Generated", "Voice Changer", "ElevenLabs"], ext, mime, {
694
+ voice_id: voiceId,
695
+ model_id: modelId
696
+ });
697
+ }
698
+
699
+ // ── AUDIO ISOLATION ────────────────────────────────────────────────
700
+ case "audio_isolation":
701
+ {
702
+ const audioBase64 = args.audio_base64;
703
+ if (!audioBase64) return {
704
+ success: false,
705
+ error: "audio_base64 parameter is required"
706
+ };
707
+ const formData = audioFormData(audioBase64, "audio");
708
+ return processFormData(sb, sid, apiKey, `${ELEVENLABS_BASE}/audio-isolation`, formData, "Audio isolation — vocals isolated", ["AI Generated", "Audio Isolation", "ElevenLabs"], ext, mime, {
709
+ description: "Isolated vocals with background noise removed"
710
+ });
711
+ }
712
+
713
+ // ── VOICE DESIGN ───────────────────────────────────────────────────
714
+ case "voice_design":
715
+ {
716
+ const voiceDescription = args.voice_description;
717
+ if (!voiceDescription) return {
718
+ success: false,
719
+ error: "voice_description parameter is required"
720
+ };
721
+ try {
722
+ const body = {
723
+ voice_description: voiceDescription,
724
+ text: args.preview_text || "Hello, this is a preview of the designed voice.",
725
+ auto_enhance_description: args.auto_enhance ?? true,
726
+ model_id: DEFAULT_TTV_MODEL
727
+ };
728
+ if (args.reference_audio_base64) {
729
+ body.reference_audio = args.reference_audio_base64;
730
+ if (args.reference_weight !== undefined) body.reference_weight = args.reference_weight;
731
+ }
732
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/text-to-voice/design?output_format=${outputFormat}`, {
733
+ method: "POST",
734
+ headers: {
735
+ "xi-api-key": apiKey,
736
+ "Content-Type": "application/json"
737
+ },
738
+ body: JSON.stringify(body)
739
+ });
740
+ if (!resp.ok) {
741
+ const errText = await resp.text();
742
+ return {
743
+ success: false,
744
+ error: `ElevenLabs Voice Design error ${resp.status}: ${errText}`
745
+ };
746
+ }
747
+ const data = await resp.json();
748
+ const previews = [];
749
+ for (let i = 0; i < (data.previews || []).length; i++) {
750
+ const p = data.previews[i];
751
+ const b64 = p.audio_base_64 || p.audio_base64;
752
+ if (!b64) continue;
404
753
  try {
405
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/music/plan`, {
406
- method: "POST",
407
- headers: { "xi-api-key": apiKey, "Content-Type": "application/json" },
408
- body: JSON.stringify(planBody),
409
- });
410
- if (!resp.ok) {
411
- const errText = await resp.text();
412
- return { success: false, error: `ElevenLabs plan error ${resp.status}: ${errText}` };
413
- }
414
- const plan = await resp.json();
415
- return {
416
- success: true,
417
- data: {
418
- composition_plan: plan,
419
- hint: "Pass this composition_plan to music_compose to generate the song. You can edit sections, lyrics, or styles before generating.",
420
- },
421
- };
422
- }
423
- catch (err) {
424
- return { success: false, error: `Music plan failed: ${err}` };
754
+ const buf = Buffer.from(b64, "base64");
755
+ const upload = await uploadAudioAndRecord(sb, sid, buf, `Voice design preview ${i + 1}: ${voiceDescription.substring(0, 60)}`, "mp3", "audio/mpeg", ["AI Generated", "Voice Design", "Preview", "ElevenLabs"]);
756
+ previews.push({
757
+ generated_voice_id: p.generated_voice_id,
758
+ file_url: upload.file_url,
759
+ media_id: upload.media_id
760
+ });
761
+ } catch (uploadErr) {
762
+ console.error(`[voice] preview ${i} upload failed:`, uploadErr);
763
+ previews.push({
764
+ generated_voice_id: p.generated_voice_id,
765
+ file_url: "(upload failed)",
766
+ media_id: ""
767
+ });
425
768
  }
426
- }
427
- // ── MUSIC COMPOSE ──────────────────────────────────────────────────
428
- case "music_compose": {
429
- const prompt = args.prompt;
430
- const compositionPlan = args.composition_plan;
431
- if (!prompt && !compositionPlan)
432
- return { success: false, error: "prompt or composition_plan is required" };
433
- const isInstrumental = args.force_instrumental ?? false;
434
- // Use higher quality for music (192kbps vs default 128kbps)
435
- const musicFormat = outputFormat === "mp3_44100_128" ? "mp3_44100_192" : outputFormat;
436
- const musicFmt = formatInfo(musicFormat);
437
- // If composition_plan provided, use it (from music_plan or manually built)
438
- if (compositionPlan) {
439
- // Clone to avoid mutating the caller's object
440
- const plan = JSON.parse(JSON.stringify(compositionPlan));
441
- // Inject vocal clarity styles when not instrumental
442
- if (!isInstrumental) {
443
- if (!plan.positive_global_styles) {
444
- plan.positive_global_styles = ["clear vocals", "crisp pronunciation", "studio quality"];
445
- }
446
- if (!plan.negative_global_styles) {
447
- plan.negative_global_styles = ["distorted vocals", "mumbled lyrics", "lo-fi vocals"];
448
- }
449
- }
450
- const body = {
451
- composition_plan: plan,
452
- model_id: DEFAULT_MUSIC_MODEL,
453
- force_instrumental: isInstrumental,
454
- };
455
- return generateAndUpload(sb, sid, apiKey, `${ELEVENLABS_BASE}/music?output_format=${musicFormat}`, body, `Music (plan): ${JSON.stringify(plan).substring(0, 80)}`, ["AI Generated", "Music", "ElevenLabs", "Composition Plan"], musicFmt.ext, musicFmt.mime, { method: "composition_plan", instrumental: isInstrumental });
769
+ }
770
+ return {
771
+ success: true,
772
+ data: {
773
+ previews,
774
+ description: voiceDescription
456
775
  }
457
- // Simple prompt mode
458
- const body = {
459
- prompt: prompt,
460
- model_id: DEFAULT_MUSIC_MODEL,
461
- force_instrumental: isInstrumental,
462
- };
463
- if (args.music_length_ms)
464
- body.music_length_ms = args.music_length_ms;
465
- return generateAndUpload(sb, sid, apiKey, `${ELEVENLABS_BASE}/music?output_format=${musicFormat}`, body, `Music: ${prompt.substring(0, 80)}`, ["AI Generated", "Music", "ElevenLabs"], musicFmt.ext, musicFmt.mime, { prompt, music_length_ms: args.music_length_ms || "auto", instrumental: isInstrumental });
776
+ };
777
+ } catch (err) {
778
+ return {
779
+ success: false,
780
+ error: `Voice design failed: ${err}`
781
+ };
466
782
  }
467
- // ── MUSIC COMPOSE DETAILED ─────────────────────────────────────────
468
- case "music_compose_detailed": {
469
- const prompt = args.prompt;
470
- const compositionPlan = args.composition_plan;
471
- if (!prompt && !compositionPlan)
472
- return { success: false, error: "prompt or composition_plan required" };
473
- const isInstrumental = args.force_instrumental ?? false;
474
- // Use higher quality for music (192kbps vs default 128kbps)
475
- const musicFormat = outputFormat === "mp3_44100_128" ? "mp3_44100_192" : outputFormat;
476
- const musicFmt = formatInfo(musicFormat);
477
- // Clone to avoid mutating the caller's object, then inject vocal clarity styles
478
- let planToUse = compositionPlan;
479
- if (compositionPlan && !isInstrumental) {
480
- const plan = JSON.parse(JSON.stringify(compositionPlan));
481
- if (!plan.positive_global_styles) {
482
- plan.positive_global_styles = ["clear vocals", "crisp pronunciation", "studio quality"];
483
- }
484
- if (!plan.negative_global_styles) {
485
- plan.negative_global_styles = ["distorted vocals", "mumbled lyrics", "lo-fi vocals"];
486
- }
487
- planToUse = plan;
488
- }
489
- const body = {
490
- model_id: DEFAULT_MUSIC_MODEL,
491
- force_instrumental: isInstrumental,
492
- with_timestamps: args.with_timestamps ?? false,
783
+ }
784
+
785
+ // ── VOICE DESIGN CREATE ────────────────────────────────────────────
786
+ case "voice_design_create":
787
+ {
788
+ const generatedVoiceId = args.generated_voice_id;
789
+ const voiceName = args.voice_name;
790
+ if (!generatedVoiceId) return {
791
+ success: false,
792
+ error: "generated_voice_id is required (from voice_design)"
793
+ };
794
+ if (!voiceName) return {
795
+ success: false,
796
+ error: "voice_name is required"
797
+ };
798
+ try {
799
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/text-to-voice/create`, {
800
+ method: "POST",
801
+ headers: {
802
+ "xi-api-key": apiKey,
803
+ "Content-Type": "application/json"
804
+ },
805
+ body: JSON.stringify({
806
+ generated_voice_id: generatedVoiceId,
807
+ voice_name: voiceName,
808
+ voice_description: args.voice_description || "",
809
+ labels: args.labels || {}
810
+ })
811
+ });
812
+ if (!resp.ok) {
813
+ const errText = await resp.text();
814
+ return {
815
+ success: false,
816
+ error: `ElevenLabs Voice Create error ${resp.status}: ${errText}`
493
817
  };
494
- if (prompt)
495
- body.prompt = prompt;
496
- if (planToUse)
497
- body.composition_plan = planToUse;
498
- if (args.music_length_ms)
499
- body.music_length_ms = args.music_length_ms;
500
- return generateAndUpload(sb, sid, apiKey, `${ELEVENLABS_BASE}/music/detailed?output_format=${musicFormat}`, body, `Music (detailed): ${(prompt || "composition plan").substring(0, 80)}`, ["AI Generated", "Music", "ElevenLabs", "Detailed"], musicFmt.ext, musicFmt.mime, { prompt: prompt || "(composition plan)", with_timestamps: body.with_timestamps });
818
+ }
819
+ return {
820
+ success: true,
821
+ data: await resp.json()
822
+ };
823
+ } catch (err) {
824
+ return {
825
+ success: false,
826
+ error: `Voice create failed: ${err}`
827
+ };
501
828
  }
502
- // ── STEM SEPARATION ────────────────────────────────────────────────
503
- case "stem_separation": {
504
- const audioBase64 = args.audio_base64;
505
- if (!audioBase64)
506
- return { success: false, error: "audio_base64 parameter is required" };
507
- const stemVariation = args.stem_variation || "six_stems_v1";
508
- const formData = audioFormData(audioBase64);
509
- formData.append("stem_variation_id", stemVariation);
510
- return processFormData(sb, sid, apiKey, `${ELEVENLABS_BASE}/music/stem-separation?output_format=${outputFormat}`, formData, `Stem separation (${stemVariation})`, ["AI Generated", "Stem Separation", "ElevenLabs"], "zip", "application/zip", {
511
- stem_variation: stemVariation,
512
- stems: stemVariation === "two_stems_v1"
513
- ? ["vocals", "accompaniment"]
514
- : ["vocals", "drums", "bass", "piano", "guitar", "other"],
515
- });
829
+ }
830
+
831
+ // ── CLONE VOICE ────────────────────────────────────────────────────
832
+ // Accepts single audio_base64 or array of audio_samples for multiple clips.
833
+ // file_paths support: CLI pre-processor reads files and injects audio_samples.
834
+ // ElevenLabs recommends 1-2 min clean audio minimum for instant cloning.
835
+ case "clone_voice":
836
+ {
837
+ const voiceName = args.voice_name;
838
+ if (!voiceName) return {
839
+ success: false,
840
+ error: "voice_name is required"
841
+ };
842
+
843
+ // Accept multiple samples (array) or single audio_base64
844
+ const samples = [];
845
+ if (Array.isArray(args.audio_samples)) {
846
+ for (const s of args.audio_samples) {
847
+ if (typeof s === "string" && s.length > 100) samples.push(s);
848
+ }
516
849
  }
517
- // ── VOICE CHANGER ──────────────────────────────────────────────────
518
- case "voice_changer": {
519
- const audioBase64 = args.audio_base64;
520
- if (!audioBase64)
521
- return { success: false, error: "audio_base64 parameter is required" };
522
- const voiceInput = (args.voice_name || args.voice_id);
523
- if (!voiceInput)
524
- return { success: false, error: "voice_id or voice_name parameter is required" };
525
- const voiceId = await resolveVoiceId(voiceInput, apiKey, sid);
526
- const modelId = args.model_id || DEFAULT_STS_MODEL;
527
- const formData = audioFormData(audioBase64, "audio");
528
- formData.append("model_id", modelId);
529
- formData.append("remove_background_noise", String(args.remove_background_noise ?? false));
530
- if (args.voice_settings)
531
- formData.append("voice_settings", JSON.stringify(args.voice_settings));
532
- return processFormData(sb, sid, apiKey, `${ELEVENLABS_BASE}/speech-to-speech/${voiceId}?output_format=${outputFormat}`, formData, `Voice change to ${voiceId}`, ["AI Generated", "Voice Changer", "ElevenLabs"], ext, mime, { voice_id: voiceId, model_id: modelId });
850
+ if (typeof args.audio_base64 === "string" && args.audio_base64.length > 100) {
851
+ samples.push(args.audio_base64);
533
852
  }
534
- // ── AUDIO ISOLATION ────────────────────────────────────────────────
535
- case "audio_isolation": {
536
- const audioBase64 = args.audio_base64;
537
- if (!audioBase64)
538
- return { success: false, error: "audio_base64 parameter is required" };
539
- const formData = audioFormData(audioBase64, "audio");
540
- return processFormData(sb, sid, apiKey, `${ELEVENLABS_BASE}/audio-isolation`, formData, "Audio isolation — vocals isolated", ["AI Generated", "Audio Isolation", "ElevenLabs"], ext, mime, { description: "Isolated vocals with background noise removed" });
853
+ if (samples.length === 0) {
854
+ return {
855
+ success: false,
856
+ error: "Provide file_paths (array of local audio paths) or audio_samples (array of base64 strings). file_paths is preferred — the CLI encodes automatically."
857
+ };
541
858
  }
542
- // ── VOICE DESIGN ───────────────────────────────────────────────────
543
- case "voice_design": {
544
- const voiceDescription = args.voice_description;
545
- if (!voiceDescription)
546
- return { success: false, error: "voice_description parameter is required" };
547
- try {
548
- const body = {
549
- voice_description: voiceDescription,
550
- text: args.preview_text || "Hello, this is a preview of the designed voice.",
551
- auto_enhance_description: args.auto_enhance ?? true,
552
- model_id: DEFAULT_TTV_MODEL,
553
- };
554
- if (args.reference_audio_base64) {
555
- body.reference_audio = args.reference_audio_base64;
556
- if (args.reference_weight !== undefined)
557
- body.reference_weight = args.reference_weight;
558
- }
559
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/text-to-voice/design?output_format=${outputFormat}`, {
560
- method: "POST",
561
- headers: { "xi-api-key": apiKey, "Content-Type": "application/json" },
562
- body: JSON.stringify(body),
563
- });
564
- if (!resp.ok) {
565
- const errText = await resp.text();
566
- return { success: false, error: `ElevenLabs Voice Design error ${resp.status}: ${errText}` };
567
- }
568
- const data = await resp.json();
569
- const previews = [];
570
- for (let i = 0; i < (data.previews || []).length; i++) {
571
- const p = data.previews[i];
572
- const b64 = p.audio_base_64 || p.audio_base64;
573
- if (!b64)
574
- continue;
575
- try {
576
- const buf = Buffer.from(b64, "base64");
577
- const upload = await uploadAudioAndRecord(sb, sid, buf, `Voice design preview ${i + 1}: ${voiceDescription.substring(0, 60)}`, "mp3", "audio/mpeg", ["AI Generated", "Voice Design", "Preview", "ElevenLabs"]);
578
- previews.push({ generated_voice_id: p.generated_voice_id, file_url: upload.file_url, media_id: upload.media_id });
579
- }
580
- catch (uploadErr) {
581
- console.error(`[voice] preview ${i} upload failed:`, uploadErr);
582
- previews.push({ generated_voice_id: p.generated_voice_id, file_url: "(upload failed)", media_id: "" });
583
- }
584
- }
585
- return { success: true, data: { previews, description: voiceDescription } };
586
- }
587
- catch (err) {
588
- return { success: false, error: `Voice design failed: ${err}` };
859
+ try {
860
+ const formData = new FormData();
861
+ formData.append("name", voiceName);
862
+ if (args.voice_description) formData.append("description", args.voice_description);
863
+
864
+ // Enable noise removal only if samples are long enough (>= 5s ≈ 80KB base64 at 128kbps)
865
+ // ElevenLabs requires >= 4.6s per sample when noise removal is on
866
+ const shortestSample = Math.min(...samples.map(s => s.length));
867
+ const autoNoiseRemoval = shortestSample >= 80_000; // ~5 seconds at 128kbps
868
+ const removeNoise = args.remove_background_noise ?? autoNoiseRemoval;
869
+ formData.append("remove_background_noise", String(removeNoise));
870
+
871
+ // Attach each sample as a separate file
872
+ for (let i = 0; i < samples.length; i++) {
873
+ const buffer = Buffer.from(samples[i], "base64");
874
+ const blob = new Blob([buffer], {
875
+ type: "audio/mpeg"
876
+ });
877
+ formData.append("files", blob, `sample-${i}.mp3`);
878
+ }
879
+
880
+ // Optional: labels for the voice
881
+ if (args.labels) {
882
+ formData.append("labels", JSON.stringify(args.labels));
883
+ }
884
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/add`, {
885
+ method: "POST",
886
+ headers: {
887
+ "xi-api-key": apiKey
888
+ },
889
+ body: formData
890
+ });
891
+ if (!resp.ok) {
892
+ const errText = await resp.text();
893
+ return {
894
+ success: false,
895
+ error: `Voice clone error ${resp.status}: ${errText}`
896
+ };
897
+ }
898
+ const data = await resp.json();
899
+
900
+ // Calculate total audio size for quality feedback
901
+ const totalBytes = samples.reduce((sum, s) => sum + Math.ceil(s.length * 3 / 4), 0);
902
+ const totalSecs = Math.round(totalBytes / 16000); // rough estimate at 128kbps
903
+ const qualityNote = totalSecs < 30 ? `Tip: only ~${totalSecs}s of audio provided. For better accuracy, use 1-2 minutes of clean, continuous speech.` : `Good: ~${totalSecs}s of audio used.`;
904
+ return {
905
+ success: true,
906
+ data: {
907
+ voice_id: data.voice_id,
908
+ name: voiceName,
909
+ samples_used: samples.length,
910
+ noise_removed: removeNoise,
911
+ quality_note: qualityNote,
912
+ message: `Voice "${voiceName}" cloned successfully. Use this voice_id with speak action.`
589
913
  }
914
+ };
915
+ } catch (err) {
916
+ return {
917
+ success: false,
918
+ error: `Voice clone failed: ${err}`
919
+ };
590
920
  }
591
- // ── VOICE DESIGN CREATE ────────────────────────────────────────────
592
- case "voice_design_create": {
593
- const generatedVoiceId = args.generated_voice_id;
594
- const voiceName = args.voice_name;
595
- if (!generatedVoiceId)
596
- return { success: false, error: "generated_voice_id is required (from voice_design)" };
597
- if (!voiceName)
598
- return { success: false, error: "voice_name is required" };
599
- try {
600
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/text-to-voice/create`, {
601
- method: "POST",
602
- headers: { "xi-api-key": apiKey, "Content-Type": "application/json" },
603
- body: JSON.stringify({
604
- generated_voice_id: generatedVoiceId,
605
- voice_name: voiceName,
606
- voice_description: args.voice_description || "",
607
- labels: args.labels || {},
608
- }),
609
- });
610
- if (!resp.ok) {
611
- const errText = await resp.text();
612
- return { success: false, error: `ElevenLabs Voice Create error ${resp.status}: ${errText}` };
613
- }
614
- return { success: true, data: await resp.json() };
615
- }
616
- catch (err) {
617
- return { success: false, error: `Voice create failed: ${err}` };
921
+ }
922
+
923
+ // ══════════════════════════════════════════════════════════════════
924
+ // PROFESSIONAL VOICE CLONING (PVC) — near-perfect accuracy
925
+ // Multi-step: create → upload → separate speakers → verify → train
926
+ // Requires Creator plan or above on ElevenLabs.
927
+ // ══════════════════════════════════════════════════════════════════
928
+
929
+ // ── PVC CREATE ─────────────────────────────────────────────────────
930
+ case "pvc_create":
931
+ {
932
+ const voiceName = args.voice_name;
933
+ const language = args.language || "en";
934
+ if (!voiceName) return {
935
+ success: false,
936
+ error: "voice_name is required"
937
+ };
938
+ try {
939
+ const body = {
940
+ name: voiceName,
941
+ language
942
+ };
943
+ if (args.voice_description) body.description = args.voice_description;
944
+ if (args.labels) body.labels = args.labels;
945
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc`, {
946
+ method: "POST",
947
+ headers: {
948
+ "xi-api-key": apiKey,
949
+ "Content-Type": "application/json"
950
+ },
951
+ body: JSON.stringify(body)
952
+ });
953
+ if (!resp.ok) {
954
+ const errText = await resp.text();
955
+ return {
956
+ success: false,
957
+ error: `PVC create error ${resp.status}: ${errText}`
958
+ };
959
+ }
960
+ const data = await resp.json();
961
+ return {
962
+ success: true,
963
+ data: {
964
+ voice_id: data.voice_id,
965
+ name: voiceName,
966
+ language,
967
+ next_step: "Upload audio samples with pvc_upload action, then use pvc_train to start training."
618
968
  }
969
+ };
970
+ } catch (err) {
971
+ return {
972
+ success: false,
973
+ error: `PVC create failed: ${err}`
974
+ };
619
975
  }
620
- // ── CLONE VOICE ────────────────────────────────────────────────────
621
- // Accepts single audio_base64 or array of audio_samples for multiple clips.
622
- // file_paths support: CLI pre-processor reads files and injects audio_samples.
623
- // ElevenLabs recommends 1-2 min clean audio minimum for instant cloning.
624
- case "clone_voice": {
625
- const voiceName = args.voice_name;
626
- if (!voiceName)
627
- return { success: false, error: "voice_name is required" };
628
- // Accept multiple samples (array) or single audio_base64
629
- const samples = [];
630
- if (Array.isArray(args.audio_samples)) {
631
- for (const s of args.audio_samples) {
632
- if (typeof s === "string" && s.length > 100)
633
- samples.push(s);
634
- }
635
- }
636
- if (typeof args.audio_base64 === "string" && args.audio_base64.length > 100) {
637
- samples.push(args.audio_base64);
638
- }
639
- if (samples.length === 0) {
640
- return { success: false, error: "Provide file_paths (array of local audio paths) or audio_samples (array of base64 strings). file_paths is preferred — the CLI encodes automatically." };
641
- }
642
- try {
643
- const formData = new FormData();
644
- formData.append("name", voiceName);
645
- if (args.voice_description)
646
- formData.append("description", args.voice_description);
647
- // Enable noise removal only if samples are long enough (>= 5s ≈ 80KB base64 at 128kbps)
648
- // ElevenLabs requires >= 4.6s per sample when noise removal is on
649
- const shortestSample = Math.min(...samples.map(s => s.length));
650
- const autoNoiseRemoval = shortestSample >= 80_000; // ~5 seconds at 128kbps
651
- const removeNoise = args.remove_background_noise ?? autoNoiseRemoval;
652
- formData.append("remove_background_noise", String(removeNoise));
653
- // Attach each sample as a separate file
654
- for (let i = 0; i < samples.length; i++) {
655
- const buffer = Buffer.from(samples[i], "base64");
656
- const blob = new Blob([buffer], { type: "audio/mpeg" });
657
- formData.append("files", blob, `sample-${i}.mp3`);
658
- }
659
- // Optional: labels for the voice
660
- if (args.labels) {
661
- formData.append("labels", JSON.stringify(args.labels));
662
- }
663
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/add`, {
664
- method: "POST",
665
- headers: { "xi-api-key": apiKey },
666
- body: formData,
667
- });
668
- if (!resp.ok) {
669
- const errText = await resp.text();
670
- return { success: false, error: `Voice clone error ${resp.status}: ${errText}` };
671
- }
672
- const data = await resp.json();
673
- // Calculate total audio size for quality feedback
674
- const totalBytes = samples.reduce((sum, s) => sum + Math.ceil(s.length * 3 / 4), 0);
675
- const totalSecs = Math.round(totalBytes / 16000); // rough estimate at 128kbps
676
- const qualityNote = totalSecs < 30
677
- ? `Tip: only ~${totalSecs}s of audio provided. For better accuracy, use 1-2 minutes of clean, continuous speech.`
678
- : `Good: ~${totalSecs}s of audio used.`;
679
- return {
680
- success: true,
681
- data: {
682
- voice_id: data.voice_id,
683
- name: voiceName,
684
- samples_used: samples.length,
685
- noise_removed: removeNoise,
686
- quality_note: qualityNote,
687
- message: `Voice "${voiceName}" cloned successfully. Use this voice_id with speak action.`,
688
- },
689
- };
690
- }
691
- catch (err) {
692
- return { success: false, error: `Voice clone failed: ${err}` };
693
- }
976
+ }
977
+
978
+ // ── PVC UPLOAD ─────────────────────────────────────────────────────
979
+ // Upload audio samples to a PVC voice. Accepts audio_samples or file_paths.
980
+ case "pvc_upload":
981
+ {
982
+ const voiceId = args.voice_id;
983
+ if (!voiceId) return {
984
+ success: false,
985
+ error: "voice_id is required (from pvc_create)"
986
+ };
987
+ const samples = [];
988
+ if (Array.isArray(args.audio_samples)) {
989
+ for (const s of args.audio_samples) {
990
+ if (typeof s === "string" && s.length > 100) samples.push(s);
991
+ }
694
992
  }
695
- // ══════════════════════════════════════════════════════════════════
696
- // PROFESSIONAL VOICE CLONING (PVC) — near-perfect accuracy
697
- // Multi-step: create → upload → separate speakers → verify → train
698
- // Requires Creator plan or above on ElevenLabs.
699
- // ══════════════════════════════════════════════════════════════════
700
- // ── PVC CREATE ─────────────────────────────────────────────────────
701
- case "pvc_create": {
702
- const voiceName = args.voice_name;
703
- const language = args.language || "en";
704
- if (!voiceName)
705
- return { success: false, error: "voice_name is required" };
706
- try {
707
- const body = { name: voiceName, language };
708
- if (args.voice_description)
709
- body.description = args.voice_description;
710
- if (args.labels)
711
- body.labels = args.labels;
712
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc`, {
713
- method: "POST",
714
- headers: { "xi-api-key": apiKey, "Content-Type": "application/json" },
715
- body: JSON.stringify(body),
716
- });
717
- if (!resp.ok) {
718
- const errText = await resp.text();
719
- return { success: false, error: `PVC create error ${resp.status}: ${errText}` };
720
- }
721
- const data = await resp.json();
722
- return {
723
- success: true,
724
- data: {
725
- voice_id: data.voice_id,
726
- name: voiceName,
727
- language,
728
- next_step: "Upload audio samples with pvc_upload action, then use pvc_train to start training.",
729
- },
730
- };
731
- }
732
- catch (err) {
733
- return { success: false, error: `PVC create failed: ${err}` };
734
- }
993
+ if (typeof args.audio_base64 === "string" && args.audio_base64.length > 100) {
994
+ samples.push(args.audio_base64);
735
995
  }
736
- // ── PVC UPLOAD ─────────────────────────────────────────────────────
737
- // Upload audio samples to a PVC voice. Accepts audio_samples or file_paths.
738
- case "pvc_upload": {
739
- const voiceId = args.voice_id;
740
- if (!voiceId)
741
- return { success: false, error: "voice_id is required (from pvc_create)" };
742
- const samples = [];
743
- if (Array.isArray(args.audio_samples)) {
744
- for (const s of args.audio_samples) {
745
- if (typeof s === "string" && s.length > 100)
746
- samples.push(s);
747
- }
748
- }
749
- if (typeof args.audio_base64 === "string" && args.audio_base64.length > 100) {
750
- samples.push(args.audio_base64);
751
- }
752
- if (samples.length === 0) {
753
- return { success: false, error: "Provide file_paths or audio_samples. The more audio, the better the clone (30+ min recommended)." };
754
- }
755
- try {
756
- const formData = new FormData();
757
- for (let i = 0; i < samples.length; i++) {
758
- const buffer = Buffer.from(samples[i], "base64");
759
- const blob = new Blob([buffer], { type: "audio/mpeg" });
760
- formData.append("files", blob, `sample-${i}.mp3`);
761
- }
762
- const removeNoise = args.remove_background_noise ?? false;
763
- formData.append("remove_background_noise", String(removeNoise));
764
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/samples`, {
765
- method: "POST",
766
- headers: { "xi-api-key": apiKey },
767
- body: formData,
768
- });
769
- if (!resp.ok) {
770
- const errText = await resp.text();
771
- return { success: false, error: `PVC upload error ${resp.status}: ${errText}` };
772
- }
773
- const sampleData = await resp.json();
774
- const sampleIds = Array.isArray(sampleData) ? sampleData.map((s) => s.sample_id) : [];
775
- return {
776
- success: true,
777
- data: {
778
- voice_id: voiceId,
779
- samples_uploaded: samples.length,
780
- sample_ids: sampleIds,
781
- noise_removed: removeNoise,
782
- next_step: "If audio has multiple speakers, use pvc_separate_speakers. Otherwise use pvc_verify to start verification, then pvc_train.",
783
- },
784
- };
785
- }
786
- catch (err) {
787
- return { success: false, error: `PVC upload failed: ${err}` };
788
- }
996
+ if (samples.length === 0) {
997
+ return {
998
+ success: false,
999
+ error: "Provide file_paths or audio_samples. The more audio, the better the clone (30+ min recommended)."
1000
+ };
789
1001
  }
790
- // ── PVC SEPARATE SPEAKERS ──────────────────────────────────────────
791
- case "pvc_separate_speakers": {
792
- const voiceId = args.voice_id;
793
- const sampleId = args.sample_id;
794
- if (!voiceId || !sampleId)
795
- return { success: false, error: "voice_id and sample_id are required" };
796
- try {
797
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/samples/${sampleId}/separate-speakers`, { method: "POST", headers: { "xi-api-key": apiKey } });
798
- if (!resp.ok) {
799
- const errText = await resp.text();
800
- return { success: false, error: `Speaker separation error ${resp.status}: ${errText}` };
801
- }
802
- return {
803
- success: true,
804
- data: {
805
- voice_id: voiceId,
806
- sample_id: sampleId,
807
- status: "started",
808
- next_step: "Use pvc_status to check separation progress. When done, use pvc_select_speaker to pick the right speaker.",
809
- },
810
- };
811
- }
812
- catch (err) {
813
- return { success: false, error: `Speaker separation failed: ${err}` };
1002
+ try {
1003
+ const formData = new FormData();
1004
+ for (let i = 0; i < samples.length; i++) {
1005
+ const buffer = Buffer.from(samples[i], "base64");
1006
+ const blob = new Blob([buffer], {
1007
+ type: "audio/mpeg"
1008
+ });
1009
+ formData.append("files", blob, `sample-${i}.mp3`);
1010
+ }
1011
+ const removeNoise = args.remove_background_noise ?? false;
1012
+ formData.append("remove_background_noise", String(removeNoise));
1013
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/samples`, {
1014
+ method: "POST",
1015
+ headers: {
1016
+ "xi-api-key": apiKey
1017
+ },
1018
+ body: formData
1019
+ });
1020
+ if (!resp.ok) {
1021
+ const errText = await resp.text();
1022
+ return {
1023
+ success: false,
1024
+ error: `PVC upload error ${resp.status}: ${errText}`
1025
+ };
1026
+ }
1027
+ const sampleData = await resp.json();
1028
+ const sampleIds = Array.isArray(sampleData) ? sampleData.map(s => s.sample_id) : [];
1029
+ return {
1030
+ success: true,
1031
+ data: {
1032
+ voice_id: voiceId,
1033
+ samples_uploaded: samples.length,
1034
+ sample_ids: sampleIds,
1035
+ noise_removed: removeNoise,
1036
+ next_step: "If audio has multiple speakers, use pvc_separate_speakers. Otherwise use pvc_verify to start verification, then pvc_train."
814
1037
  }
1038
+ };
1039
+ } catch (err) {
1040
+ return {
1041
+ success: false,
1042
+ error: `PVC upload failed: ${err}`
1043
+ };
815
1044
  }
816
- // ── PVC STATUS ─────────────────────────────────────────────────────
817
- // Check speaker separation status or training status
818
- case "pvc_status": {
819
- const voiceId = args.voice_id;
820
- if (!voiceId)
821
- return { success: false, error: "voice_id is required" };
822
- try {
823
- // If sample_id provided, check speaker separation status
824
- if (args.sample_id) {
825
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/samples/${args.sample_id}/speakers`, { headers: { "xi-api-key": apiKey } });
826
- if (!resp.ok) {
827
- const errText = await resp.text();
828
- return { success: false, error: `PVC status error ${resp.status}: ${errText}` };
829
- }
830
- const data = await resp.json();
831
- return { success: true, data };
832
- }
833
- // Otherwise check overall voice / training status
834
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/${voiceId}`, {
835
- headers: { "xi-api-key": apiKey },
836
- });
837
- if (!resp.ok) {
838
- const errText = await resp.text();
839
- return { success: false, error: `PVC status error ${resp.status}: ${errText}` };
840
- }
841
- const voice = await resp.json();
842
- return {
843
- success: true,
844
- data: {
845
- voice_id: voice.voice_id,
846
- name: voice.name,
847
- category: voice.category,
848
- fine_tuning: voice.fine_tuning,
849
- samples: voice.samples?.length || 0,
850
- },
851
- };
1045
+ }
1046
+
1047
+ // ── PVC SEPARATE SPEAKERS ──────────────────────────────────────────
1048
+ case "pvc_separate_speakers":
1049
+ {
1050
+ const voiceId = args.voice_id;
1051
+ const sampleId = args.sample_id;
1052
+ if (!voiceId || !sampleId) return {
1053
+ success: false,
1054
+ error: "voice_id and sample_id are required"
1055
+ };
1056
+ try {
1057
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/samples/${sampleId}/separate-speakers`, {
1058
+ method: "POST",
1059
+ headers: {
1060
+ "xi-api-key": apiKey
852
1061
  }
853
- catch (err) {
854
- return { success: false, error: `PVC status failed: ${err}` };
1062
+ });
1063
+ if (!resp.ok) {
1064
+ const errText = await resp.text();
1065
+ return {
1066
+ success: false,
1067
+ error: `Speaker separation error ${resp.status}: ${errText}`
1068
+ };
1069
+ }
1070
+ return {
1071
+ success: true,
1072
+ data: {
1073
+ voice_id: voiceId,
1074
+ sample_id: sampleId,
1075
+ status: "started",
1076
+ next_step: "Use pvc_status to check separation progress. When done, use pvc_select_speaker to pick the right speaker."
855
1077
  }
1078
+ };
1079
+ } catch (err) {
1080
+ return {
1081
+ success: false,
1082
+ error: `Speaker separation failed: ${err}`
1083
+ };
856
1084
  }
857
- // ── PVC SELECT SPEAKER ─────────────────────────────────────────────
858
- case "pvc_select_speaker": {
859
- const voiceId = args.voice_id;
860
- const sampleId = args.sample_id;
861
- const speakerIds = args.speaker_ids;
862
- if (!voiceId || !sampleId)
863
- return { success: false, error: "voice_id and sample_id required" };
864
- if (!Array.isArray(speakerIds) || speakerIds.length === 0) {
865
- return { success: false, error: "speaker_ids array required (from pvc_status separation results)" };
1085
+ }
1086
+
1087
+ // ── PVC STATUS ─────────────────────────────────────────────────────
1088
+ // Check speaker separation status or training status
1089
+ case "pvc_status":
1090
+ {
1091
+ const voiceId = args.voice_id;
1092
+ if (!voiceId) return {
1093
+ success: false,
1094
+ error: "voice_id is required"
1095
+ };
1096
+ try {
1097
+ // If sample_id provided, check speaker separation status
1098
+ if (args.sample_id) {
1099
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/samples/${args.sample_id}/speakers`, {
1100
+ headers: {
1101
+ "xi-api-key": apiKey
1102
+ }
1103
+ });
1104
+ if (!resp.ok) {
1105
+ const errText = await resp.text();
1106
+ return {
1107
+ success: false,
1108
+ error: `PVC status error ${resp.status}: ${errText}`
1109
+ };
866
1110
  }
867
- try {
868
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/samples/${sampleId}`, {
869
- method: "POST",
870
- headers: { "xi-api-key": apiKey, "Content-Type": "application/json" },
871
- body: JSON.stringify({ selected_speaker_ids: speakerIds }),
872
- });
873
- if (!resp.ok) {
874
- const errText = await resp.text();
875
- return { success: false, error: `Select speaker error ${resp.status}: ${errText}` };
876
- }
877
- return {
878
- success: true,
879
- data: {
880
- voice_id: voiceId,
881
- sample_id: sampleId,
882
- selected_speakers: speakerIds,
883
- next_step: "Use pvc_verify for verification, then pvc_train to start training.",
884
- },
885
- };
1111
+ const data = await resp.json();
1112
+ return {
1113
+ success: true,
1114
+ data
1115
+ };
1116
+ }
1117
+
1118
+ // Otherwise check overall voice / training status
1119
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/${voiceId}`, {
1120
+ headers: {
1121
+ "xi-api-key": apiKey
886
1122
  }
887
- catch (err) {
888
- return { success: false, error: `Select speaker failed: ${err}` };
1123
+ });
1124
+ if (!resp.ok) {
1125
+ const errText = await resp.text();
1126
+ return {
1127
+ success: false,
1128
+ error: `PVC status error ${resp.status}: ${errText}`
1129
+ };
1130
+ }
1131
+ const voice = await resp.json();
1132
+ return {
1133
+ success: true,
1134
+ data: {
1135
+ voice_id: voice.voice_id,
1136
+ name: voice.name,
1137
+ category: voice.category,
1138
+ fine_tuning: voice.fine_tuning,
1139
+ samples: voice.samples?.length || 0
889
1140
  }
1141
+ };
1142
+ } catch (err) {
1143
+ return {
1144
+ success: false,
1145
+ error: `PVC status failed: ${err}`
1146
+ };
890
1147
  }
891
- // ── PVC VERIFY ─────────────────────────────────────────────────────
892
- // Request manual verification (easiest path — ElevenLabs staff reviews)
893
- case "pvc_verify": {
894
- const voiceId = args.voice_id;
895
- if (!voiceId)
896
- return { success: false, error: "voice_id is required" };
897
- try {
898
- const formData = new FormData();
899
- // Optional: extra context about why you have permission to use this voice
900
- const extraText = args.extra_text || "Voice owner has given consent for cloning.";
901
- formData.append("extra_text", extraText);
902
- // If verification audio provided (voice owner speaking), attach it
903
- if (typeof args.audio_base64 === "string" && args.audio_base64.length > 100) {
904
- const buffer = Buffer.from(args.audio_base64, "base64");
905
- const blob = new Blob([buffer], { type: "audio/mpeg" });
906
- formData.append("files", blob, "verification.mp3");
907
- }
908
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/verification`, {
909
- method: "POST",
910
- headers: { "xi-api-key": apiKey },
911
- body: formData,
912
- });
913
- if (!resp.ok) {
914
- const errText = await resp.text();
915
- return { success: false, error: `PVC verify error ${resp.status}: ${errText}` };
916
- }
917
- return {
918
- success: true,
919
- data: {
920
- voice_id: voiceId,
921
- status: "verification_requested",
922
- message: "Manual verification submitted. ElevenLabs will review. Once approved, use pvc_train to start training.",
923
- },
924
- };
925
- }
926
- catch (err) {
927
- return { success: false, error: `PVC verify failed: ${err}` };
928
- }
1148
+ }
1149
+
1150
+ // ── PVC SELECT SPEAKER ─────────────────────────────────────────────
1151
+ case "pvc_select_speaker":
1152
+ {
1153
+ const voiceId = args.voice_id;
1154
+ const sampleId = args.sample_id;
1155
+ const speakerIds = args.speaker_ids;
1156
+ if (!voiceId || !sampleId) return {
1157
+ success: false,
1158
+ error: "voice_id and sample_id required"
1159
+ };
1160
+ if (!Array.isArray(speakerIds) || speakerIds.length === 0) {
1161
+ return {
1162
+ success: false,
1163
+ error: "speaker_ids array required (from pvc_status separation results)"
1164
+ };
929
1165
  }
930
- // ── PVC TRAIN ──────────────────────────────────────────────────────
931
- case "pvc_train": {
932
- const voiceId = args.voice_id;
933
- if (!voiceId)
934
- return { success: false, error: "voice_id is required" };
935
- // eleven_multilingual_v2 is most stable for PVC training
936
- const modelId = args.model_id || "eleven_multilingual_v2";
937
- try {
938
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/train`, {
939
- method: "POST",
940
- headers: { "xi-api-key": apiKey, "Content-Type": "application/json" },
941
- body: JSON.stringify({ model_id: modelId }),
942
- });
943
- if (!resp.ok) {
944
- const errText = await resp.text();
945
- return { success: false, error: `PVC train error ${resp.status}: ${errText}` };
946
- }
947
- return {
948
- success: true,
949
- data: {
950
- voice_id: voiceId,
951
- model_id: modelId,
952
- status: "training_started",
953
- message: "Training started. Use pvc_status to monitor progress. Training typically takes 5-30 minutes depending on audio length.",
954
- },
955
- };
956
- }
957
- catch (err) {
958
- return { success: false, error: `PVC train failed: ${err}` };
1166
+ try {
1167
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/samples/${sampleId}`, {
1168
+ method: "POST",
1169
+ headers: {
1170
+ "xi-api-key": apiKey,
1171
+ "Content-Type": "application/json"
1172
+ },
1173
+ body: JSON.stringify({
1174
+ selected_speaker_ids: speakerIds
1175
+ })
1176
+ });
1177
+ if (!resp.ok) {
1178
+ const errText = await resp.text();
1179
+ return {
1180
+ success: false,
1181
+ error: `Select speaker error ${resp.status}: ${errText}`
1182
+ };
1183
+ }
1184
+ return {
1185
+ success: true,
1186
+ data: {
1187
+ voice_id: voiceId,
1188
+ sample_id: sampleId,
1189
+ selected_speakers: speakerIds,
1190
+ next_step: "Use pvc_verify for verification, then pvc_train to start training."
959
1191
  }
1192
+ };
1193
+ } catch (err) {
1194
+ return {
1195
+ success: false,
1196
+ error: `Select speaker failed: ${err}`
1197
+ };
960
1198
  }
961
- // ── FORCED ALIGNMENT ───────────────────────────────────────────────
962
- case "forced_alignment": {
963
- const audioBase64 = args.audio_base64;
964
- const transcript = args.transcript;
965
- if (!audioBase64)
966
- return { success: false, error: "audio_base64 parameter is required" };
967
- if (!transcript)
968
- return { success: false, error: "transcript parameter is required" };
969
- try {
970
- const formData = audioFormData(audioBase64);
971
- formData.append("text", transcript);
972
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/forced-alignment`, {
973
- method: "POST",
974
- headers: { "xi-api-key": apiKey },
975
- body: formData,
976
- });
977
- if (!resp.ok) {
978
- const errText = await resp.text();
979
- return { success: false, error: `ElevenLabs Forced Alignment error ${resp.status}: ${errText}` };
980
- }
981
- return { success: true, data: await resp.json() };
982
- }
983
- catch (err) {
984
- return { success: false, error: `Forced alignment failed: ${err}` };
1199
+ }
1200
+
1201
+ // ── PVC VERIFY ─────────────────────────────────────────────────────
1202
+ // Request manual verification (easiest path — ElevenLabs staff reviews)
1203
+ case "pvc_verify":
1204
+ {
1205
+ const voiceId = args.voice_id;
1206
+ if (!voiceId) return {
1207
+ success: false,
1208
+ error: "voice_id is required"
1209
+ };
1210
+ try {
1211
+ const formData = new FormData();
1212
+ // Optional: extra context about why you have permission to use this voice
1213
+ const extraText = args.extra_text || "Voice owner has given consent for cloning.";
1214
+ formData.append("extra_text", extraText);
1215
+
1216
+ // If verification audio provided (voice owner speaking), attach it
1217
+ if (typeof args.audio_base64 === "string" && args.audio_base64.length > 100) {
1218
+ const buffer = Buffer.from(args.audio_base64, "base64");
1219
+ const blob = new Blob([buffer], {
1220
+ type: "audio/mpeg"
1221
+ });
1222
+ formData.append("files", blob, "verification.mp3");
1223
+ }
1224
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/verification`, {
1225
+ method: "POST",
1226
+ headers: {
1227
+ "xi-api-key": apiKey
1228
+ },
1229
+ body: formData
1230
+ });
1231
+ if (!resp.ok) {
1232
+ const errText = await resp.text();
1233
+ return {
1234
+ success: false,
1235
+ error: `PVC verify error ${resp.status}: ${errText}`
1236
+ };
1237
+ }
1238
+ return {
1239
+ success: true,
1240
+ data: {
1241
+ voice_id: voiceId,
1242
+ status: "verification_requested",
1243
+ message: "Manual verification submitted. ElevenLabs will review. Once approved, use pvc_train to start training."
985
1244
  }
1245
+ };
1246
+ } catch (err) {
1247
+ return {
1248
+ success: false,
1249
+ error: `PVC verify failed: ${err}`
1250
+ };
986
1251
  }
987
- // ── SEARCH VOICES ──────────────────────────────────────────────────
988
- case "search_voices": {
989
- const query = args.query || "";
990
- try {
991
- // Use shared voices endpoint for broader library search
992
- const params = new URLSearchParams({ page_size: "25" });
993
- if (query)
994
- params.set("search", query);
995
- if (args.gender)
996
- params.set("gender", args.gender);
997
- if (args.language)
998
- params.set("language", args.language);
999
- if (args.accent)
1000
- params.set("accent", args.accent);
1001
- if (args.age)
1002
- params.set("age", args.age);
1003
- if (args.use_case)
1004
- params.set("use_case", args.use_case);
1005
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/shared-voices?${params}`, {
1006
- headers: { "xi-api-key": apiKey },
1007
- });
1008
- if (!resp.ok) {
1009
- const errText = await resp.text();
1010
- return { success: false, error: `Voice search error ${resp.status}: ${errText}` };
1011
- }
1012
- const data = await resp.json();
1013
- const voices = (data.voices || []).map((v) => ({
1014
- voice_id: v.voice_id,
1015
- name: v.name,
1016
- category: v.category,
1017
- gender: v.gender,
1018
- accent: v.accent,
1019
- age: v.age,
1020
- language: v.language,
1021
- use_case: v.use_case,
1022
- description: v.description,
1023
- preview_url: v.preview_url,
1024
- }));
1025
- return { success: true, data: { count: voices.length, voices } };
1026
- }
1027
- catch (err) {
1028
- return { success: false, error: `Voice search failed: ${err}` };
1252
+ }
1253
+
1254
+ // ── PVC TRAIN ──────────────────────────────────────────────────────
1255
+ case "pvc_train":
1256
+ {
1257
+ const voiceId = args.voice_id;
1258
+ if (!voiceId) return {
1259
+ success: false,
1260
+ error: "voice_id is required"
1261
+ };
1262
+
1263
+ // eleven_multilingual_v2 is most stable for PVC training
1264
+ const modelId = args.model_id || "eleven_multilingual_v2";
1265
+ try {
1266
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/pvc/${voiceId}/train`, {
1267
+ method: "POST",
1268
+ headers: {
1269
+ "xi-api-key": apiKey,
1270
+ "Content-Type": "application/json"
1271
+ },
1272
+ body: JSON.stringify({
1273
+ model_id: modelId
1274
+ })
1275
+ });
1276
+ if (!resp.ok) {
1277
+ const errText = await resp.text();
1278
+ return {
1279
+ success: false,
1280
+ error: `PVC train error ${resp.status}: ${errText}`
1281
+ };
1282
+ }
1283
+ return {
1284
+ success: true,
1285
+ data: {
1286
+ voice_id: voiceId,
1287
+ model_id: modelId,
1288
+ status: "training_started",
1289
+ message: "Training started. Use pvc_status to monitor progress. Training typically takes 5-30 minutes depending on audio length."
1029
1290
  }
1291
+ };
1292
+ } catch (err) {
1293
+ return {
1294
+ success: false,
1295
+ error: `PVC train failed: ${err}`
1296
+ };
1030
1297
  }
1031
- // ── LIST VOICES ────────────────────────────────────────────────────
1032
- case "list_voices": {
1033
- try {
1034
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices`, {
1035
- headers: { "xi-api-key": apiKey },
1036
- });
1037
- if (!resp.ok) {
1038
- const errText = await resp.text();
1039
- return { success: false, error: `List Voices error ${resp.status}: ${errText}` };
1040
- }
1041
- const data = await resp.json();
1042
- const voices = (data.voices || []).map((v) => ({
1043
- voice_id: v.voice_id, name: v.name, category: v.category,
1044
- labels: v.labels, description: v.description, preview_url: v.preview_url,
1045
- }));
1046
- return { success: true, data: { count: voices.length, voices } };
1298
+ }
1299
+
1300
+ // ── FORCED ALIGNMENT ───────────────────────────────────────────────
1301
+ case "forced_alignment":
1302
+ {
1303
+ const audioBase64 = args.audio_base64;
1304
+ const transcript = args.transcript;
1305
+ if (!audioBase64) return {
1306
+ success: false,
1307
+ error: "audio_base64 parameter is required"
1308
+ };
1309
+ if (!transcript) return {
1310
+ success: false,
1311
+ error: "transcript parameter is required"
1312
+ };
1313
+ try {
1314
+ const formData = audioFormData(audioBase64);
1315
+ formData.append("text", transcript);
1316
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/forced-alignment`, {
1317
+ method: "POST",
1318
+ headers: {
1319
+ "xi-api-key": apiKey
1320
+ },
1321
+ body: formData
1322
+ });
1323
+ if (!resp.ok) {
1324
+ const errText = await resp.text();
1325
+ return {
1326
+ success: false,
1327
+ error: `ElevenLabs Forced Alignment error ${resp.status}: ${errText}`
1328
+ };
1329
+ }
1330
+ return {
1331
+ success: true,
1332
+ data: await resp.json()
1333
+ };
1334
+ } catch (err) {
1335
+ return {
1336
+ success: false,
1337
+ error: `Forced alignment failed: ${err}`
1338
+ };
1339
+ }
1340
+ }
1341
+
1342
+ // ── SEARCH VOICES ──────────────────────────────────────────────────
1343
+ case "search_voices":
1344
+ {
1345
+ const query = args.query || "";
1346
+ try {
1347
+ // Use shared voices endpoint for broader library search
1348
+ const params = new URLSearchParams({
1349
+ page_size: "25"
1350
+ });
1351
+ if (query) params.set("search", query);
1352
+ if (args.gender) params.set("gender", args.gender);
1353
+ if (args.language) params.set("language", args.language);
1354
+ if (args.accent) params.set("accent", args.accent);
1355
+ if (args.age) params.set("age", args.age);
1356
+ if (args.use_case) params.set("use_case", args.use_case);
1357
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/shared-voices?${params}`, {
1358
+ headers: {
1359
+ "xi-api-key": apiKey
1047
1360
  }
1048
- catch (err) {
1049
- return { success: false, error: `List voices failed: ${err}` };
1361
+ });
1362
+ if (!resp.ok) {
1363
+ const errText = await resp.text();
1364
+ return {
1365
+ success: false,
1366
+ error: `Voice search error ${resp.status}: ${errText}`
1367
+ };
1368
+ }
1369
+ const data = await resp.json();
1370
+ const voices = (data.voices || []).map(v => ({
1371
+ voice_id: v.voice_id,
1372
+ name: v.name,
1373
+ category: v.category,
1374
+ gender: v.gender,
1375
+ accent: v.accent,
1376
+ age: v.age,
1377
+ language: v.language,
1378
+ use_case: v.use_case,
1379
+ description: v.description,
1380
+ preview_url: v.preview_url
1381
+ }));
1382
+ return {
1383
+ success: true,
1384
+ data: {
1385
+ count: voices.length,
1386
+ voices
1050
1387
  }
1388
+ };
1389
+ } catch (err) {
1390
+ return {
1391
+ success: false,
1392
+ error: `Voice search failed: ${err}`
1393
+ };
1051
1394
  }
1052
- // ── GET VOICE ──────────────────────────────────────────────────────
1053
- case "get_voice": {
1054
- const voiceInput = (args.voice_name || args.voice_id);
1055
- if (!voiceInput)
1056
- return { success: false, error: "voice_id or voice_name is required" };
1057
- try {
1058
- const voiceId = await resolveVoiceId(voiceInput, apiKey, sid);
1059
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/${voiceId}`, {
1060
- headers: { "xi-api-key": apiKey },
1061
- });
1062
- if (!resp.ok) {
1063
- const errText = await resp.text();
1064
- return { success: false, error: `Get Voice error ${resp.status}: ${errText}` };
1065
- }
1066
- const voice = await resp.json();
1067
- return {
1068
- success: true,
1069
- data: {
1070
- voice_id: voice.voice_id, name: voice.name, category: voice.category,
1071
- labels: voice.labels, description: voice.description, preview_url: voice.preview_url,
1072
- settings: voice.settings, fine_tuning: voice.fine_tuning,
1073
- },
1074
- };
1395
+ }
1396
+
1397
+ // ── LIST VOICES ────────────────────────────────────────────────────
1398
+ case "list_voices":
1399
+ {
1400
+ try {
1401
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices`, {
1402
+ headers: {
1403
+ "xi-api-key": apiKey
1075
1404
  }
1076
- catch (err) {
1077
- return { success: false, error: `Get voice failed: ${err}` };
1405
+ });
1406
+ if (!resp.ok) {
1407
+ const errText = await resp.text();
1408
+ return {
1409
+ success: false,
1410
+ error: `List Voices error ${resp.status}: ${errText}`
1411
+ };
1412
+ }
1413
+ const data = await resp.json();
1414
+ const voices = (data.voices || []).map(v => ({
1415
+ voice_id: v.voice_id,
1416
+ name: v.name,
1417
+ category: v.category,
1418
+ labels: v.labels,
1419
+ description: v.description,
1420
+ preview_url: v.preview_url
1421
+ }));
1422
+ return {
1423
+ success: true,
1424
+ data: {
1425
+ count: voices.length,
1426
+ voices
1078
1427
  }
1428
+ };
1429
+ } catch (err) {
1430
+ return {
1431
+ success: false,
1432
+ error: `List voices failed: ${err}`
1433
+ };
1079
1434
  }
1080
- // ── USAGE / QUOTA ──────────────────────────────────────────────────
1081
- case "usage": {
1082
- try {
1083
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/user/subscription`, {
1084
- headers: { "xi-api-key": apiKey },
1085
- });
1086
- if (!resp.ok) {
1087
- const errText = await resp.text();
1088
- return { success: false, error: `Usage error ${resp.status}: ${errText}` };
1089
- }
1090
- const sub = await resp.json();
1091
- return {
1092
- success: true,
1093
- data: {
1094
- tier: sub.tier,
1095
- character_count: sub.character_count,
1096
- character_limit: sub.character_limit,
1097
- characters_remaining: (sub.character_limit || 0) - (sub.character_count || 0),
1098
- usage_percent: sub.character_limit
1099
- ? Math.round(((sub.character_count || 0) / sub.character_limit) * 100)
1100
- : 0,
1101
- next_reset: sub.next_character_count_reset_unix
1102
- ? new Date(sub.next_character_count_reset_unix * 1000).toISOString()
1103
- : null,
1104
- max_voice_add_edits: sub.max_voice_add_edits,
1105
- voice_add_edit_counter: sub.voice_add_edit_counter,
1106
- professional_voice_limit: sub.professional_voice_limit,
1107
- },
1108
- };
1435
+ }
1436
+
1437
+ // ── GET VOICE ──────────────────────────────────────────────────────
1438
+ case "get_voice":
1439
+ {
1440
+ const voiceInput = args.voice_name || args.voice_id;
1441
+ if (!voiceInput) return {
1442
+ success: false,
1443
+ error: "voice_id or voice_name is required"
1444
+ };
1445
+ try {
1446
+ const voiceId = await resolveVoiceId(voiceInput, apiKey, sid);
1447
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices/${voiceId}`, {
1448
+ headers: {
1449
+ "xi-api-key": apiKey
1109
1450
  }
1110
- catch (err) {
1111
- return { success: false, error: `Usage check failed: ${err}` };
1451
+ });
1452
+ if (!resp.ok) {
1453
+ const errText = await resp.text();
1454
+ return {
1455
+ success: false,
1456
+ error: `Get Voice error ${resp.status}: ${errText}`
1457
+ };
1458
+ }
1459
+ const voice = await resp.json();
1460
+ return {
1461
+ success: true,
1462
+ data: {
1463
+ voice_id: voice.voice_id,
1464
+ name: voice.name,
1465
+ category: voice.category,
1466
+ labels: voice.labels,
1467
+ description: voice.description,
1468
+ preview_url: voice.preview_url,
1469
+ settings: voice.settings,
1470
+ fine_tuning: voice.fine_tuning
1112
1471
  }
1472
+ };
1473
+ } catch (err) {
1474
+ return {
1475
+ success: false,
1476
+ error: `Get voice failed: ${err}`
1477
+ };
1113
1478
  }
1114
- // ── LIST MODELS ────────────────────────────────────────────────────
1115
- case "list_models": {
1116
- try {
1117
- const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/models`, {
1118
- headers: { "xi-api-key": apiKey },
1119
- });
1120
- if (!resp.ok) {
1121
- const errText = await resp.text();
1122
- return { success: false, error: `List Models error ${resp.status}: ${errText}` };
1479
+ }
1480
+
1481
+ // ── USAGE / QUOTA ──────────────────────────────────────────────────
1482
+ case "usage":
1483
+ {
1484
+ try {
1485
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/user/subscription`, {
1486
+ headers: {
1487
+ "xi-api-key": apiKey
1488
+ }
1489
+ });
1490
+ if (!resp.ok) {
1491
+ // If key lacks user_read scope, fall back to a partial status check
1492
+ if (resp.status === 401 || resp.status === 403) {
1493
+ const voicesResp = await fetchWithRetry(`${ELEVENLABS_BASE}/voices`, {
1494
+ headers: {
1495
+ "xi-api-key": apiKey
1496
+ }
1497
+ });
1498
+ const keyWorks = voicesResp.ok;
1499
+ const voiceCount = keyWorks ? (await voicesResp.json()).voices?.length ?? 0 : 0;
1500
+ return {
1501
+ success: true,
1502
+ data: {
1503
+ tier: "unknown (API key missing user_read scope)",
1504
+ character_count: null,
1505
+ character_limit: null,
1506
+ characters_remaining: null,
1507
+ usage_percent: null,
1508
+ next_reset: null,
1509
+ key_valid: keyWorks,
1510
+ voice_count: voiceCount,
1511
+ note: "ElevenLabs API key is missing the 'user_read' permission. Regenerate the key at elevenlabs.io with full permissions to see usage stats. All generation features (speak, music, SFX) still work."
1123
1512
  }
1124
- const models = await resp.json();
1125
- return {
1126
- success: true,
1127
- data: (models || []).map((m) => ({
1128
- model_id: m.model_id, name: m.name, description: m.description,
1129
- can_be_finetuned: m.can_be_finetuned,
1130
- can_do_text_to_speech: m.can_do_text_to_speech,
1131
- can_do_voice_conversion: m.can_do_voice_conversion,
1132
- languages: m.languages,
1133
- })),
1134
- };
1513
+ };
1135
1514
  }
1136
- catch (err) {
1137
- return { success: false, error: `List models failed: ${err}` };
1515
+ const errText = await resp.text();
1516
+ return {
1517
+ success: false,
1518
+ error: `Usage error ${resp.status}: ${errText}`
1519
+ };
1520
+ }
1521
+ const sub = await resp.json();
1522
+ return {
1523
+ success: true,
1524
+ data: {
1525
+ tier: sub.tier,
1526
+ character_count: sub.character_count,
1527
+ character_limit: sub.character_limit,
1528
+ characters_remaining: (sub.character_limit || 0) - (sub.character_count || 0),
1529
+ usage_percent: sub.character_limit ? Math.round((sub.character_count || 0) / sub.character_limit * 100) : 0,
1530
+ next_reset: sub.next_character_count_reset_unix ? new Date(sub.next_character_count_reset_unix * 1000).toISOString() : null,
1531
+ max_voice_add_edits: sub.max_voice_add_edits,
1532
+ voice_add_edit_counter: sub.voice_add_edit_counter,
1533
+ professional_voice_limit: sub.professional_voice_limit
1138
1534
  }
1535
+ };
1536
+ } catch (err) {
1537
+ return {
1538
+ success: false,
1539
+ error: `Usage check failed: ${err}`
1540
+ };
1139
1541
  }
1140
- default:
1542
+ }
1543
+
1544
+ // ── LIST MODELS ────────────────────────────────────────────────────
1545
+ case "list_models":
1546
+ {
1547
+ try {
1548
+ const resp = await fetchWithRetry(`${ELEVENLABS_BASE}/models`, {
1549
+ headers: {
1550
+ "xi-api-key": apiKey
1551
+ }
1552
+ });
1553
+ if (!resp.ok) {
1554
+ const errText = await resp.text();
1141
1555
  return {
1142
- success: false,
1143
- error: `Unknown voice action: ${action}. Valid: speak, batch, sound_effects, music_plan, music_compose, music_compose_detailed, speech_to_text, stem_separation, voice_changer, audio_isolation, voice_design, voice_design_create, clone_voice, forced_alignment, search_voices, list_voices, get_voice, usage, list_models, pvc_create, pvc_upload, pvc_separate_speakers, pvc_status, pvc_select_speaker, pvc_verify, pvc_train`,
1556
+ success: false,
1557
+ error: `List Models error ${resp.status}: ${errText}`
1144
1558
  };
1145
- }
1559
+ }
1560
+ const models = await resp.json();
1561
+ return {
1562
+ success: true,
1563
+ data: (models || []).map(m => ({
1564
+ model_id: m.model_id,
1565
+ name: m.name,
1566
+ description: m.description,
1567
+ can_be_finetuned: m.can_be_finetuned,
1568
+ can_do_text_to_speech: m.can_do_text_to_speech,
1569
+ can_do_voice_conversion: m.can_do_voice_conversion,
1570
+ languages: m.languages
1571
+ }))
1572
+ };
1573
+ } catch (err) {
1574
+ return {
1575
+ success: false,
1576
+ error: `List models failed: ${err}`
1577
+ };
1578
+ }
1579
+ }
1580
+ default:
1581
+ return {
1582
+ success: false,
1583
+ error: `Unknown voice action: ${action}. Valid: speak, batch, sound_effects, music_plan, music_compose, music_compose_detailed, speech_to_text, stem_separation, voice_changer, audio_isolation, voice_design, voice_design_create, clone_voice, forced_alignment, search_voices, list_voices, get_voice, usage, list_models, pvc_create, pvc_upload, pvc_separate_speakers, pvc_status, pvc_select_speaker, pvc_verify, pvc_train`
1584
+ };
1585
+ }
1146
1586
  }
1587
+ //# sourceMappingURL=voice.js.map