@xopcai/xopc 0.0.115 → 0.0.116

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 (487) hide show
  1. package/README.md +5 -4
  2. package/README.zh-CN.md +5 -4
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  5. package/dist/extensions/feishu/src/workflow-progress.js +1 -1
  6. package/dist/extensions/telegram/src/adapters/streaming-adapter.js +1 -1
  7. package/dist/extensions/telegram/src/inbound-processor.js +1 -1
  8. package/dist/extensions/telegram/src/plugin.js +1 -1
  9. package/dist/extensions/telegram/src/routing-integration.js +2 -2
  10. package/dist/extensions/telegram/src/update-offset-store.js +1 -1
  11. package/dist/extensions/telegram/src/workflow-progress.js +1 -1
  12. package/dist/extensions/telegram/xopc.extension.json +1 -1
  13. package/dist/extensions/weixin/src/api/api.js +2 -2
  14. package/dist/extensions/weixin/src/auth/accounts.js +1 -1
  15. package/dist/extensions/weixin/src/cdn/upload.js +1 -1
  16. package/dist/extensions/weixin/src/media/data-url.js +1 -1
  17. package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
  18. package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
  19. package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
  20. package/dist/extensions/weixin/src/plugin.js +1 -1
  21. package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
  22. package/dist/extensions/weixin/src/workflow-progress.js +1 -1
  23. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DVnBfANm.js → agent-avatar-dicebear-CWM1UU5P.js} +1 -1
  24. package/dist/gateway/static/root/assets/agent-browser-settings-page-D1SyFYeh.js +6 -0
  25. package/dist/gateway/static/root/assets/agent-defaults-tabbed-page-C9YxgvuI.js +1 -0
  26. package/dist/gateway/static/root/assets/agents-CteVs3UO.js +71 -0
  27. package/dist/gateway/static/root/assets/app-management-settings-panel-yXSXY_bu.js +1 -0
  28. package/dist/gateway/static/root/assets/{appearance-settings-BQpPSTHD.js → appearance-settings-DjY1P_ug.js} +1 -1
  29. package/dist/gateway/static/root/assets/apps-page-CVQ1M8Ax.js +1 -0
  30. package/dist/gateway/static/root/assets/{archive-plugin-CXu-Csep.js → archive-plugin-CytE5j-e.js} +1 -1
  31. package/dist/gateway/static/root/assets/{attachment-load-BAjgOoGY.js → attachment-load-BvvrN6rF.js} +1 -1
  32. package/dist/gateway/static/root/assets/{binary-plugins-BSBgB2hO.js → binary-plugins-IBWSitnj.js} +1 -1
  33. package/dist/gateway/static/root/assets/{block-editor-BpXw9TTR.js → block-editor-C-wDpsf-.js} +3 -3
  34. package/dist/gateway/static/root/assets/{channels-settings-BbPUpzWB.js → channels-settings-Cd3JbeQ1.js} +1 -1
  35. package/dist/gateway/static/root/assets/{config-api-NOoPiIYz.js → config-api-C4RAtTKW.js} +1 -1
  36. package/dist/gateway/static/root/assets/connectors-page-MsMa5qBr.js +2 -0
  37. package/dist/gateway/static/root/assets/{cron-api-C9BOqAXd.js → cron-api-sJNMqB6P.js} +1 -1
  38. package/dist/gateway/static/root/assets/cron-page-D30CSwkn.js +3 -0
  39. package/dist/gateway/static/root/assets/dreaming-settings-6fCUWRQf.js +1 -0
  40. package/dist/gateway/static/root/assets/{extension-debug-page-D9kgUHCc.js → extension-debug-page-BAIyjDlq.js} +1 -1
  41. package/dist/gateway/static/root/assets/{extension-page-CGyhXqzR.js → extension-page-DEhv2GpC.js} +1 -1
  42. package/dist/gateway/static/root/assets/{extension-settings-page-DL8Xquqd.js → extension-settings-page-DOuAxTDW.js} +1 -1
  43. package/dist/gateway/static/root/assets/{fetch-D0VXA932.js → fetch-CTsREQIe.js} +1 -1
  44. package/dist/gateway/static/root/assets/{format-interval-label-Be8SmgUw.js → format-interval-label-D35Bfhtx.js} +1 -1
  45. package/dist/gateway/static/root/assets/{gateway-config-api-C-xjn__h.js → gateway-config-api-C6989a-W.js} +1 -1
  46. package/dist/gateway/static/root/assets/gateway-settings-BpF26t91.js +1 -0
  47. package/dist/gateway/static/root/assets/{gateway-startup-retry-DERUH61L.js → gateway-startup-retry-C1KAHrd2.js} +1 -1
  48. package/dist/gateway/static/root/assets/goal-detail-page-DOjhmRyf.js +1 -0
  49. package/dist/gateway/static/root/assets/{goals-config-api-BPmqWk90.js → goals-config-api-KnLGpu6h.js} +1 -1
  50. package/dist/gateway/static/root/assets/goals-page-Xl9_wrFc.js +2 -0
  51. package/dist/gateway/static/root/assets/goals-settings-DtPtqZi8.js +1 -0
  52. package/dist/gateway/static/root/assets/{heartbeat-config-api-Bfdzr8oV.js → heartbeat-config-api-uKw1f0IG.js} +1 -1
  53. package/dist/gateway/static/root/assets/heartbeat-settings-DN9vEj9P.js +1 -0
  54. package/dist/gateway/static/root/assets/index-C8ytlAF2.js +122 -0
  55. package/dist/gateway/static/root/assets/index-CVpJW2NS.css +1 -0
  56. package/dist/gateway/static/root/assets/{interval-presets-utKbuCRY.js → interval-presets-Dq0FXJ65.js} +1 -1
  57. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CjqS6c6V.js → keyboard-shortcuts-settings-C9_t4m7t.js} +1 -1
  58. package/dist/gateway/static/root/assets/logs-page-SNkWzidV.js +2 -0
  59. package/dist/gateway/static/root/assets/{markdown-split-DW8C47iB.js → markdown-split-Ca87W8-h.js} +1 -1
  60. package/dist/gateway/static/root/assets/{media-plugins-D6HwDhDT.js → media-plugins-BAcUjPRJ.js} +1 -1
  61. package/dist/gateway/static/root/assets/messages-BqzIDrlr.js +3 -0
  62. package/dist/gateway/static/root/assets/models-hub-panel-BO4o-GmO.js +2 -0
  63. package/dist/gateway/static/root/assets/note-detail-page-DLV_XDmT.js +2 -0
  64. package/dist/gateway/static/root/assets/{note-image-lightbox-pJM35Pz5.js → note-image-lightbox-hnSX5SSn.js} +1 -1
  65. package/dist/gateway/static/root/assets/notes-page-ycsDlkFA.js +1 -0
  66. package/dist/gateway/static/root/assets/{preview-open-alternatives-CVwHuyN6.js → preview-open-alternatives-CNgM20H5.js} +1 -1
  67. package/dist/gateway/static/root/assets/remote-access-hub-DQpnE8K9.js +1 -0
  68. package/dist/gateway/static/root/assets/{save-bar-controls-Ba-UEk7x.js → save-bar-controls-CcdjGcfT.js} +1 -1
  69. package/dist/gateway/static/root/assets/{schedule-field-CNN9595U.js → schedule-field-CYr39z76.js} +1 -1
  70. package/dist/gateway/static/root/assets/{schema-form-DnNMmtD5.js → schema-form-BRAJgZQ2.js} +1 -1
  71. package/dist/gateway/static/root/assets/sessions-page-BZui7iAY.js +1 -0
  72. package/dist/gateway/static/root/assets/settings-advanced-gate-DEfzZ1ax.js +1 -0
  73. package/dist/gateway/static/root/assets/{settings-collapsible-section-CmOz80i5.js → settings-collapsible-section-BhzjeGWi.js} +1 -1
  74. package/dist/gateway/static/root/assets/{settings-form-section-BmK7JJOV.js → settings-form-section-Cz3Pcr9b.js} +1 -1
  75. package/dist/gateway/static/root/assets/settings-page-CLuSsu1-.js +1 -0
  76. package/dist/gateway/static/root/assets/setup-status-panel-Cq4qpXLi.js +1 -0
  77. package/dist/gateway/static/root/assets/{share-preview-page-Dp2qGrHC.js → share-preview-page-DL4y9K5e.js} +1 -1
  78. package/dist/gateway/static/root/assets/shares-settings-B9F3HyYQ.js +1 -0
  79. package/dist/gateway/static/root/assets/skill-reload-api-Bp9UPPwm.js +1 -0
  80. package/dist/gateway/static/root/assets/skills-page-BIoBsfSV.js +2 -0
  81. package/dist/gateway/static/root/assets/system-settings-panel-FD2jxRZ3.js +1 -0
  82. package/dist/gateway/static/root/assets/use-agent-defaults-form-Cz2_pPzu.js +1 -0
  83. package/dist/gateway/static/root/assets/{use-channel-catalog-CHlO8J9G.js → use-channel-catalog-BInPrfR5.js} +1 -1
  84. package/dist/gateway/static/root/assets/{use-settings-tab-guard-CW5JORnl.js → use-settings-tab-guard-nuHMmZQv.js} +1 -1
  85. package/dist/gateway/static/root/assets/voice-api-key-field-BUIrKb8s.js +1 -0
  86. package/dist/gateway/static/root/assets/{voice-player-store-DDDWYHvg.js → voice-player-store-1U3vFppm.js} +1 -1
  87. package/dist/gateway/static/root/assets/workflows-page-qE3SexnO.js +18 -0
  88. package/dist/gateway/static/root/index.html +5 -5
  89. package/dist/package.js +1 -1
  90. package/dist/src/agent/agent-manager.js +6 -6
  91. package/dist/src/agent/agent-scope.js +1 -1
  92. package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
  93. package/dist/src/agent/bootstrap/project-agents-context.js +1 -1
  94. package/dist/src/agent/context/workspace-seed.js +2 -2
  95. package/dist/src/agent/context/workspace-state.js +1 -1
  96. package/dist/src/agent/context/workspace.js +1 -1
  97. package/dist/src/agent/goals/checklist-judge.d.ts +1 -0
  98. package/dist/src/agent/goals/checklist-judge.js +1 -1
  99. package/dist/src/agent/goals/checklist-judge.js.map +1 -1
  100. package/dist/src/agent/goals/evaluate-turn.d.ts +1 -0
  101. package/dist/src/agent/goals/evaluate-turn.js +1 -0
  102. package/dist/src/agent/goals/evaluate-turn.js.map +1 -1
  103. package/dist/src/agent/goals/goal-locale.js +2 -2
  104. package/dist/src/agent/goals/post-turn.js +11 -4
  105. package/dist/src/agent/goals/post-turn.js.map +1 -1
  106. package/dist/src/agent/image/load-image-media.js +2 -2
  107. package/dist/src/agent/image/understanding/pi-ai-provider.js +5 -2
  108. package/dist/src/agent/image/understanding/pi-ai-provider.js.map +1 -1
  109. package/dist/src/agent/image/understanding/provider-registry.d.ts +1 -0
  110. package/dist/src/agent/image/understanding/provider-registry.js +16 -2
  111. package/dist/src/agent/image/understanding/provider-registry.js.map +1 -1
  112. package/dist/src/agent/ipc/inbox.js +3 -3
  113. package/dist/src/agent/ipc/socket.js +1 -1
  114. package/dist/src/agent/mcp/bundle-mcp-materialize.js +1 -1
  115. package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
  116. package/dist/src/agent/mcp/mcp-transport-config.js +1 -1
  117. package/dist/src/agent/mcp/mcp-transport.js +1 -1
  118. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  119. package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
  120. package/dist/src/agent/memory/dreaming/events.js +1 -1
  121. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  122. package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
  123. package/dist/src/agent/memory/dreaming/preview.js +1 -1
  124. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  125. package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
  126. package/dist/src/agent/memory/dreaming/utils.js +1 -1
  127. package/dist/src/agent/memory/memory-flush.js +1 -1
  128. package/dist/src/agent/memory/plugin-discovery.js +1 -1
  129. package/dist/src/agent/models/manager.js +1 -1
  130. package/dist/src/agent/prompt/memory/index.js +1 -1
  131. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  132. package/dist/src/agent/reply/post-compaction-context.js +1 -1
  133. package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
  134. package/dist/src/agent/sandbox/path-policy.js +2 -2
  135. package/dist/src/agent/service.js +4 -4
  136. package/dist/src/agent/session/session-inspector.js +1 -1
  137. package/dist/src/agent/skills/config.js +2 -2
  138. package/dist/src/agent/skills/filesystem-watcher.js +1 -1
  139. package/dist/src/agent/skills/hub-hash.js +2 -2
  140. package/dist/src/agent/skills/hub-lock.js +1 -1
  141. package/dist/src/agent/skills/hub-pull.js +2 -2
  142. package/dist/src/agent/skills/index.js +2 -2
  143. package/dist/src/agent/skills/installer.js +2 -2
  144. package/dist/src/agent/skills/managed-store.js +1 -1
  145. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  146. package/dist/src/agent/skills/skill-manager.js +1 -1
  147. package/dist/src/agent/skills/skill-view-path.js +1 -1
  148. package/dist/src/agent/skills/test-framework.js +1 -1
  149. package/dist/src/agent/starter-agents.js +1 -1
  150. package/dist/src/agent/tools/composio-tool.d.ts +8 -0
  151. package/dist/src/agent/tools/composio-tool.js +46 -0
  152. package/dist/src/agent/tools/composio-tool.js.map +1 -0
  153. package/dist/src/agent/tools/dreaming-tool.js +1 -1
  154. package/dist/src/agent/tools/factory.js +13 -8
  155. package/dist/src/agent/tools/factory.js.map +1 -1
  156. package/dist/src/agent/tools/find.js +1 -1
  157. package/dist/src/agent/tools/grep.js +1 -1
  158. package/dist/src/agent/tools/image-generate-tool.js +1 -1
  159. package/dist/src/agent/tools/index.d.ts +1 -0
  160. package/dist/src/agent/tools/index.js +2 -1
  161. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  162. package/dist/src/agent/tools/workflow-tool.js +1 -1
  163. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +1 -1
  164. package/dist/src/agent/workflow/builtins/audit-repo.js +15 -2
  165. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
  166. package/dist/src/agent/workflow/builtins/client-proposal.d.ts +1 -1
  167. package/dist/src/agent/workflow/builtins/client-proposal.js +10 -1
  168. package/dist/src/agent/workflow/builtins/client-proposal.js.map +1 -1
  169. package/dist/src/agent/workflow/builtins/competitor-scan.d.ts +1 -1
  170. package/dist/src/agent/workflow/builtins/competitor-scan.js +16 -2
  171. package/dist/src/agent/workflow/builtins/competitor-scan.js.map +1 -1
  172. package/dist/src/agent/workflow/builtins/content-draft.d.ts +1 -1
  173. package/dist/src/agent/workflow/builtins/content-draft.js +15 -2
  174. package/dist/src/agent/workflow/builtins/content-draft.js.map +1 -1
  175. package/dist/src/agent/workflow/builtins/content-repurpose.d.ts +1 -1
  176. package/dist/src/agent/workflow/builtins/content-repurpose.js +10 -1
  177. package/dist/src/agent/workflow/builtins/content-repurpose.js.map +1 -1
  178. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +1 -1
  179. package/dist/src/agent/workflow/builtins/debug-incident.js +9 -1
  180. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -1
  181. package/dist/src/agent/workflow/builtins/decision-compare.d.ts +1 -1
  182. package/dist/src/agent/workflow/builtins/decision-compare.js +16 -2
  183. package/dist/src/agent/workflow/builtins/decision-compare.js.map +1 -1
  184. package/dist/src/agent/workflow/builtins/implementation-plan.d.ts +1 -1
  185. package/dist/src/agent/workflow/builtins/implementation-plan.js +11 -1
  186. package/dist/src/agent/workflow/builtins/implementation-plan.js.map +1 -1
  187. package/dist/src/agent/workflow/builtins/inbox-triage.d.ts +1 -1
  188. package/dist/src/agent/workflow/builtins/inbox-triage.js +11 -1
  189. package/dist/src/agent/workflow/builtins/inbox-triage.js.map +1 -1
  190. package/dist/src/agent/workflow/builtins/meeting-prep.d.ts +1 -1
  191. package/dist/src/agent/workflow/builtins/meeting-prep.js +9 -1
  192. package/dist/src/agent/workflow/builtins/meeting-prep.js.map +1 -1
  193. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +1 -1
  194. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +9 -1
  195. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
  196. package/dist/src/agent/workflow/builtins/offer-design.d.ts +1 -1
  197. package/dist/src/agent/workflow/builtins/offer-design.js +10 -1
  198. package/dist/src/agent/workflow/builtins/offer-design.js.map +1 -1
  199. package/dist/src/agent/workflow/builtins/pr-review.d.ts +1 -1
  200. package/dist/src/agent/workflow/builtins/pr-review.js +9 -1
  201. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -1
  202. package/dist/src/agent/workflow/builtins/release-check.d.ts +1 -1
  203. package/dist/src/agent/workflow/builtins/release-check.js +10 -1
  204. package/dist/src/agent/workflow/builtins/release-check.js.map +1 -1
  205. package/dist/src/agent/workflow/builtins/research.d.ts +1 -1
  206. package/dist/src/agent/workflow/builtins/research.js +16 -2
  207. package/dist/src/agent/workflow/builtins/research.js.map +1 -1
  208. package/dist/src/agent/workflow/builtins/weekly-review.d.ts +1 -1
  209. package/dist/src/agent/workflow/builtins/weekly-review.js +9 -1
  210. package/dist/src/agent/workflow/builtins/weekly-review.js.map +1 -1
  211. package/dist/src/agent/workflow/catalog.js +1 -1
  212. package/dist/src/ai-assist/text-assist.js +1 -1
  213. package/dist/src/auth/credentials.js +2 -2
  214. package/dist/src/auth/profiles/store.js +2 -2
  215. package/dist/src/auth/sync-provider-auth.js +1 -1
  216. package/dist/src/browser/cache-dir-policy.js +1 -1
  217. package/dist/src/browser/cdp-local-launcher.js +2 -2
  218. package/dist/src/browser/pipeline/source.js +1 -1
  219. package/dist/src/browser/providers/browser-ext-install.js +4 -4
  220. package/dist/src/browser/providers/cloakbrowser.js +5 -5
  221. package/dist/src/browser/providers/playwright-doctor.js +1 -1
  222. package/dist/src/browser/stealth.js +1 -1
  223. package/dist/src/channels/catalog/channel-catalog-service.js +1 -1
  224. package/dist/src/channels/outbound/persist-store.js +1 -1
  225. package/dist/src/channels/pairing/allow-from-file.js +1 -1
  226. package/dist/src/channels/pairing/pairing-store.js +2 -2
  227. package/dist/src/chat-commands/agent-edit.js +2 -2
  228. package/dist/src/chat-commands/builtins/config.js +2 -2
  229. package/dist/src/cli/commands/config.js +1 -1
  230. package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
  231. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  232. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  233. package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
  234. package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
  235. package/dist/src/cli/commands/extension-create.js +1 -1
  236. package/dist/src/cli/commands/extension-dev.js +2 -2
  237. package/dist/src/cli/commands/extension-marketplace.js +2 -2
  238. package/dist/src/cli/commands/extension-pack.js +2 -2
  239. package/dist/src/cli/commands/extension.js +3 -3
  240. package/dist/src/cli/commands/gateway/logs.js +1 -1
  241. package/dist/src/cli/commands/image.js +1 -1
  242. package/dist/src/cli/commands/init.js +2 -2
  243. package/dist/src/cli/commands/models.js +1 -1
  244. package/dist/src/cli/commands/skills-test.js +1 -1
  245. package/dist/src/cli/templates.js +1 -1
  246. package/dist/src/cli/utils/gateway-client.js +1 -1
  247. package/dist/src/cli/utils/init-workspace-core.js +2 -2
  248. package/dist/src/config/agent-profile.js +1 -1
  249. package/dist/src/config/agent-typed-models.js +1 -1
  250. package/dist/src/config/gateway-bind.js +1 -1
  251. package/dist/src/config/index.js +8 -8
  252. package/dist/src/config/loader.js +4 -4
  253. package/dist/src/config/models-json.js +2 -2
  254. package/dist/src/config/paths-state.js +1 -1
  255. package/dist/src/config/paths.js +1 -1
  256. package/dist/src/config/profile.js +1 -1
  257. package/dist/src/config/reload.js +1 -1
  258. package/dist/src/config/schema.d.ts +7 -0
  259. package/dist/src/config/schema.js +5 -3
  260. package/dist/src/config/schema.js.map +1 -1
  261. package/dist/src/config/workspace-path.js +1 -1
  262. package/dist/src/connectors/composio-triggers.d.ts +10 -0
  263. package/dist/src/connectors/composio-triggers.js +32 -0
  264. package/dist/src/connectors/composio-triggers.js.map +1 -0
  265. package/dist/src/connectors/composio.d.ts +54 -0
  266. package/dist/src/connectors/composio.js +334 -0
  267. package/dist/src/connectors/composio.js.map +1 -0
  268. package/dist/src/connectors/health.js +16 -1
  269. package/dist/src/connectors/health.js.map +1 -1
  270. package/dist/src/connectors/install.d.ts +2 -1
  271. package/dist/src/connectors/install.js +50 -13
  272. package/dist/src/connectors/install.js.map +1 -1
  273. package/dist/src/connectors/instances.js +57 -6
  274. package/dist/src/connectors/instances.js.map +1 -1
  275. package/dist/src/connectors/lifecycle.d.ts +3 -0
  276. package/dist/src/connectors/lifecycle.js +23 -0
  277. package/dist/src/connectors/lifecycle.js.map +1 -0
  278. package/dist/src/connectors/materialize.js +2 -1
  279. package/dist/src/connectors/materialize.js.map +1 -1
  280. package/dist/src/connectors/providers.d.ts +1 -0
  281. package/dist/src/connectors/providers.js +10 -2
  282. package/dist/src/connectors/providers.js.map +1 -1
  283. package/dist/src/connectors/registries/modelscope.d.ts +2 -0
  284. package/dist/src/connectors/registries/modelscope.js +205 -0
  285. package/dist/src/connectors/registries/modelscope.js.map +1 -0
  286. package/dist/src/connectors/registries/official-mcp.d.ts +2 -0
  287. package/dist/src/connectors/registries/official-mcp.js +142 -0
  288. package/dist/src/connectors/registries/official-mcp.js.map +1 -0
  289. package/dist/src/connectors/registries/search.d.ts +12 -0
  290. package/dist/src/connectors/registries/search.js +89 -0
  291. package/dist/src/connectors/registries/search.js.map +1 -0
  292. package/dist/src/connectors/registries/smithery.d.ts +2 -0
  293. package/dist/src/connectors/registries/smithery.js +98 -0
  294. package/dist/src/connectors/registries/smithery.js.map +1 -0
  295. package/dist/src/connectors/registries/types.d.ts +20 -0
  296. package/dist/src/connectors/registries/types.js +1 -0
  297. package/dist/src/connectors/secret-store.js +4 -2
  298. package/dist/src/connectors/secret-store.js.map +1 -1
  299. package/dist/src/connectors/setup-secrets.d.ts +15 -0
  300. package/dist/src/connectors/setup-secrets.js +64 -0
  301. package/dist/src/connectors/setup-secrets.js.map +1 -0
  302. package/dist/src/connectors/supervisor.d.ts +15 -0
  303. package/dist/src/connectors/supervisor.js +73 -0
  304. package/dist/src/connectors/supervisor.js.map +1 -0
  305. package/dist/src/connectors/types.d.ts +27 -3
  306. package/dist/src/connectors/usage.js +6 -1
  307. package/dist/src/connectors/usage.js.map +1 -1
  308. package/dist/src/cron/executor.js +1 -1
  309. package/dist/src/cron/service.js +1 -1
  310. package/dist/src/daemon/constants.js +1 -1
  311. package/dist/src/daemon/install-plan.js +2 -2
  312. package/dist/src/daemon/launchd.js +2 -2
  313. package/dist/src/daemon/schtasks.js +2 -2
  314. package/dist/src/daemon/systemd.js +2 -2
  315. package/dist/src/extensions/bundle-mcp.js +1 -1
  316. package/dist/src/extensions/discover-extensions.js +2 -2
  317. package/dist/src/extensions/health.js +1 -1
  318. package/dist/src/extensions/install.js +2 -2
  319. package/dist/src/extensions/loader.js +2 -2
  320. package/dist/src/extensions/lockfile.js +3 -3
  321. package/dist/src/extensions/security.js +1 -1
  322. package/dist/src/extensions/update.js +2 -2
  323. package/dist/src/gateway/agents-admin.js +2 -2
  324. package/dist/src/gateway/file-path-classifier.js +2 -2
  325. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  326. package/dist/src/gateway/hono/lib/extension-store.js +2 -2
  327. package/dist/src/gateway/hono/lib/static-ui.js +2 -2
  328. package/dist/src/gateway/hono/routes/agents.js +1 -1
  329. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  330. package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
  331. package/dist/src/gateway/hono/routes/connectors.js +230 -3
  332. package/dist/src/gateway/hono/routes/connectors.js.map +1 -1
  333. package/dist/src/gateway/hono/routes/dreaming.js +1 -1
  334. package/dist/src/gateway/hono/routes/goals.js +52 -5
  335. package/dist/src/gateway/hono/routes/goals.js.map +1 -1
  336. package/dist/src/gateway/hono/routes/host-fs.js +2 -2
  337. package/dist/src/gateway/hono/routes/models.js +1 -1
  338. package/dist/src/gateway/hono/routes/shares.js +1 -1
  339. package/dist/src/gateway/hono/routes/voice.js +14 -1
  340. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  341. package/dist/src/gateway/hono/routes/workflows.js +1 -1
  342. package/dist/src/gateway/hono/routes/workspace.js +2 -2
  343. package/dist/src/gateway/hono/sse.js.map +1 -1
  344. package/dist/src/gateway/lock.js +3 -3
  345. package/dist/src/gateway/ports.js +1 -1
  346. package/dist/src/gateway/service/agent-runner.d.ts +5 -12
  347. package/dist/src/gateway/service/agent-runner.js +8 -8
  348. package/dist/src/gateway/service/agent-runner.js.map +1 -1
  349. package/dist/src/gateway/service/config-coordinator.js +1 -1
  350. package/dist/src/gateway/service/marketplace-service.js +2 -2
  351. package/dist/src/gateway/service/run-gateway-agent.d.ts +2 -10
  352. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  353. package/dist/src/gateway/service.d.ts +1 -1
  354. package/dist/src/gateway/service.js +13 -8
  355. package/dist/src/gateway/service.js.map +1 -1
  356. package/dist/src/gateway/user-turn-input.d.ts +18 -0
  357. package/dist/src/gateway/user-turn-input.js +16 -0
  358. package/dist/src/gateway/user-turn-input.js.map +1 -0
  359. package/dist/src/gateway/workspace-fs-file-list.js +1 -1
  360. package/dist/src/goals/goal-queue-store.d.ts +2 -1
  361. package/dist/src/goals/goal-queue-store.js +2 -2
  362. package/dist/src/goals/goal-queue-store.js.map +1 -1
  363. package/dist/src/goals/goal-queue-types.d.ts +4 -3
  364. package/dist/src/goals/goal-runner.js +25 -3
  365. package/dist/src/goals/goal-runner.js.map +1 -1
  366. package/dist/src/goals/goal-service.d.ts +6 -1
  367. package/dist/src/goals/goal-service.js +9 -2
  368. package/dist/src/goals/goal-service.js.map +1 -1
  369. package/dist/src/goals/goal-store.d.ts +7 -1
  370. package/dist/src/goals/goal-store.js +42 -0
  371. package/dist/src/goals/goal-store.js.map +1 -1
  372. package/dist/src/goals/goal-workflow-judge.js +2 -2
  373. package/dist/src/goals/types.d.ts +10 -0
  374. package/dist/src/i18n/goals-bundle.js +1 -1
  375. package/dist/src/i18n/index.js +1 -1
  376. package/dist/src/i18n/share-tool-bundle.js +2 -2
  377. package/dist/src/infra/brew.js +1 -1
  378. package/dist/src/infra/package-json.js +1 -1
  379. package/dist/src/infra/package-update-steps.js +1 -1
  380. package/dist/src/infra/path-env.js +2 -2
  381. package/dist/src/infra/restart.js +1 -1
  382. package/dist/src/infra/stable-node-path.js +1 -1
  383. package/dist/src/infra/update-check.js +1 -1
  384. package/dist/src/infra/update-global.js +1 -1
  385. package/dist/src/infra/update-lock.js +2 -2
  386. package/dist/src/infra/update-restart.js +1 -1
  387. package/dist/src/infra/update-runner.js +1 -1
  388. package/dist/src/infra/update-startup.js +1 -1
  389. package/dist/src/infra/write-file-atomic.js +2 -2
  390. package/dist/src/media/store.js +1 -1
  391. package/dist/src/notes/agent-context.js +1 -1
  392. package/dist/src/notes/service.js +1 -1
  393. package/dist/src/notes/store.js +2 -2
  394. package/dist/src/project-trust/trust-store.js +2 -2
  395. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  396. package/dist/src/providers/index.js +1 -1
  397. package/dist/src/providers/model-registry.js +4 -4
  398. package/dist/src/session/init-session-turn.js +1 -1
  399. package/dist/src/session/resolve-session.js +1 -1
  400. package/dist/src/session/session-title.js +2 -2
  401. package/dist/src/session/store.js +1 -1
  402. package/dist/src/share/share-auto.js +2 -2
  403. package/dist/src/share/share-store.js +3 -3
  404. package/dist/src/share/share-thumbnail.js +2 -2
  405. package/dist/src/share/share-zip.js +1 -1
  406. package/dist/src/share/site-share-store.js +3 -3
  407. package/dist/src/share/site-static-serve.js +1 -1
  408. package/dist/src/storage/sqlite/connection.js +1 -1
  409. package/dist/src/storage/sqlite/memory-index-repository.js +2 -2
  410. package/dist/src/storage/sqlite/migrations/011_goal_context_messages.sql +8 -0
  411. package/dist/src/storage/sqlite/migrations/discover.js +1 -1
  412. package/dist/src/storage/sqlite/migrations/runner.d.ts +2 -2
  413. package/dist/src/storage/sqlite/migrations/runner.js +5 -5
  414. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  415. package/dist/src/storage/sqlite/migrations/runner.ts +2 -2
  416. package/dist/src/storage/sqlite/schema.js +4 -4
  417. package/dist/src/storage/sqlite/schema.sql +9 -0
  418. package/dist/src/tui/backends/embedded-backend.js +1 -1
  419. package/dist/src/tui/clipboard-image.js +3 -3
  420. package/dist/src/tui/extension-host/load-extensions.js +1 -1
  421. package/dist/src/tui/theme-manager.js +1 -1
  422. package/dist/src/tui/tui-command-formatters.js +4 -4
  423. package/dist/src/tui/tui-commands.js +2 -2
  424. package/dist/src/tui/tui-keybindings-file.js +1 -1
  425. package/dist/src/tui/tui-scoped-models.js +2 -2
  426. package/dist/src/tui/tui-settings.js +1 -1
  427. package/dist/src/tui/tui-startup-resources.js +1 -1
  428. package/dist/src/tui/tui.js +6 -6
  429. package/dist/src/tunnel/frpc-binary.js +4 -4
  430. package/dist/src/tunnel/frpc-config.js +1 -1
  431. package/dist/src/tunnel/frpc-extract.js +1 -1
  432. package/dist/src/tunnel/tunnel-state.js +1 -1
  433. package/dist/src/utils/logger/audit.js +1 -1
  434. package/dist/src/utils/logger/log-store.js +1 -1
  435. package/dist/src/utils/logger/rotation.js +1 -1
  436. package/dist/src/utils/logger/streams.js +1 -1
  437. package/dist/src/voice/tts/audio.js +1 -1
  438. package/dist/src/voice/tts/openai-compatible-speech.d.ts +1 -1
  439. package/dist/src/voice/tts/openai-compatible-speech.js.map +1 -1
  440. package/dist/src/voice/tts/providers/edge-speech.js +15 -3
  441. package/dist/src/voice/tts/providers/edge-speech.js.map +1 -1
  442. package/dist/src/voice/tts/speak-core.js +3 -3
  443. package/dist/src/voice/tts/speak-core.js.map +1 -1
  444. package/dist/src/voice/tts/speech-provider-types.d.ts +2 -2
  445. package/dist/src/voice/tts/types.js +1 -1
  446. package/dist/src/voice/tts/types.js.map +1 -1
  447. package/dist/src/workflows/domain/result.d.ts +0 -1
  448. package/dist/src/workflows/draft/workflow-draft-service.js +1 -1
  449. package/dist/src/workflows/engine/workflow-engine.js +7 -23
  450. package/dist/src/workflows/engine/workflow-engine.js.map +1 -1
  451. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  452. package/dist/src/workflows/store/event-store.js +1 -1
  453. package/dist/src/workflows/store/run-store.js +1 -1
  454. package/package.json +2 -2
  455. package/dist/gateway/static/root/assets/agent-browser-settings-page-DPXc4hz8.js +0 -6
  456. package/dist/gateway/static/root/assets/agent-defaults-tabbed-page-CTJ0YnRQ.js +0 -1
  457. package/dist/gateway/static/root/assets/agents-zJXQfJ_W.js +0 -71
  458. package/dist/gateway/static/root/assets/app-management-settings-panel-DzGbdr04.js +0 -1
  459. package/dist/gateway/static/root/assets/apps-page-CPjfyzSD.js +0 -1
  460. package/dist/gateway/static/root/assets/connectors-settings-CAXYMOdp.js +0 -2
  461. package/dist/gateway/static/root/assets/cron-page-DuUbBeKI.js +0 -3
  462. package/dist/gateway/static/root/assets/dreaming-settings-DQ4DtiK1.js +0 -1
  463. package/dist/gateway/static/root/assets/gateway-settings-BMPwIjKI.js +0 -1
  464. package/dist/gateway/static/root/assets/goal-detail-page-BJsyjJnF.js +0 -1
  465. package/dist/gateway/static/root/assets/goals-page-BerDgIhw.js +0 -2
  466. package/dist/gateway/static/root/assets/goals-settings-ByTjeAto.js +0 -1
  467. package/dist/gateway/static/root/assets/heartbeat-settings-TbHgcjTn.js +0 -1
  468. package/dist/gateway/static/root/assets/index-AkTrf6Pb.js +0 -122
  469. package/dist/gateway/static/root/assets/index-fh5sXTj9.css +0 -1
  470. package/dist/gateway/static/root/assets/logs-page-DGXuPf5B.js +0 -2
  471. package/dist/gateway/static/root/assets/messages-upgxw6Ub.js +0 -3
  472. package/dist/gateway/static/root/assets/models-hub-panel-Bot16nn7.js +0 -2
  473. package/dist/gateway/static/root/assets/note-detail-page-Brlyou8M.js +0 -2
  474. package/dist/gateway/static/root/assets/notes-page-DvZ4pwc-.js +0 -1
  475. package/dist/gateway/static/root/assets/remote-access-hub-BZEDmxbZ.js +0 -1
  476. package/dist/gateway/static/root/assets/sessions-page-DdGGlsfn.js +0 -1
  477. package/dist/gateway/static/root/assets/settings-advanced-gate-DlkEkPf9.js +0 -1
  478. package/dist/gateway/static/root/assets/settings-page-DFPTrsPF.js +0 -1
  479. package/dist/gateway/static/root/assets/setup-status-panel-CYCCfgh4.js +0 -1
  480. package/dist/gateway/static/root/assets/shares-settings-Bff1pGd0.js +0 -1
  481. package/dist/gateway/static/root/assets/skill-reload-api-CJGKpBur.js +0 -1
  482. package/dist/gateway/static/root/assets/skills-page-C6ocduTD.js +0 -2
  483. package/dist/gateway/static/root/assets/system-settings-panel-5jrIEr5b.js +0 -1
  484. package/dist/gateway/static/root/assets/use-agent-defaults-form-BuiXLIs5.js +0 -1
  485. package/dist/gateway/static/root/assets/voice-api-key-field-DDoQyghO.js +0 -1
  486. package/dist/gateway/static/root/assets/workflow-page.utils-CKjeEGqx.js +0 -1
  487. package/dist/gateway/static/root/assets/workflows-page-B604ehHG.js +0 -18
@@ -1,7 +1,7 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
- import { dirname, join } from "node:path";
4
3
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
5
  import { spawn } from "node:child_process";
6
6
  import AdmZip from "adm-zip";
7
7
  import { gunzipSync } from "node:zlib";
@@ -1,7 +1,7 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
- import { join } from "node:path";
4
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
5
  //#region src/tunnel/tunnel-state.ts
6
6
  init_paths();
7
7
  const TUNNEL_STATE_FILE = "tunnel.json";
@@ -1,7 +1,7 @@
1
1
  import { __esmMin } from "../../../_virtual/_rolldown/runtime.js";
2
2
  import { getLogDir, init_config } from "./config.js";
3
- import path from "path";
4
3
  import { existsSync, mkdirSync } from "fs";
4
+ import path from "path";
5
5
  import { appendFile } from "fs/promises";
6
6
  //#region src/utils/logger/audit.ts
7
7
  /**
@@ -1,6 +1,6 @@
1
1
  import { init_pino_record, logEntrySearchText, pinoRecordToLogEntry } from "./pino-record.js";
2
- import { basename, join } from "path";
3
2
  import { createReadStream, existsSync, mkdirSync, readdirSync, statSync } from "fs";
3
+ import { basename, join } from "path";
4
4
  import { Readable } from "stream";
5
5
  import { readFile } from "fs/promises";
6
6
  import { gunzip } from "zlib";
@@ -1,7 +1,7 @@
1
1
  import { __esmMin } from "../../../_virtual/_rolldown/runtime.js";
2
2
  import { config, getLogDir, init_config } from "./config.js";
3
- import { join } from "path";
4
3
  import { readFileSync, readdirSync, statSync, unlinkSync } from "fs";
4
+ import { join } from "path";
5
5
  import { writeFile } from "fs/promises";
6
6
  import { gzip } from "zlib";
7
7
  import { promisify } from "util";
@@ -2,8 +2,8 @@ import { __esmMin } from "../../../_virtual/_rolldown/runtime.js";
2
2
  import { config, getLogDir, init_config } from "./config.js";
3
3
  import { emitLogEntry, hasSubscribers, init_log_stream } from "./log-stream.js";
4
4
  import { init_pino_record, pinoRecordToLogEntry } from "./pino-record.js";
5
- import path from "path";
6
5
  import { createWriteStream, existsSync, mkdirSync } from "fs";
6
+ import path from "path";
7
7
  import { Writable } from "stream";
8
8
  //#region src/utils/logger/streams.ts
9
9
  /**
@@ -1,8 +1,8 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
+ import { spawn } from "child_process";
3
4
  import { join } from "path";
4
5
  import { unlink, writeFile } from "fs/promises";
5
- import { spawn } from "child_process";
6
6
  import { tmpdir } from "os";
7
7
  //#region src/voice/tts/audio.ts
8
8
  /**
@@ -42,7 +42,7 @@ export interface OpenAiCompatibleSpeechProviderExtraJsonBodyField {
42
42
  export interface OpenAiCompatibleSpeechProviderOptions {
43
43
  /** Canonical provider id (e.g. "openai", "minimax"). */
44
44
  id: string;
45
- /** Optional aliases for back-compat config strings. */
45
+ /** Optional aliases for provider lookup. */
46
46
  aliases?: readonly string[];
47
47
  /** Human-readable label for error messages and logs (e.g. "OpenAI", "MiniMax"). */
48
48
  label: string;
@@ -1 +1 @@
1
- {"version":3,"file":"openai-compatible-speech.js","names":["normalizeBaseUrlGeneric"],"sources":["../../../../src/voice/tts/openai-compatible-speech.ts"],"sourcesContent":["/**\n * Factory: createOpenAiCompatibleSpeechProvider\n *\n * Most TTS providers (OpenAI, MiniMax, ElevenLabs, OpenRouter, ...) speak the\n * OpenAI `/audio/speech` POST shape. This factory eliminates the boilerplate so\n * a new compatible vendor is ~30 lines of declaration instead of a full provider\n * implementation.\n *\n * Ported from openclaw/src/tts/openai-compatible-speech-provider.ts (commit\n * baseline 2026-05-08), with the following INTENTIONAL OMISSIONS per\n * docs/voice-rearchitecture.md §15.3:\n * - resolveTalkConfig / resolveTalkOverrides (talk-mode is excluded)\n * - Persona resolution context (persona system is excluded)\n * - Custom dispatcherPolicy (xopc uses Node 22 native fetch — see ssrf-guard.ts)\n *\n * DECISION: We use `Record<string, unknown>` for the extra-config slot rather\n * than openclaw's generic `<ExtraConfig extends Record<string, unknown>>`. The\n * generic adds significant TypeScript complexity but no runtime safety; we\n * declare per-provider config types in each provider file's own normalizer.\n *\n * DECISION: `voiceCompatibleResponseFormats` defaults to `['opus']` if omitted,\n * because Telegram voice notes require opus/ogg. Providers that natively support\n * opus output (OpenAI, ElevenLabs) can rely on this; others (MiniMax mp3-only)\n * pass an empty array and downstream ffmpeg-compresses to opus.\n */\n\nimport {\n ProviderHttpError,\n fetchWithTimeoutGuarded,\n normalizeBaseUrl as normalizeBaseUrlGeneric,\n postJsonRequest,\n} from '../../media-shared/http/index.js';\nimport { createLogger } from '../../utils/logger.js';\n\nimport type {\n SpeechDirectiveTokenParseContext,\n SpeechDirectiveTokenParseResult,\n SpeechProviderConfig,\n SpeechProviderConfiguredContext,\n SpeechProviderOverrides,\n SpeechProviderPlugin,\n SpeechProviderResolveConfigContext,\n SpeechSynthesisRequest,\n SpeechSynthesisResult,\n SpeechVoiceOption,\n} from './speech-provider-types.js';\n\nconst log = createLogger('SpeechProvider:OpenAiCompat');\n\ninterface OpenAiCompatibleBaseConfig {\n apiKey?: string;\n baseUrl?: string;\n model: string;\n voice: string;\n speed?: number;\n responseFormat?: string;\n}\n\nexport type OpenAiCompatibleSpeechProviderConfig = OpenAiCompatibleBaseConfig &\n Record<string, unknown>;\n\nexport interface OpenAiCompatibleSpeechProviderExtraJsonBodyField {\n /** Key to read from normalized config. */\n configKey: string;\n /** Key to send in the HTTP body. Defaults to `configKey`. */\n requestKey?: string;\n}\n\nexport interface OpenAiCompatibleSpeechProviderOptions {\n /** Canonical provider id (e.g. \"openai\", \"minimax\"). */\n id: string;\n /** Optional aliases for back-compat config strings. */\n aliases?: readonly string[];\n /** Human-readable label for error messages and logs (e.g. \"OpenAI\", \"MiniMax\"). */\n label: string;\n /** Sort key for UI auto-fallback ordering. Lower = higher priority. */\n autoSelectOrder: number;\n /** Allowed model ids. Used by listVoices and validation. */\n models: readonly string[];\n /** Allowed voice ids. */\n voices: readonly string[];\n /** Default model when config does not specify. */\n defaultModel: string;\n /** Default voice. */\n defaultVoice: string;\n /** Default base URL (e.g. \"https://api.openai.com/v1\"). */\n defaultBaseUrl: string;\n /** Env var name for the api key (e.g. \"OPENAI_API_KEY\"). */\n envKey: string;\n /** Allowed response formats. */\n responseFormats: readonly string[];\n /** Default response format (e.g. \"opus\" for OpenAI, \"mp3\" for MiniMax). */\n defaultResponseFormat: string;\n /** Response formats that produce a voice-note-compatible buffer (no ffmpeg needed). */\n voiceCompatibleResponseFormats?: readonly string[];\n /** Extra static headers (e.g. `{ \"OpenAI-Beta\": \"...\" }`). Auth header is added automatically. */\n extraHeaders?: Record<string, string>;\n /** Read provider-specific extra config keys (e.g. MiniMax's `groupId`). */\n readExtraConfig?: (raw: Record<string, unknown> | undefined) => Record<string, unknown>;\n /** Provider-specific extra body fields to copy from config into request JSON. */\n extraJsonBodyFields?: readonly OpenAiCompatibleSpeechProviderExtraJsonBodyField[];\n /** Override the API error label (defaults to `${label} TTS API error`). */\n apiErrorLabel?: string;\n /** Override the missing-key error message. */\n missingApiKeyError?: string;\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction trimToUndefined(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction asFiniteNumber(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string') {\n const parsed = Number.parseFloat(value);\n if (Number.isFinite(parsed)) {\n return parsed;\n }\n }\n return undefined;\n}\n\nfunction compactKey(value: string): string {\n return value.replace(/[^a-z0-9]+/giu, '').toLowerCase();\n}\n\nfunction normalizeResponseFormat(label: string, formats: readonly string[], value: unknown): string | undefined {\n const next = trimToUndefined(value)?.toLowerCase();\n if (!next) {\n return undefined;\n }\n if (formats.includes(next)) {\n return next;\n }\n throw new Error(`Invalid ${label} speech responseFormat: ${next}`);\n}\n\nfunction buildExtraJsonBodyFields(\n config: OpenAiCompatibleSpeechProviderConfig,\n fields: readonly OpenAiCompatibleSpeechProviderExtraJsonBodyField[] | undefined,\n): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n for (const field of fields ?? []) {\n const value = config[field.configKey];\n if (value !== undefined && value !== null && value !== '') {\n body[field.requestKey ?? field.configKey] = value;\n }\n }\n return body;\n}\n\nfunction readSpeechOverrides(overrides: SpeechProviderOverrides | undefined): {\n model?: string;\n voice?: string;\n speed?: number;\n} {\n if (!overrides) {\n return {};\n }\n return {\n model: trimToUndefined(overrides.model ?? overrides.modelId),\n voice: trimToUndefined(overrides.voice ?? overrides.voiceId),\n speed: asFiniteNumber(overrides.speed),\n };\n}\n\nfunction parseDirectiveTokenInternal(\n ctx: SpeechDirectiveTokenParseContext,\n providerConfigKey: string,\n): SpeechDirectiveTokenParseResult {\n const compactProviderKey = compactKey(providerConfigKey);\n switch (ctx.key) {\n case 'voice':\n case 'voice_id':\n case 'voiceid':\n case `${providerConfigKey}_voice`:\n case `${compactProviderKey}voice`:\n if (!ctx.policy.allowVoice) {\n return { handled: true };\n }\n return { handled: true, overrides: { voice: ctx.value } };\n case 'model':\n case 'model_id':\n case 'modelid':\n case `${providerConfigKey}_model`:\n case `${compactProviderKey}model`:\n if (!ctx.policy.allowModelId) {\n return { handled: true };\n }\n return { handled: true, overrides: { model: ctx.value } };\n case 'speed': {\n const speed = asFiniteNumber(ctx.value);\n if (speed === undefined) {\n return { handled: true, warnings: [`Invalid speed value: ${ctx.value}`] };\n }\n return { handled: true, overrides: { speed } };\n }\n default:\n return { handled: false };\n }\n}\n\nexport function createOpenAiCompatibleSpeechProvider(\n options: OpenAiCompatibleSpeechProviderOptions,\n): SpeechProviderPlugin {\n const providerConfigKey = options.id;\n const voiceCompatibleResponseFormats = options.voiceCompatibleResponseFormats ?? ['opus'];\n\n function readExtraConfig(raw: Record<string, unknown> | undefined): Record<string, unknown> {\n return options.readExtraConfig?.(raw) ?? {};\n }\n\n function normalizeConfig(rawConfig: Record<string, unknown>): OpenAiCompatibleSpeechProviderConfig {\n const raw = asObject(rawConfig[providerConfigKey]) ?? rawConfig;\n return {\n apiKey: trimToUndefined(raw.apiKey),\n baseUrl: trimToUndefined(raw.baseUrl)\n ? normalizeBaseUrlGeneric(String(raw.baseUrl))\n : undefined,\n model: trimToUndefined(raw.model ?? raw.modelId) ?? options.defaultModel,\n voice: trimToUndefined(raw.voice ?? raw.voiceId) ?? options.defaultVoice,\n speed: asFiniteNumber(raw.speed),\n responseFormat: normalizeResponseFormat(options.label, options.responseFormats, raw.responseFormat),\n ...readExtraConfig(raw),\n };\n }\n\n function readProviderConfig(config: SpeechProviderConfig): OpenAiCompatibleSpeechProviderConfig {\n return {\n apiKey: trimToUndefined(config.apiKey),\n baseUrl: trimToUndefined(config.baseUrl)\n ? normalizeBaseUrlGeneric(String(config.baseUrl))\n : undefined,\n model: trimToUndefined(config.model ?? config.modelId) ?? options.defaultModel,\n voice: trimToUndefined(config.voice ?? config.voiceId) ?? options.defaultVoice,\n speed: asFiniteNumber(config.speed),\n responseFormat:\n normalizeResponseFormat(options.label, options.responseFormats, config.responseFormat) ??\n options.defaultResponseFormat,\n ...readExtraConfig(config),\n };\n }\n\n function resolveApiKey(providerConfig: OpenAiCompatibleSpeechProviderConfig): string | undefined {\n return providerConfig.apiKey ?? trimToUndefined(process.env[options.envKey]);\n }\n\n function resolveBaseUrl(providerConfig: OpenAiCompatibleSpeechProviderConfig): string {\n return providerConfig.baseUrl ?? options.defaultBaseUrl.replace(/\\/+$/u, '');\n }\n\n return {\n id: options.id,\n aliases: options.aliases,\n autoSelectOrder: options.autoSelectOrder,\n\n resolveConfig: (ctx: SpeechProviderResolveConfigContext) => normalizeConfig(ctx.rawConfig),\n\n isConfigured: (ctx: SpeechProviderConfiguredContext) =>\n Boolean(resolveApiKey(readProviderConfig(ctx.providerConfig))),\n\n parseDirectiveToken: (ctx) => parseDirectiveTokenInternal(ctx, providerConfigKey),\n\n listVoices: async (): Promise<SpeechVoiceOption[]> =>\n options.voices.map((voice) => ({ id: voice, name: voice })),\n\n synthesize: async (req: SpeechSynthesisRequest): Promise<SpeechSynthesisResult> => {\n const config = readProviderConfig(req.providerConfig);\n const overrides = readSpeechOverrides(req.providerOverrides);\n const apiKey = resolveApiKey(config);\n if (!apiKey) {\n throw new Error(options.missingApiKeyError ?? `${options.label} API key missing`);\n }\n const baseUrl = resolveBaseUrl(config);\n const responseFormat = config.responseFormat ?? options.defaultResponseFormat;\n const speed = overrides.speed ?? config.speed;\n const model = overrides.model ?? config.model;\n const voice = overrides.voice ?? config.voice;\n\n const url = `${baseUrl}/audio/speech`;\n log.debug(\n { provider: options.id, model, voice, responseFormat, baseUrl, target: req.target },\n `Calling ${options.label} TTS`,\n );\n\n // postJsonRequest already throws ProviderHttpError on non-2xx.\n const response = await postJsonRequest(url, {\n timeoutMs: req.timeoutMs,\n label: options.apiErrorLabel ?? `${options.label} TTS API error`,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n ...options.extraHeaders,\n },\n body: {\n model,\n input: req.text,\n voice,\n response_format: responseFormat,\n ...(speed === undefined ? {} : { speed }),\n ...buildExtraJsonBodyFields(config, options.extraJsonBodyFields),\n },\n });\n\n const audioBuffer = Buffer.from(await response.arrayBuffer());\n return {\n audioBuffer,\n outputFormat: responseFormat,\n fileExtension: responseFormat,\n voiceCompatible: voiceCompatibleResponseFormats.includes(responseFormat),\n };\n },\n\n /**\n * Streaming via OpenAI's POST /audio/speech (the response body itself is\n * the audio stream — no SSE / WebSocket needed). We do NOT call assertOk\n * via postJsonRequest because we want to surface the body as-is for\n * streaming consumers; instead we manually check status and convert to\n * ProviderHttpError on failure (matches openclaw behavior).\n */\n synthesizeStream: async (req) => {\n const config = readProviderConfig(req.providerConfig);\n const overrides = readSpeechOverrides(req.providerOverrides);\n const apiKey = resolveApiKey(config);\n if (!apiKey) {\n throw new Error(options.missingApiKeyError ?? `${options.label} API key missing`);\n }\n const baseUrl = resolveBaseUrl(config);\n const responseFormat = config.responseFormat ?? options.defaultResponseFormat;\n const speed = overrides.speed ?? config.speed;\n const model = overrides.model ?? config.model;\n const voice = overrides.voice ?? config.voice;\n\n const response = await fetchWithTimeoutGuarded(`${baseUrl}/audio/speech`, {\n timeoutMs: req.timeoutMs,\n label: options.apiErrorLabel ?? `${options.label} TTS streaming`,\n init: {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n accept: 'application/octet-stream',\n ...options.extraHeaders,\n },\n body: JSON.stringify({\n model,\n input: req.text,\n voice,\n response_format: responseFormat,\n stream: true,\n ...(speed === undefined ? {} : { speed }),\n ...buildExtraJsonBodyFields(config, options.extraJsonBodyFields),\n }),\n },\n });\n\n if (!response.ok) {\n const detail = await response.text().catch(() => undefined);\n throw new ProviderHttpError({\n label: options.apiErrorLabel ?? `${options.label} TTS streaming`,\n status: response.status,\n detail: detail?.slice(0, 220),\n });\n }\n if (!response.body) {\n throw new Error(`${options.label} TTS streaming: empty response body`);\n }\n\n return {\n audioStream: response.body,\n outputFormat: responseFormat,\n fileExtension: responseFormat,\n voiceCompatible: voiceCompatibleResponseFormats.includes(responseFormat),\n };\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"openai-compatible-speech.js","names":["normalizeBaseUrlGeneric"],"sources":["../../../../src/voice/tts/openai-compatible-speech.ts"],"sourcesContent":["/**\n * Factory: createOpenAiCompatibleSpeechProvider\n *\n * Most TTS providers (OpenAI, MiniMax, ElevenLabs, OpenRouter, ...) speak the\n * OpenAI `/audio/speech` POST shape. This factory eliminates the boilerplate so\n * a new compatible vendor is ~30 lines of declaration instead of a full provider\n * implementation.\n *\n * Ported from openclaw/src/tts/openai-compatible-speech-provider.ts (commit\n * baseline 2026-05-08), with the following INTENTIONAL OMISSIONS per\n * docs/voice-rearchitecture.md §15.3:\n * - resolveTalkConfig / resolveTalkOverrides (talk-mode is excluded)\n * - Persona resolution context (persona system is excluded)\n * - Custom dispatcherPolicy (xopc uses Node 22 native fetch — see ssrf-guard.ts)\n *\n * DECISION: We use `Record<string, unknown>` for the extra-config slot rather\n * than openclaw's generic `<ExtraConfig extends Record<string, unknown>>`. The\n * generic adds significant TypeScript complexity but no runtime safety; we\n * declare per-provider config types in each provider file's own normalizer.\n *\n * DECISION: `voiceCompatibleResponseFormats` defaults to `['opus']` if omitted,\n * because Telegram voice notes require opus/ogg. Providers that natively support\n * opus output (OpenAI, ElevenLabs) can rely on this; others (MiniMax mp3-only)\n * pass an empty array and downstream ffmpeg-compresses to opus.\n */\n\nimport {\n ProviderHttpError,\n fetchWithTimeoutGuarded,\n normalizeBaseUrl as normalizeBaseUrlGeneric,\n postJsonRequest,\n} from '../../media-shared/http/index.js';\nimport { createLogger } from '../../utils/logger.js';\n\nimport type {\n SpeechDirectiveTokenParseContext,\n SpeechDirectiveTokenParseResult,\n SpeechProviderConfig,\n SpeechProviderConfiguredContext,\n SpeechProviderOverrides,\n SpeechProviderPlugin,\n SpeechProviderResolveConfigContext,\n SpeechSynthesisRequest,\n SpeechSynthesisResult,\n SpeechVoiceOption,\n} from './speech-provider-types.js';\n\nconst log = createLogger('SpeechProvider:OpenAiCompat');\n\ninterface OpenAiCompatibleBaseConfig {\n apiKey?: string;\n baseUrl?: string;\n model: string;\n voice: string;\n speed?: number;\n responseFormat?: string;\n}\n\nexport type OpenAiCompatibleSpeechProviderConfig = OpenAiCompatibleBaseConfig &\n Record<string, unknown>;\n\nexport interface OpenAiCompatibleSpeechProviderExtraJsonBodyField {\n /** Key to read from normalized config. */\n configKey: string;\n /** Key to send in the HTTP body. Defaults to `configKey`. */\n requestKey?: string;\n}\n\nexport interface OpenAiCompatibleSpeechProviderOptions {\n /** Canonical provider id (e.g. \"openai\", \"minimax\"). */\n id: string;\n /** Optional aliases for provider lookup. */\n aliases?: readonly string[];\n /** Human-readable label for error messages and logs (e.g. \"OpenAI\", \"MiniMax\"). */\n label: string;\n /** Sort key for UI auto-fallback ordering. Lower = higher priority. */\n autoSelectOrder: number;\n /** Allowed model ids. Used by listVoices and validation. */\n models: readonly string[];\n /** Allowed voice ids. */\n voices: readonly string[];\n /** Default model when config does not specify. */\n defaultModel: string;\n /** Default voice. */\n defaultVoice: string;\n /** Default base URL (e.g. \"https://api.openai.com/v1\"). */\n defaultBaseUrl: string;\n /** Env var name for the api key (e.g. \"OPENAI_API_KEY\"). */\n envKey: string;\n /** Allowed response formats. */\n responseFormats: readonly string[];\n /** Default response format (e.g. \"opus\" for OpenAI, \"mp3\" for MiniMax). */\n defaultResponseFormat: string;\n /** Response formats that produce a voice-note-compatible buffer (no ffmpeg needed). */\n voiceCompatibleResponseFormats?: readonly string[];\n /** Extra static headers (e.g. `{ \"OpenAI-Beta\": \"...\" }`). Auth header is added automatically. */\n extraHeaders?: Record<string, string>;\n /** Read provider-specific extra config keys (e.g. MiniMax's `groupId`). */\n readExtraConfig?: (raw: Record<string, unknown> | undefined) => Record<string, unknown>;\n /** Provider-specific extra body fields to copy from config into request JSON. */\n extraJsonBodyFields?: readonly OpenAiCompatibleSpeechProviderExtraJsonBodyField[];\n /** Override the API error label (defaults to `${label} TTS API error`). */\n apiErrorLabel?: string;\n /** Override the missing-key error message. */\n missingApiKeyError?: string;\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction trimToUndefined(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction asFiniteNumber(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string') {\n const parsed = Number.parseFloat(value);\n if (Number.isFinite(parsed)) {\n return parsed;\n }\n }\n return undefined;\n}\n\nfunction compactKey(value: string): string {\n return value.replace(/[^a-z0-9]+/giu, '').toLowerCase();\n}\n\nfunction normalizeResponseFormat(label: string, formats: readonly string[], value: unknown): string | undefined {\n const next = trimToUndefined(value)?.toLowerCase();\n if (!next) {\n return undefined;\n }\n if (formats.includes(next)) {\n return next;\n }\n throw new Error(`Invalid ${label} speech responseFormat: ${next}`);\n}\n\nfunction buildExtraJsonBodyFields(\n config: OpenAiCompatibleSpeechProviderConfig,\n fields: readonly OpenAiCompatibleSpeechProviderExtraJsonBodyField[] | undefined,\n): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n for (const field of fields ?? []) {\n const value = config[field.configKey];\n if (value !== undefined && value !== null && value !== '') {\n body[field.requestKey ?? field.configKey] = value;\n }\n }\n return body;\n}\n\nfunction readSpeechOverrides(overrides: SpeechProviderOverrides | undefined): {\n model?: string;\n voice?: string;\n speed?: number;\n} {\n if (!overrides) {\n return {};\n }\n return {\n model: trimToUndefined(overrides.model ?? overrides.modelId),\n voice: trimToUndefined(overrides.voice ?? overrides.voiceId),\n speed: asFiniteNumber(overrides.speed),\n };\n}\n\nfunction parseDirectiveTokenInternal(\n ctx: SpeechDirectiveTokenParseContext,\n providerConfigKey: string,\n): SpeechDirectiveTokenParseResult {\n const compactProviderKey = compactKey(providerConfigKey);\n switch (ctx.key) {\n case 'voice':\n case 'voice_id':\n case 'voiceid':\n case `${providerConfigKey}_voice`:\n case `${compactProviderKey}voice`:\n if (!ctx.policy.allowVoice) {\n return { handled: true };\n }\n return { handled: true, overrides: { voice: ctx.value } };\n case 'model':\n case 'model_id':\n case 'modelid':\n case `${providerConfigKey}_model`:\n case `${compactProviderKey}model`:\n if (!ctx.policy.allowModelId) {\n return { handled: true };\n }\n return { handled: true, overrides: { model: ctx.value } };\n case 'speed': {\n const speed = asFiniteNumber(ctx.value);\n if (speed === undefined) {\n return { handled: true, warnings: [`Invalid speed value: ${ctx.value}`] };\n }\n return { handled: true, overrides: { speed } };\n }\n default:\n return { handled: false };\n }\n}\n\nexport function createOpenAiCompatibleSpeechProvider(\n options: OpenAiCompatibleSpeechProviderOptions,\n): SpeechProviderPlugin {\n const providerConfigKey = options.id;\n const voiceCompatibleResponseFormats = options.voiceCompatibleResponseFormats ?? ['opus'];\n\n function readExtraConfig(raw: Record<string, unknown> | undefined): Record<string, unknown> {\n return options.readExtraConfig?.(raw) ?? {};\n }\n\n function normalizeConfig(rawConfig: Record<string, unknown>): OpenAiCompatibleSpeechProviderConfig {\n const raw = asObject(rawConfig[providerConfigKey]) ?? rawConfig;\n return {\n apiKey: trimToUndefined(raw.apiKey),\n baseUrl: trimToUndefined(raw.baseUrl)\n ? normalizeBaseUrlGeneric(String(raw.baseUrl))\n : undefined,\n model: trimToUndefined(raw.model ?? raw.modelId) ?? options.defaultModel,\n voice: trimToUndefined(raw.voice ?? raw.voiceId) ?? options.defaultVoice,\n speed: asFiniteNumber(raw.speed),\n responseFormat: normalizeResponseFormat(options.label, options.responseFormats, raw.responseFormat),\n ...readExtraConfig(raw),\n };\n }\n\n function readProviderConfig(config: SpeechProviderConfig): OpenAiCompatibleSpeechProviderConfig {\n return {\n apiKey: trimToUndefined(config.apiKey),\n baseUrl: trimToUndefined(config.baseUrl)\n ? normalizeBaseUrlGeneric(String(config.baseUrl))\n : undefined,\n model: trimToUndefined(config.model ?? config.modelId) ?? options.defaultModel,\n voice: trimToUndefined(config.voice ?? config.voiceId) ?? options.defaultVoice,\n speed: asFiniteNumber(config.speed),\n responseFormat:\n normalizeResponseFormat(options.label, options.responseFormats, config.responseFormat) ??\n options.defaultResponseFormat,\n ...readExtraConfig(config),\n };\n }\n\n function resolveApiKey(providerConfig: OpenAiCompatibleSpeechProviderConfig): string | undefined {\n return providerConfig.apiKey ?? trimToUndefined(process.env[options.envKey]);\n }\n\n function resolveBaseUrl(providerConfig: OpenAiCompatibleSpeechProviderConfig): string {\n return providerConfig.baseUrl ?? options.defaultBaseUrl.replace(/\\/+$/u, '');\n }\n\n return {\n id: options.id,\n aliases: options.aliases,\n autoSelectOrder: options.autoSelectOrder,\n\n resolveConfig: (ctx: SpeechProviderResolveConfigContext) => normalizeConfig(ctx.rawConfig),\n\n isConfigured: (ctx: SpeechProviderConfiguredContext) =>\n Boolean(resolveApiKey(readProviderConfig(ctx.providerConfig))),\n\n parseDirectiveToken: (ctx) => parseDirectiveTokenInternal(ctx, providerConfigKey),\n\n listVoices: async (): Promise<SpeechVoiceOption[]> =>\n options.voices.map((voice) => ({ id: voice, name: voice })),\n\n synthesize: async (req: SpeechSynthesisRequest): Promise<SpeechSynthesisResult> => {\n const config = readProviderConfig(req.providerConfig);\n const overrides = readSpeechOverrides(req.providerOverrides);\n const apiKey = resolveApiKey(config);\n if (!apiKey) {\n throw new Error(options.missingApiKeyError ?? `${options.label} API key missing`);\n }\n const baseUrl = resolveBaseUrl(config);\n const responseFormat = config.responseFormat ?? options.defaultResponseFormat;\n const speed = overrides.speed ?? config.speed;\n const model = overrides.model ?? config.model;\n const voice = overrides.voice ?? config.voice;\n\n const url = `${baseUrl}/audio/speech`;\n log.debug(\n { provider: options.id, model, voice, responseFormat, baseUrl, target: req.target },\n `Calling ${options.label} TTS`,\n );\n\n // postJsonRequest already throws ProviderHttpError on non-2xx.\n const response = await postJsonRequest(url, {\n timeoutMs: req.timeoutMs,\n label: options.apiErrorLabel ?? `${options.label} TTS API error`,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n ...options.extraHeaders,\n },\n body: {\n model,\n input: req.text,\n voice,\n response_format: responseFormat,\n ...(speed === undefined ? {} : { speed }),\n ...buildExtraJsonBodyFields(config, options.extraJsonBodyFields),\n },\n });\n\n const audioBuffer = Buffer.from(await response.arrayBuffer());\n return {\n audioBuffer,\n outputFormat: responseFormat,\n fileExtension: responseFormat,\n voiceCompatible: voiceCompatibleResponseFormats.includes(responseFormat),\n };\n },\n\n /**\n * Streaming via OpenAI's POST /audio/speech (the response body itself is\n * the audio stream — no SSE / WebSocket needed). We do NOT call assertOk\n * via postJsonRequest because we want to surface the body as-is for\n * streaming consumers; instead we manually check status and convert to\n * ProviderHttpError on failure (matches openclaw behavior).\n */\n synthesizeStream: async (req) => {\n const config = readProviderConfig(req.providerConfig);\n const overrides = readSpeechOverrides(req.providerOverrides);\n const apiKey = resolveApiKey(config);\n if (!apiKey) {\n throw new Error(options.missingApiKeyError ?? `${options.label} API key missing`);\n }\n const baseUrl = resolveBaseUrl(config);\n const responseFormat = config.responseFormat ?? options.defaultResponseFormat;\n const speed = overrides.speed ?? config.speed;\n const model = overrides.model ?? config.model;\n const voice = overrides.voice ?? config.voice;\n\n const response = await fetchWithTimeoutGuarded(`${baseUrl}/audio/speech`, {\n timeoutMs: req.timeoutMs,\n label: options.apiErrorLabel ?? `${options.label} TTS streaming`,\n init: {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n accept: 'application/octet-stream',\n ...options.extraHeaders,\n },\n body: JSON.stringify({\n model,\n input: req.text,\n voice,\n response_format: responseFormat,\n stream: true,\n ...(speed === undefined ? {} : { speed }),\n ...buildExtraJsonBodyFields(config, options.extraJsonBodyFields),\n }),\n },\n });\n\n if (!response.ok) {\n const detail = await response.text().catch(() => undefined);\n throw new ProviderHttpError({\n label: options.apiErrorLabel ?? `${options.label} TTS streaming`,\n status: response.status,\n detail: detail?.slice(0, 220),\n });\n }\n if (!response.body) {\n throw new Error(`${options.label} TTS streaming: empty response body`);\n }\n\n return {\n audioStream: response.body,\n outputFormat: responseFormat,\n fileExtension: responseFormat,\n voiceCompatible: voiceCompatibleResponseFormats.includes(responseFormat),\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import { createLogger } from "../../../utils/logger/index.js";
2
2
  import { init_logger } from "../../../utils/logger.js";
3
3
  import { registerSpeechProvider } from "../speech-registry.js";
4
- import { tmpdir } from "node:os";
4
+ import { mkdtempSync, readFileSync, rmSync, statSync } from "node:fs";
5
5
  import path from "node:path";
6
- import { mkdtempSync, readFileSync, rmSync } from "node:fs";
6
+ import { tmpdir } from "node:os";
7
7
  //#region src/voice/tts/providers/edge-speech.ts
8
8
  /**
9
9
  * Edge TTS provider — wraps the `node-edge-tts` package. Requires no API key
@@ -94,6 +94,15 @@ function parseDirectiveTokenInternal(ctx) {
94
94
  default: return { handled: false };
95
95
  }
96
96
  }
97
+ async function waitForNonEmptyFile(filePath, timeoutMs) {
98
+ const deadline = Date.now() + Math.min(Math.max(timeoutMs, 1e3), 5e3);
99
+ while (Date.now() < deadline) {
100
+ try {
101
+ if (statSync(filePath).size > 0) return;
102
+ } catch {}
103
+ await new Promise((resolve) => setTimeout(resolve, 50));
104
+ }
105
+ }
97
106
  async function synthesizeToBuffer(text, config, voiceOverride, timeoutMs) {
98
107
  const { EdgeTTS } = await import("node-edge-tts");
99
108
  const voice = voiceOverride ?? config.voice;
@@ -116,8 +125,11 @@ async function synthesizeToBuffer(text, config, voiceOverride, timeoutMs) {
116
125
  volume: config.volume,
117
126
  timeout: timeoutMs
118
127
  }).ttsPromise(text, outputPath);
128
+ await waitForNonEmptyFile(outputPath, timeoutMs);
129
+ const buffer = readFileSync(outputPath);
130
+ if (buffer.length === 0) throw new Error(`Edge TTS produced an empty ${format} file for voice ${voice}`);
119
131
  return {
120
- buffer: readFileSync(outputPath),
132
+ buffer,
121
133
  outputFormat: format,
122
134
  ext
123
135
  };
@@ -1 +1 @@
1
- {"version":3,"file":"edge-speech.js","names":[],"sources":["../../../../../src/voice/tts/providers/edge-speech.ts"],"sourcesContent":["/**\n * Edge TTS provider — wraps the `node-edge-tts` package. Requires no API key\n * (Microsoft's Edge browser endpoint is unauthenticated).\n *\n * Implementation notes:\n * - Temp-file dance (mkdtemp → ttsPromise → readFileSync → rm) is required\n * because `node-edge-tts` does not expose a stream-to-buffer API.\n * - `synthesizeStream` is implemented as \"synthesize + wrap as single-chunk\n * stream\" so callers see uniform behavior. This is not a true network\n * stream; it's buffered-then-wrapped.\n * - `inferEdgeExtension` derives the file extension from Edge's outputFormat\n * string.\n */\n\nimport { mkdtempSync, readFileSync, rmSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\n\nimport { createLogger } from '../../../utils/logger.js';\nimport { registerSpeechProvider } from '../speech-registry.js';\nimport type {\n SpeechDirectiveTokenParseContext,\n SpeechDirectiveTokenParseResult,\n SpeechProviderConfig,\n SpeechProviderConfiguredContext,\n SpeechProviderPlugin,\n SpeechProviderResolveConfigContext,\n SpeechSynthesisRequest,\n SpeechSynthesisResult,\n SpeechSynthesisStreamRequest,\n SpeechSynthesisStreamResult,\n} from '../speech-provider-types.js';\n\nconst log = createLogger('SpeechProvider:Edge');\n\nconst DEFAULT_VOICE = 'en-US-MichelleNeural';\nconst DEFAULT_LANG = 'en-US';\nconst DEFAULT_OUTPUT_FORMAT = 'audio-24khz-48kbitrate-mono-mp3';\n\ninterface EdgeTtsConfig extends Record<string, unknown> {\n voice: string;\n lang: string;\n outputFormat: string;\n pitch?: string;\n rate?: string;\n volume?: string;\n proxy?: string;\n enabled: boolean;\n}\n\nfunction trimToUndefined(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction inferEdgeExtension(outputFormat: string): { ext: string; format: string } {\n const normalized = outputFormat.toLowerCase();\n if (normalized.includes('webm')) return { ext: 'webm', format: 'webm' };\n if (normalized.includes('opus')) return { ext: 'opus', format: 'opus' };\n if (normalized.includes('ogg')) return { ext: 'ogg', format: 'ogg' };\n if (normalized.includes('wav') || normalized.includes('riff') || normalized.includes('pcm')) {\n return { ext: 'wav', format: 'wav' };\n }\n return { ext: 'mp3', format: 'mp3' };\n}\n\nfunction normalizeConfig(rawConfig: Record<string, unknown>): EdgeTtsConfig {\n const raw = asObject(rawConfig.edge) ?? rawConfig;\n return {\n voice: trimToUndefined(raw.voice ?? raw.voiceId) ?? DEFAULT_VOICE,\n lang: trimToUndefined(raw.lang) ?? DEFAULT_LANG,\n outputFormat: trimToUndefined(raw.outputFormat) ?? DEFAULT_OUTPUT_FORMAT,\n pitch: trimToUndefined(raw.pitch),\n rate: trimToUndefined(raw.rate),\n volume: trimToUndefined(raw.volume),\n proxy: trimToUndefined(raw.proxy),\n enabled: raw.enabled !== false, // default-on\n };\n}\n\nfunction readProviderConfig(config: SpeechProviderConfig): EdgeTtsConfig {\n return {\n voice: trimToUndefined(config.voice ?? config.voiceId) ?? DEFAULT_VOICE,\n lang: trimToUndefined(config.lang) ?? DEFAULT_LANG,\n outputFormat: trimToUndefined(config.outputFormat) ?? DEFAULT_OUTPUT_FORMAT,\n pitch: trimToUndefined(config.pitch),\n rate: trimToUndefined(config.rate),\n volume: trimToUndefined(config.volume),\n proxy: trimToUndefined(config.proxy),\n enabled: config.enabled !== false,\n };\n}\n\nfunction parseDirectiveTokenInternal(\n ctx: SpeechDirectiveTokenParseContext,\n): SpeechDirectiveTokenParseResult {\n switch (ctx.key) {\n case 'voice':\n case 'voice_id':\n case 'voiceid':\n case 'edge_voice':\n case 'edgevoice':\n if (!ctx.policy.allowVoice) {\n return { handled: true };\n }\n return { handled: true, overrides: { voice: ctx.value } };\n default:\n return { handled: false };\n }\n}\n\nasync function synthesizeToBuffer(\n text: string,\n config: EdgeTtsConfig,\n voiceOverride: string | undefined,\n timeoutMs: number,\n): Promise<{ buffer: Buffer; outputFormat: string; ext: string }> {\n // Lazy import — node-edge-tts loads ws transitively; skip cost for users that\n // never enable the edge provider.\n const { EdgeTTS } = await import('node-edge-tts');\n\n const voice = voiceOverride ?? config.voice;\n const tempDir = mkdtempSync(path.join(tmpdir(), 'tts-edge-'));\n const { ext, format } = inferEdgeExtension(config.outputFormat);\n const outputPath = path.join(tempDir, `speech-${Date.now()}.${ext}`);\n\n try {\n log.debug(\n { voice, outputFormat: config.outputFormat, textLength: text.length },\n 'Calling Edge TTS',\n );\n const tts = new EdgeTTS({\n voice,\n lang: config.lang,\n outputFormat: config.outputFormat,\n proxy: config.proxy,\n rate: config.rate,\n pitch: config.pitch,\n volume: config.volume,\n timeout: timeoutMs,\n });\n await tts.ttsPromise(text, outputPath);\n const buffer = readFileSync(outputPath);\n return { buffer, outputFormat: format, ext };\n } finally {\n try {\n rmSync(tempDir, { recursive: true, force: true });\n } catch (cleanupError) {\n log.warn({ err: cleanupError, tempDir }, 'Failed to cleanup Edge TTS temp directory');\n }\n }\n}\n\nexport const edgeSpeechProvider: SpeechProviderPlugin = {\n id: 'edge',\n autoSelectOrder: 100,\n\n resolveConfig: (ctx: SpeechProviderResolveConfigContext) => normalizeConfig(ctx.rawConfig),\n\n isConfigured: (ctx: SpeechProviderConfiguredContext) => readProviderConfig(ctx.providerConfig).enabled,\n\n parseDirectiveToken: parseDirectiveTokenInternal,\n\n // Edge has hundreds of voices; we don't enumerate them statically. UI should\n // call upstream Microsoft endpoint for the full catalog.\n listVoices: async () => [{ id: DEFAULT_VOICE, name: DEFAULT_VOICE, locale: DEFAULT_LANG }],\n\n synthesize: async (req: SpeechSynthesisRequest): Promise<SpeechSynthesisResult> => {\n const config = readProviderConfig(req.providerConfig);\n if (!config.enabled) {\n throw new Error('Edge TTS is disabled in config (messages.tts.providers.edge.enabled = false)');\n }\n const overrides = req.providerOverrides ?? {};\n const voiceOverride = trimToUndefined(overrides.voice ?? overrides.voiceId);\n const { buffer, outputFormat, ext } = await synthesizeToBuffer(\n req.text,\n config,\n voiceOverride,\n req.timeoutMs,\n );\n return {\n audioBuffer: buffer,\n outputFormat,\n fileExtension: ext,\n voiceCompatible: outputFormat === 'opus' || outputFormat === 'ogg',\n };\n },\n\n // True streaming would require a node-edge-tts upstream change. We expose a\n // \"fake stream\" (single chunk) so callers don't fall through to speak-core's\n // wrapBufferAsStream branch and can treat all providers uniformly.\n synthesizeStream: async (req: SpeechSynthesisStreamRequest): Promise<SpeechSynthesisStreamResult> => {\n const config = readProviderConfig(req.providerConfig);\n if (!config.enabled) {\n throw new Error('Edge TTS is disabled in config (messages.tts.providers.edge.enabled = false)');\n }\n const overrides = req.providerOverrides ?? {};\n const voiceOverride = trimToUndefined(overrides.voice ?? overrides.voiceId);\n const { buffer, outputFormat, ext } = await synthesizeToBuffer(\n req.text,\n config,\n voiceOverride,\n req.timeoutMs,\n );\n const audioStream = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(new Uint8Array(buffer));\n controller.close();\n },\n });\n return {\n audioStream,\n outputFormat,\n fileExtension: ext,\n voiceCompatible: outputFormat === 'opus' || outputFormat === 'ogg',\n };\n },\n};\n\nregisterSpeechProvider(edgeSpeechProvider);\n"],"mappings":"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"}
1
+ {"version":3,"file":"edge-speech.js","names":[],"sources":["../../../../../src/voice/tts/providers/edge-speech.ts"],"sourcesContent":["/**\n * Edge TTS provider — wraps the `node-edge-tts` package. Requires no API key\n * (Microsoft's Edge browser endpoint is unauthenticated).\n *\n * Implementation notes:\n * - Temp-file dance (mkdtemp → ttsPromise → readFileSync → rm) is required\n * because `node-edge-tts` does not expose a stream-to-buffer API.\n * - `synthesizeStream` is implemented as \"synthesize + wrap as single-chunk\n * stream\" so callers see uniform behavior. This is not a true network\n * stream; it's buffered-then-wrapped.\n * - `inferEdgeExtension` derives the file extension from Edge's outputFormat\n * string.\n */\n\nimport { mkdtempSync, readFileSync, rmSync, statSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\n\nimport { createLogger } from '../../../utils/logger.js';\nimport { registerSpeechProvider } from '../speech-registry.js';\nimport type {\n SpeechDirectiveTokenParseContext,\n SpeechDirectiveTokenParseResult,\n SpeechProviderConfig,\n SpeechProviderConfiguredContext,\n SpeechProviderPlugin,\n SpeechProviderResolveConfigContext,\n SpeechSynthesisRequest,\n SpeechSynthesisResult,\n SpeechSynthesisStreamRequest,\n SpeechSynthesisStreamResult,\n} from '../speech-provider-types.js';\n\nconst log = createLogger('SpeechProvider:Edge');\n\nconst DEFAULT_VOICE = 'en-US-MichelleNeural';\nconst DEFAULT_LANG = 'en-US';\nconst DEFAULT_OUTPUT_FORMAT = 'audio-24khz-48kbitrate-mono-mp3';\n\ninterface EdgeTtsConfig extends Record<string, unknown> {\n voice: string;\n lang: string;\n outputFormat: string;\n pitch?: string;\n rate?: string;\n volume?: string;\n proxy?: string;\n enabled: boolean;\n}\n\nfunction trimToUndefined(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction inferEdgeExtension(outputFormat: string): { ext: string; format: string } {\n const normalized = outputFormat.toLowerCase();\n if (normalized.includes('webm')) return { ext: 'webm', format: 'webm' };\n if (normalized.includes('opus')) return { ext: 'opus', format: 'opus' };\n if (normalized.includes('ogg')) return { ext: 'ogg', format: 'ogg' };\n if (normalized.includes('wav') || normalized.includes('riff') || normalized.includes('pcm')) {\n return { ext: 'wav', format: 'wav' };\n }\n return { ext: 'mp3', format: 'mp3' };\n}\n\nfunction normalizeConfig(rawConfig: Record<string, unknown>): EdgeTtsConfig {\n const raw = asObject(rawConfig.edge) ?? rawConfig;\n return {\n voice: trimToUndefined(raw.voice ?? raw.voiceId) ?? DEFAULT_VOICE,\n lang: trimToUndefined(raw.lang) ?? DEFAULT_LANG,\n outputFormat: trimToUndefined(raw.outputFormat) ?? DEFAULT_OUTPUT_FORMAT,\n pitch: trimToUndefined(raw.pitch),\n rate: trimToUndefined(raw.rate),\n volume: trimToUndefined(raw.volume),\n proxy: trimToUndefined(raw.proxy),\n enabled: raw.enabled !== false, // default-on\n };\n}\n\nfunction readProviderConfig(config: SpeechProviderConfig): EdgeTtsConfig {\n return {\n voice: trimToUndefined(config.voice ?? config.voiceId) ?? DEFAULT_VOICE,\n lang: trimToUndefined(config.lang) ?? DEFAULT_LANG,\n outputFormat: trimToUndefined(config.outputFormat) ?? DEFAULT_OUTPUT_FORMAT,\n pitch: trimToUndefined(config.pitch),\n rate: trimToUndefined(config.rate),\n volume: trimToUndefined(config.volume),\n proxy: trimToUndefined(config.proxy),\n enabled: config.enabled !== false,\n };\n}\n\nfunction parseDirectiveTokenInternal(\n ctx: SpeechDirectiveTokenParseContext,\n): SpeechDirectiveTokenParseResult {\n switch (ctx.key) {\n case 'voice':\n case 'voice_id':\n case 'voiceid':\n case 'edge_voice':\n case 'edgevoice':\n if (!ctx.policy.allowVoice) {\n return { handled: true };\n }\n return { handled: true, overrides: { voice: ctx.value } };\n default:\n return { handled: false };\n }\n}\n\nasync function waitForNonEmptyFile(filePath: string, timeoutMs: number): Promise<void> {\n const deadline = Date.now() + Math.min(Math.max(timeoutMs, 1000), 5000);\n while (Date.now() < deadline) {\n try {\n if (statSync(filePath).size > 0) return;\n } catch {\n // The Edge package may resolve before the file is visible on slower filesystems.\n }\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n}\n\nasync function synthesizeToBuffer(\n text: string,\n config: EdgeTtsConfig,\n voiceOverride: string | undefined,\n timeoutMs: number,\n): Promise<{ buffer: Buffer; outputFormat: string; ext: string }> {\n // Lazy import — node-edge-tts loads ws transitively; skip cost for users that\n // never enable the edge provider.\n const { EdgeTTS } = await import('node-edge-tts');\n\n const voice = voiceOverride ?? config.voice;\n const tempDir = mkdtempSync(path.join(tmpdir(), 'tts-edge-'));\n const { ext, format } = inferEdgeExtension(config.outputFormat);\n const outputPath = path.join(tempDir, `speech-${Date.now()}.${ext}`);\n\n try {\n log.debug(\n { voice, outputFormat: config.outputFormat, textLength: text.length },\n 'Calling Edge TTS',\n );\n const tts = new EdgeTTS({\n voice,\n lang: config.lang,\n outputFormat: config.outputFormat,\n proxy: config.proxy,\n rate: config.rate,\n pitch: config.pitch,\n volume: config.volume,\n timeout: timeoutMs,\n });\n await tts.ttsPromise(text, outputPath);\n await waitForNonEmptyFile(outputPath, timeoutMs);\n const buffer = readFileSync(outputPath);\n if (buffer.length === 0) {\n throw new Error(`Edge TTS produced an empty ${format} file for voice ${voice}`);\n }\n return { buffer, outputFormat: format, ext };\n } finally {\n try {\n rmSync(tempDir, { recursive: true, force: true });\n } catch (cleanupError) {\n log.warn({ err: cleanupError, tempDir }, 'Failed to cleanup Edge TTS temp directory');\n }\n }\n}\n\nexport const edgeSpeechProvider: SpeechProviderPlugin = {\n id: 'edge',\n autoSelectOrder: 100,\n\n resolveConfig: (ctx: SpeechProviderResolveConfigContext) => normalizeConfig(ctx.rawConfig),\n\n isConfigured: (ctx: SpeechProviderConfiguredContext) => readProviderConfig(ctx.providerConfig).enabled,\n\n parseDirectiveToken: parseDirectiveTokenInternal,\n\n // Edge has hundreds of voices; we don't enumerate them statically. UI should\n // call upstream Microsoft endpoint for the full catalog.\n listVoices: async () => [{ id: DEFAULT_VOICE, name: DEFAULT_VOICE, locale: DEFAULT_LANG }],\n\n synthesize: async (req: SpeechSynthesisRequest): Promise<SpeechSynthesisResult> => {\n const config = readProviderConfig(req.providerConfig);\n if (!config.enabled) {\n throw new Error('Edge TTS is disabled in config (messages.tts.providers.edge.enabled = false)');\n }\n const overrides = req.providerOverrides ?? {};\n const voiceOverride = trimToUndefined(overrides.voice ?? overrides.voiceId);\n const { buffer, outputFormat, ext } = await synthesizeToBuffer(\n req.text,\n config,\n voiceOverride,\n req.timeoutMs,\n );\n return {\n audioBuffer: buffer,\n outputFormat,\n fileExtension: ext,\n voiceCompatible: outputFormat === 'opus' || outputFormat === 'ogg',\n };\n },\n\n // True streaming would require a node-edge-tts upstream change. We expose a\n // \"fake stream\" (single chunk) so callers don't fall through to speak-core's\n // wrapBufferAsStream branch and can treat all providers uniformly.\n synthesizeStream: async (req: SpeechSynthesisStreamRequest): Promise<SpeechSynthesisStreamResult> => {\n const config = readProviderConfig(req.providerConfig);\n if (!config.enabled) {\n throw new Error('Edge TTS is disabled in config (messages.tts.providers.edge.enabled = false)');\n }\n const overrides = req.providerOverrides ?? {};\n const voiceOverride = trimToUndefined(overrides.voice ?? overrides.voiceId);\n const { buffer, outputFormat, ext } = await synthesizeToBuffer(\n req.text,\n config,\n voiceOverride,\n req.timeoutMs,\n );\n const audioStream = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(new Uint8Array(buffer));\n controller.close();\n },\n });\n return {\n audioStream,\n outputFormat,\n fileExtension: ext,\n voiceCompatible: outputFormat === 'opus' || outputFormat === 'ogg',\n };\n },\n};\n\nregisterSpeechProvider(edgeSpeechProvider);\n"],"mappings":"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"}
@@ -100,7 +100,7 @@ function buildSynthesisRequest(resolved, preparedText, ttsOptions, directiveOver
100
100
  };
101
101
  }
102
102
  /** Convert SpeechSynthesisResult → TTSResult shape (audio/format/provider). */
103
- function toLegacyResult(resolved, result, durationSeconds) {
103
+ function toTtsResult(resolved, result, durationSeconds) {
104
104
  return {
105
105
  audio: result.audioBuffer,
106
106
  format: result.outputFormat,
@@ -167,7 +167,7 @@ async function speak(text, config, options) {
167
167
  size: result.audioBuffer.length
168
168
  }, "TTS succeeded");
169
169
  return {
170
- ...toLegacyResult(resolved, result, durationSeconds),
170
+ ...toTtsResult(resolved, result, durationSeconds),
171
171
  attempts,
172
172
  ...fallbackFrom ? { fallbackFrom } : {},
173
173
  attemptedProviders,
@@ -211,7 +211,7 @@ async function speakWithProvider(text, config, providerName, options) {
211
211
  const startTime = Date.now();
212
212
  const request = buildSynthesisRequest(resolved, prepared.text, prepared.ttsOptions, prepared.directiveOverrides, options?.appConfig, "audio-file");
213
213
  return {
214
- ...toLegacyResult(resolved, await resolved.plugin.synthesize(request), (Date.now() - startTime) / 1e3),
214
+ ...toTtsResult(resolved, await resolved.plugin.synthesize(request), (Date.now() - startTime) / 1e3),
215
215
  attempts: [{
216
216
  provider: providerName,
217
217
  outcome: "success",
@@ -1 +1 @@
1
- {"version":3,"file":"speak-core.js","names":[],"sources":["../../../../src/voice/tts/speak-core.ts"],"sourcesContent":["/**\n * speak-core — orchestrates per-call TTS synthesis through the SpeechProviderPlugin chain.\n *\n * Provider invocation goes through `plugin.synthesize` / `plugin.synthesizeStream`.\n * Streaming consumers (Telegram draft etc.) use `speakStream`; providers without\n * `synthesizeStream` automatically fall back to `synthesize` wrapped as a\n * single-chunk ReadableStream (see wrapBufferAsStream).\n *\n * Directive overrides are routed via per-provider buckets (`overrides.openai`,\n * `overrides.minimax`, …). The bucket whose key matches the currently active\n * provider id wins; cross-provider keys are ignored.\n *\n * `cfg: Config` is threaded through `SpeakOptions.appConfig`. The orchestrator\n * does NOT auto-summarize / preprocess for `speakStream` beyond the same path\n * as `speak`; streaming is a transport optimization, not a different semantic.\n */\n\nimport type { Config } from '../../config/schema.js';\nimport { createLogger } from '../../utils/logger.js';\n\nimport { parseTtsDirectives } from './directives.js';\nimport { resolveSpeechProviderChain, resolveSpeechProvider, type ResolvedSpeechProvider } from './factory.js';\nimport { preprocessText, type PreprocessOptions, type PreprocessResult } from './preprocess.js';\nimport type {\n SpeechProviderOverrides,\n SpeechSynthesisRequest,\n SpeechSynthesisResult,\n SpeechSynthesisStreamRequest,\n SpeechSynthesisStreamResult,\n} from './speech-provider-types.js';\nimport { summarizeForTts } from './summarize.js';\nimport type {\n ProviderAttempt,\n ProviderFailureReason,\n TTSConfig,\n TTSModelOverrideConfig,\n TTSOptions,\n TTSProvider,\n TTSResultWithTracking,\n TtsDirectiveOverrides,\n} from './types.js';\n\nconst log = createLogger('TTS');\n\nexport interface SpeakOptions {\n tts?: TTSOptions;\n preprocess?: PreprocessOptions;\n parseDirectives?: boolean;\n modelOverrides?: TTSModelOverrideConfig;\n appConfig?: Config;\n /** Optional caller-supplied AbortSignal (currently passed through to providers via timeoutMs guard only). */\n signal?: AbortSignal;\n}\n\n/** Pulls the override bucket for a given provider id from the directive overrides. */\nfunction pickProviderOverrides(\n overrides: TtsDirectiveOverrides | undefined,\n providerId: TTSProvider,\n): SpeechProviderOverrides | undefined {\n if (!overrides) return undefined;\n const bucket = (overrides as Record<string, unknown>)[providerId];\n if (bucket && typeof bucket === 'object') {\n return bucket as SpeechProviderOverrides;\n }\n return undefined;\n}\n\n/** Merges TTSOptions (caller-supplied) with directive bucket. Directive wins on overlap. */\nfunction mergeOverrides(\n ttsOptions: TTSOptions | undefined,\n directiveBucket: SpeechProviderOverrides | undefined,\n): SpeechProviderOverrides | undefined {\n const merged: Record<string, unknown> = {};\n if (ttsOptions?.voice) merged.voice = ttsOptions.voice;\n if (ttsOptions?.model) merged.model = ttsOptions.model;\n if (ttsOptions?.speed) merged.speed = ttsOptions.speed;\n if (directiveBucket) {\n Object.assign(merged, directiveBucket);\n }\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\nfunction classifyError(error: unknown): ProviderFailureReason {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (message.includes('not configured') || message.includes('api key')) {\n return 'not_configured';\n }\n if (message.includes('timeout') || message.includes('timed out') || message.includes('aborted')) {\n return 'timeout';\n }\n if (message.includes('too long') || message.includes('text length') || message.includes('exceeds')) {\n return 'text_too_long';\n }\n return 'provider_error';\n }\n return 'unknown';\n}\n\n/** Common pre-flight: directive parse → preprocess → optional summarize. */\nasync function prepareTextForSynthesis(\n text: string,\n config: TTSConfig,\n options: SpeakOptions | undefined,\n): Promise<{\n text: string;\n wasPreprocessed: boolean;\n wasSummarized: boolean;\n ttsOptions: TTSOptions | undefined;\n directiveOverrides: TtsDirectiveOverrides | undefined;\n selectedProvider: TTSProvider;\n}> {\n let ttsText = text;\n let ttsOptions = options?.tts;\n let directiveOverrides: TtsDirectiveOverrides | undefined;\n let selectedProvider: TTSProvider = config.provider;\n\n if (options?.parseDirectives !== false && config.modelOverrides?.enabled) {\n const directiveResult = parseTtsDirectives(text, config.modelOverrides);\n ttsText = directiveResult.ttsText || directiveResult.cleanedText;\n directiveOverrides = directiveResult.overrides;\n if (directiveResult.overrides?.provider) {\n selectedProvider = directiveResult.overrides.provider;\n }\n }\n\n const maxLength = config.maxTextLength || 4096;\n const preprocessOptions: PreprocessOptions = {\n maxLength,\n stripMarkdown: true,\n normalizeWhitespace: true,\n ...options?.preprocess,\n };\n\n let preprocessResult: PreprocessResult = preprocessText(ttsText, preprocessOptions);\n const wasPreprocessed =\n preprocessResult.wasTruncated || preprocessResult.originalLength !== preprocessResult.finalLength;\n\n let wasSummarized = false;\n const sumCfg = config.summarization;\n const threshold = sumCfg?.threshold ?? maxLength;\n if (sumCfg?.enabled !== false && preprocessResult.text.length > threshold) {\n const summarizeResult = await summarizeForTts({\n text: preprocessResult.text,\n targetLength: sumCfg?.targetLength ?? maxLength,\n config: options?.appConfig,\n modelRef: sumCfg?.model,\n });\n if (summarizeResult.wasSummarized) {\n preprocessResult = {\n ...preprocessResult,\n text: summarizeResult.summary,\n finalLength: summarizeResult.summaryLength,\n wasTruncated: false,\n };\n wasSummarized = true;\n log.info(\n {\n originalLength: summarizeResult.originalLength,\n summaryLength: summarizeResult.summaryLength,\n },\n 'Text summarized before TTS',\n );\n }\n }\n\n return {\n text: preprocessResult.text,\n wasPreprocessed,\n wasSummarized,\n ttsOptions,\n directiveOverrides,\n selectedProvider,\n };\n}\n\n/** Build the SpeechSynthesisRequest from a resolved provider + prepared text. */\nfunction buildSynthesisRequest(\n resolved: ResolvedSpeechProvider,\n preparedText: string,\n ttsOptions: TTSOptions | undefined,\n directiveOverrides: TtsDirectiveOverrides | undefined,\n appConfig: Config | undefined,\n target: 'audio-file' | 'voice-note',\n): SpeechSynthesisRequest {\n const directiveBucket = pickProviderOverrides(directiveOverrides, resolved.providerId);\n const providerOverrides = mergeOverrides(ttsOptions, directiveBucket);\n const callTimeoutMs = ttsOptions?.timeoutMs ?? resolved.timeoutMs;\n return {\n text: preparedText,\n cfg: appConfig as Config,\n providerConfig: resolved.providerConfig,\n target,\n timeoutMs: callTimeoutMs,\n ...(providerOverrides ? { providerOverrides } : {}),\n };\n}\n\n/** Convert SpeechSynthesisResult → TTSResult shape (audio/format/provider). */\nfunction toLegacyResult(\n resolved: ResolvedSpeechProvider,\n result: SpeechSynthesisResult,\n durationSeconds: number | undefined,\n): { audio: Buffer; format: string; provider: string; duration?: number } {\n return {\n audio: result.audioBuffer,\n format: result.outputFormat,\n provider: resolved.providerId,\n ...(durationSeconds !== undefined ? { duration: durationSeconds } : {}),\n };\n}\n\n// ---- Public API ---------------------------------------------------------\n\n/**\n * Run the TTS chain (primary → fallback) and return the first successful result.\n */\nexport async function speak(\n text: string,\n config: TTSConfig,\n options?: SpeakOptions,\n): Promise<TTSResultWithTracking> {\n if (!config.enabled) {\n throw new Error('TTS is not enabled');\n }\n if (config.trigger === 'tagged' && !/\\[\\[tts/.test(text)) {\n throw new Error('TTS trigger is tagged but no [[tts]] directive found');\n }\n\n const prepared = await prepareTextForSynthesis(text, config, options);\n const chain = resolveSpeechProviderChain(config);\n\n // Reorder so the directive-selected provider (if registered + configured) goes first.\n const orderedChain = (() => {\n const idx = chain.findIndex((c) => c.providerId === prepared.selectedProvider);\n if (idx <= 0) return chain;\n const reordered = [...chain];\n const [picked] = reordered.splice(idx, 1);\n if (picked) {\n reordered.unshift(picked);\n }\n return reordered;\n })();\n\n const attempts: ProviderAttempt[] = [];\n const attemptedProviders: string[] = [];\n let lastError: Error | undefined;\n\n for (const resolved of orderedChain) {\n const startTime = Date.now();\n attemptedProviders.push(resolved.providerId);\n\n if (typeof resolved.plugin.synthesize !== 'function') {\n attempts.push({\n provider: resolved.providerId,\n outcome: 'skipped',\n reasonCode: 'not_configured',\n latencyMs: Date.now() - startTime,\n error: `Provider \"${resolved.providerId}\" does not implement synthesize()`,\n });\n continue;\n }\n\n try {\n log.debug(\n { textLength: prepared.text.length, provider: resolved.providerId },\n 'Converting text to speech',\n );\n const request = buildSynthesisRequest(\n resolved,\n prepared.text,\n prepared.ttsOptions,\n prepared.directiveOverrides,\n options?.appConfig,\n 'audio-file',\n );\n const result = await resolved.plugin.synthesize(request);\n const durationSeconds = (Date.now() - startTime) / 1000;\n attempts.push({\n provider: resolved.providerId,\n outcome: 'success',\n reasonCode: 'success',\n latencyMs: Date.now() - startTime,\n });\n\n const primaryProvider = orderedChain[0]!.providerId;\n const fallbackFrom = resolved.providerId !== primaryProvider ? primaryProvider : undefined;\n if (fallbackFrom) {\n log.info(\n { primaryProvider: fallbackFrom, actualProvider: resolved.providerId, attempts },\n 'TTS used fallback provider',\n );\n } else {\n log.info(\n { provider: resolved.providerId, format: result.outputFormat, size: result.audioBuffer.length },\n 'TTS succeeded',\n );\n }\n\n return {\n ...toLegacyResult(resolved, result, durationSeconds),\n attempts,\n ...(fallbackFrom ? { fallbackFrom } : {}),\n attemptedProviders,\n wasPreprocessed: prepared.wasPreprocessed,\n ttsText: prepared.text,\n wasSummarized: prepared.wasSummarized,\n };\n } catch (error) {\n const latencyMs = Date.now() - startTime;\n const reasonCode = classifyError(error);\n const errorMsg = error instanceof Error ? error.message : String(error);\n attempts.push({\n provider: resolved.providerId,\n outcome: 'failed',\n reasonCode,\n latencyMs,\n error: errorMsg,\n });\n lastError = error instanceof Error ? error : new Error(String(error));\n log.warn(\n { provider: resolved.providerId, errorMessage: errorMsg, reasonCode, latencyMs },\n 'TTS provider failed, trying next',\n );\n }\n }\n\n log.error({ attempts, attemptedProviders }, 'All TTS providers failed');\n throw (\n lastError ?? new Error(`All TTS providers failed: ${attempts.map((a) => a.error).join('; ')}`)\n );\n}\n\n/** Single-provider variant; bypasses the fallback chain. */\nexport async function speakWithProvider(\n text: string,\n config: TTSConfig,\n providerName: TTSProvider,\n options?: SpeakOptions,\n): Promise<TTSResultWithTracking> {\n if (!config.enabled) {\n throw new Error('TTS is not enabled');\n }\n const resolved = resolveSpeechProvider(providerName, config);\n if (!resolved) {\n throw new Error(`Provider '${providerName}' is not available`);\n }\n if (typeof resolved.plugin.synthesize !== 'function') {\n throw new Error(`Provider '${providerName}' does not implement synthesize()`);\n }\n\n const prepared = await prepareTextForSynthesis(text, config, options);\n const startTime = Date.now();\n const request = buildSynthesisRequest(\n resolved,\n prepared.text,\n prepared.ttsOptions,\n prepared.directiveOverrides,\n options?.appConfig,\n 'audio-file',\n );\n const result = await resolved.plugin.synthesize(request);\n const durationSeconds = (Date.now() - startTime) / 1000;\n\n return {\n ...toLegacyResult(resolved, result, durationSeconds),\n attempts: [\n {\n provider: providerName,\n outcome: 'success',\n reasonCode: 'success',\n latencyMs: Date.now() - startTime,\n },\n ],\n attemptedProviders: [providerName],\n wasPreprocessed: prepared.wasPreprocessed,\n ttsText: prepared.text,\n wasSummarized: prepared.wasSummarized,\n };\n}\n\n// ---- Streaming surface --------------------------------------------------\n\nexport interface SpeakStreamResult {\n /** Audio chunks. Caller is responsible for consuming the stream. */\n audioStream: ReadableStream<Uint8Array>;\n outputFormat: string;\n fileExtension: string;\n voiceCompatible: boolean;\n /** Provider id that produced this stream. */\n provider: TTSProvider;\n /** Cleanup hook (close sockets etc.). Always defined; no-op when nothing to release. */\n release: () => Promise<void>;\n /** Pre-flight metadata (after directive parse / preprocess / summarize). */\n ttsText: string;\n wasPreprocessed: boolean;\n wasSummarized: boolean;\n}\n\n/**\n * Streaming TTS — tries the chain and returns the first provider's stream.\n * Providers without `synthesizeStream` are wrapped via wrapBufferAsStream.\n */\nexport async function speakStream(\n text: string,\n config: TTSConfig,\n options?: SpeakOptions,\n): Promise<SpeakStreamResult> {\n if (!config.enabled) {\n throw new Error('TTS is not enabled');\n }\n if (config.trigger === 'tagged' && !/\\[\\[tts/.test(text)) {\n throw new Error('TTS trigger is tagged but no [[tts]] directive found');\n }\n\n const prepared = await prepareTextForSynthesis(text, config, options);\n const chain = resolveSpeechProviderChain(config);\n\n let lastError: Error | undefined;\n for (const resolved of chain) {\n try {\n const baseRequest: SpeechSynthesisStreamRequest = buildSynthesisRequest(\n resolved,\n prepared.text,\n prepared.ttsOptions,\n prepared.directiveOverrides,\n options?.appConfig,\n 'voice-note',\n );\n\n let streamResult: SpeechSynthesisStreamResult;\n if (typeof resolved.plugin.synthesizeStream === 'function') {\n streamResult = await resolved.plugin.synthesizeStream(baseRequest);\n } else if (typeof resolved.plugin.synthesize === 'function') {\n // Fallback: buffer-then-wrap. Documented behavior: callers see a\n // single-chunk stream with the same outputFormat.\n const bufferResult = await resolved.plugin.synthesize(baseRequest);\n streamResult = wrapBufferAsStream(bufferResult);\n } else {\n throw new Error(\n `Provider \"${resolved.providerId}\" implements neither synthesize nor synthesizeStream`,\n );\n }\n\n log.info(\n {\n provider: resolved.providerId,\n outputFormat: streamResult.outputFormat,\n voiceCompatible: streamResult.voiceCompatible,\n streamed: typeof resolved.plugin.synthesizeStream === 'function',\n },\n 'TTS stream ready',\n );\n\n const release = streamResult.release ?? (async () => {});\n return {\n audioStream: streamResult.audioStream,\n outputFormat: streamResult.outputFormat,\n fileExtension: streamResult.fileExtension,\n voiceCompatible: streamResult.voiceCompatible,\n provider: resolved.providerId,\n release,\n ttsText: prepared.text,\n wasPreprocessed: prepared.wasPreprocessed,\n wasSummarized: prepared.wasSummarized,\n };\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n log.warn(\n { err: lastError, provider: resolved.providerId },\n `TTS stream provider \"${resolved.providerId}\" failed, trying next`,\n );\n }\n }\n throw lastError ?? new Error('All TTS providers failed (streaming)');\n}\n\n/** Wrap a one-shot synthesis result into a single-chunk stream (no-op release). */\nfunction wrapBufferAsStream(result: SpeechSynthesisResult): SpeechSynthesisStreamResult {\n const { audioBuffer } = result;\n const audioStream = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(new Uint8Array(audioBuffer));\n controller.close();\n },\n });\n return {\n audioStream,\n outputFormat: result.outputFormat,\n fileExtension: result.fileExtension,\n voiceCompatible: result.voiceCompatible,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"speak-core.js","names":[],"sources":["../../../../src/voice/tts/speak-core.ts"],"sourcesContent":["/**\n * speak-core — orchestrates per-call TTS synthesis through the SpeechProviderPlugin chain.\n *\n * Provider invocation goes through `plugin.synthesize` / `plugin.synthesizeStream`.\n * Streaming consumers (Telegram draft etc.) use `speakStream`; providers without\n * `synthesizeStream` automatically fall back to `synthesize` wrapped as a\n * single-chunk ReadableStream (see wrapBufferAsStream).\n *\n * Directive overrides are routed via per-provider buckets (`overrides.openai`,\n * `overrides.minimax`, …). The bucket whose key matches the currently active\n * provider id wins; cross-provider keys are ignored.\n *\n * `cfg: Config` is threaded through `SpeakOptions.appConfig`. The orchestrator\n * does NOT auto-summarize / preprocess for `speakStream` beyond the same path\n * as `speak`; streaming is a transport optimization, not a different semantic.\n */\n\nimport type { Config } from '../../config/schema.js';\nimport { createLogger } from '../../utils/logger.js';\n\nimport { parseTtsDirectives } from './directives.js';\nimport { resolveSpeechProviderChain, resolveSpeechProvider, type ResolvedSpeechProvider } from './factory.js';\nimport { preprocessText, type PreprocessOptions, type PreprocessResult } from './preprocess.js';\nimport type {\n SpeechProviderOverrides,\n SpeechSynthesisRequest,\n SpeechSynthesisResult,\n SpeechSynthesisStreamRequest,\n SpeechSynthesisStreamResult,\n} from './speech-provider-types.js';\nimport { summarizeForTts } from './summarize.js';\nimport type {\n ProviderAttempt,\n ProviderFailureReason,\n TTSConfig,\n TTSModelOverrideConfig,\n TTSOptions,\n TTSProvider,\n TTSResultWithTracking,\n TtsDirectiveOverrides,\n} from './types.js';\n\nconst log = createLogger('TTS');\n\nexport interface SpeakOptions {\n tts?: TTSOptions;\n preprocess?: PreprocessOptions;\n parseDirectives?: boolean;\n modelOverrides?: TTSModelOverrideConfig;\n appConfig?: Config;\n /** Optional caller-supplied AbortSignal (currently passed through to providers via timeoutMs guard only). */\n signal?: AbortSignal;\n}\n\n/** Pulls the override bucket for a given provider id from the directive overrides. */\nfunction pickProviderOverrides(\n overrides: TtsDirectiveOverrides | undefined,\n providerId: TTSProvider,\n): SpeechProviderOverrides | undefined {\n if (!overrides) return undefined;\n const bucket = (overrides as Record<string, unknown>)[providerId];\n if (bucket && typeof bucket === 'object') {\n return bucket as SpeechProviderOverrides;\n }\n return undefined;\n}\n\n/** Merges TTSOptions (caller-supplied) with directive bucket. Directive wins on overlap. */\nfunction mergeOverrides(\n ttsOptions: TTSOptions | undefined,\n directiveBucket: SpeechProviderOverrides | undefined,\n): SpeechProviderOverrides | undefined {\n const merged: Record<string, unknown> = {};\n if (ttsOptions?.voice) merged.voice = ttsOptions.voice;\n if (ttsOptions?.model) merged.model = ttsOptions.model;\n if (ttsOptions?.speed) merged.speed = ttsOptions.speed;\n if (directiveBucket) {\n Object.assign(merged, directiveBucket);\n }\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\nfunction classifyError(error: unknown): ProviderFailureReason {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (message.includes('not configured') || message.includes('api key')) {\n return 'not_configured';\n }\n if (message.includes('timeout') || message.includes('timed out') || message.includes('aborted')) {\n return 'timeout';\n }\n if (message.includes('too long') || message.includes('text length') || message.includes('exceeds')) {\n return 'text_too_long';\n }\n return 'provider_error';\n }\n return 'unknown';\n}\n\n/** Common pre-flight: directive parse → preprocess → optional summarize. */\nasync function prepareTextForSynthesis(\n text: string,\n config: TTSConfig,\n options: SpeakOptions | undefined,\n): Promise<{\n text: string;\n wasPreprocessed: boolean;\n wasSummarized: boolean;\n ttsOptions: TTSOptions | undefined;\n directiveOverrides: TtsDirectiveOverrides | undefined;\n selectedProvider: TTSProvider;\n}> {\n let ttsText = text;\n let ttsOptions = options?.tts;\n let directiveOverrides: TtsDirectiveOverrides | undefined;\n let selectedProvider: TTSProvider = config.provider;\n\n if (options?.parseDirectives !== false && config.modelOverrides?.enabled) {\n const directiveResult = parseTtsDirectives(text, config.modelOverrides);\n ttsText = directiveResult.ttsText || directiveResult.cleanedText;\n directiveOverrides = directiveResult.overrides;\n if (directiveResult.overrides?.provider) {\n selectedProvider = directiveResult.overrides.provider;\n }\n }\n\n const maxLength = config.maxTextLength || 4096;\n const preprocessOptions: PreprocessOptions = {\n maxLength,\n stripMarkdown: true,\n normalizeWhitespace: true,\n ...options?.preprocess,\n };\n\n let preprocessResult: PreprocessResult = preprocessText(ttsText, preprocessOptions);\n const wasPreprocessed =\n preprocessResult.wasTruncated || preprocessResult.originalLength !== preprocessResult.finalLength;\n\n let wasSummarized = false;\n const sumCfg = config.summarization;\n const threshold = sumCfg?.threshold ?? maxLength;\n if (sumCfg?.enabled !== false && preprocessResult.text.length > threshold) {\n const summarizeResult = await summarizeForTts({\n text: preprocessResult.text,\n targetLength: sumCfg?.targetLength ?? maxLength,\n config: options?.appConfig,\n modelRef: sumCfg?.model,\n });\n if (summarizeResult.wasSummarized) {\n preprocessResult = {\n ...preprocessResult,\n text: summarizeResult.summary,\n finalLength: summarizeResult.summaryLength,\n wasTruncated: false,\n };\n wasSummarized = true;\n log.info(\n {\n originalLength: summarizeResult.originalLength,\n summaryLength: summarizeResult.summaryLength,\n },\n 'Text summarized before TTS',\n );\n }\n }\n\n return {\n text: preprocessResult.text,\n wasPreprocessed,\n wasSummarized,\n ttsOptions,\n directiveOverrides,\n selectedProvider,\n };\n}\n\n/** Build the SpeechSynthesisRequest from a resolved provider + prepared text. */\nfunction buildSynthesisRequest(\n resolved: ResolvedSpeechProvider,\n preparedText: string,\n ttsOptions: TTSOptions | undefined,\n directiveOverrides: TtsDirectiveOverrides | undefined,\n appConfig: Config | undefined,\n target: 'audio-file' | 'voice-note',\n): SpeechSynthesisRequest {\n const directiveBucket = pickProviderOverrides(directiveOverrides, resolved.providerId);\n const providerOverrides = mergeOverrides(ttsOptions, directiveBucket);\n const callTimeoutMs = ttsOptions?.timeoutMs ?? resolved.timeoutMs;\n return {\n text: preparedText,\n cfg: appConfig as Config,\n providerConfig: resolved.providerConfig,\n target,\n timeoutMs: callTimeoutMs,\n ...(providerOverrides ? { providerOverrides } : {}),\n };\n}\n\n/** Convert SpeechSynthesisResult → TTSResult shape (audio/format/provider). */\nfunction toTtsResult(\n resolved: ResolvedSpeechProvider,\n result: SpeechSynthesisResult,\n durationSeconds: number | undefined,\n): { audio: Buffer; format: string; provider: string; duration?: number } {\n return {\n audio: result.audioBuffer,\n format: result.outputFormat,\n provider: resolved.providerId,\n ...(durationSeconds !== undefined ? { duration: durationSeconds } : {}),\n };\n}\n\n// ---- Public API ---------------------------------------------------------\n\n/**\n * Run the TTS chain (primary → fallback) and return the first successful result.\n */\nexport async function speak(\n text: string,\n config: TTSConfig,\n options?: SpeakOptions,\n): Promise<TTSResultWithTracking> {\n if (!config.enabled) {\n throw new Error('TTS is not enabled');\n }\n if (config.trigger === 'tagged' && !/\\[\\[tts/.test(text)) {\n throw new Error('TTS trigger is tagged but no [[tts]] directive found');\n }\n\n const prepared = await prepareTextForSynthesis(text, config, options);\n const chain = resolveSpeechProviderChain(config);\n\n // Reorder so the directive-selected provider (if registered + configured) goes first.\n const orderedChain = (() => {\n const idx = chain.findIndex((c) => c.providerId === prepared.selectedProvider);\n if (idx <= 0) return chain;\n const reordered = [...chain];\n const [picked] = reordered.splice(idx, 1);\n if (picked) {\n reordered.unshift(picked);\n }\n return reordered;\n })();\n\n const attempts: ProviderAttempt[] = [];\n const attemptedProviders: string[] = [];\n let lastError: Error | undefined;\n\n for (const resolved of orderedChain) {\n const startTime = Date.now();\n attemptedProviders.push(resolved.providerId);\n\n if (typeof resolved.plugin.synthesize !== 'function') {\n attempts.push({\n provider: resolved.providerId,\n outcome: 'skipped',\n reasonCode: 'not_configured',\n latencyMs: Date.now() - startTime,\n error: `Provider \"${resolved.providerId}\" does not implement synthesize()`,\n });\n continue;\n }\n\n try {\n log.debug(\n { textLength: prepared.text.length, provider: resolved.providerId },\n 'Converting text to speech',\n );\n const request = buildSynthesisRequest(\n resolved,\n prepared.text,\n prepared.ttsOptions,\n prepared.directiveOverrides,\n options?.appConfig,\n 'audio-file',\n );\n const result = await resolved.plugin.synthesize(request);\n const durationSeconds = (Date.now() - startTime) / 1000;\n attempts.push({\n provider: resolved.providerId,\n outcome: 'success',\n reasonCode: 'success',\n latencyMs: Date.now() - startTime,\n });\n\n const primaryProvider = orderedChain[0]!.providerId;\n const fallbackFrom = resolved.providerId !== primaryProvider ? primaryProvider : undefined;\n if (fallbackFrom) {\n log.info(\n { primaryProvider: fallbackFrom, actualProvider: resolved.providerId, attempts },\n 'TTS used fallback provider',\n );\n } else {\n log.info(\n { provider: resolved.providerId, format: result.outputFormat, size: result.audioBuffer.length },\n 'TTS succeeded',\n );\n }\n\n return {\n ...toTtsResult(resolved, result, durationSeconds),\n attempts,\n ...(fallbackFrom ? { fallbackFrom } : {}),\n attemptedProviders,\n wasPreprocessed: prepared.wasPreprocessed,\n ttsText: prepared.text,\n wasSummarized: prepared.wasSummarized,\n };\n } catch (error) {\n const latencyMs = Date.now() - startTime;\n const reasonCode = classifyError(error);\n const errorMsg = error instanceof Error ? error.message : String(error);\n attempts.push({\n provider: resolved.providerId,\n outcome: 'failed',\n reasonCode,\n latencyMs,\n error: errorMsg,\n });\n lastError = error instanceof Error ? error : new Error(String(error));\n log.warn(\n { provider: resolved.providerId, errorMessage: errorMsg, reasonCode, latencyMs },\n 'TTS provider failed, trying next',\n );\n }\n }\n\n log.error({ attempts, attemptedProviders }, 'All TTS providers failed');\n throw (\n lastError ?? new Error(`All TTS providers failed: ${attempts.map((a) => a.error).join('; ')}`)\n );\n}\n\n/** Single-provider variant; bypasses the fallback chain. */\nexport async function speakWithProvider(\n text: string,\n config: TTSConfig,\n providerName: TTSProvider,\n options?: SpeakOptions,\n): Promise<TTSResultWithTracking> {\n if (!config.enabled) {\n throw new Error('TTS is not enabled');\n }\n const resolved = resolveSpeechProvider(providerName, config);\n if (!resolved) {\n throw new Error(`Provider '${providerName}' is not available`);\n }\n if (typeof resolved.plugin.synthesize !== 'function') {\n throw new Error(`Provider '${providerName}' does not implement synthesize()`);\n }\n\n const prepared = await prepareTextForSynthesis(text, config, options);\n const startTime = Date.now();\n const request = buildSynthesisRequest(\n resolved,\n prepared.text,\n prepared.ttsOptions,\n prepared.directiveOverrides,\n options?.appConfig,\n 'audio-file',\n );\n const result = await resolved.plugin.synthesize(request);\n const durationSeconds = (Date.now() - startTime) / 1000;\n\n return {\n ...toTtsResult(resolved, result, durationSeconds),\n attempts: [\n {\n provider: providerName,\n outcome: 'success',\n reasonCode: 'success',\n latencyMs: Date.now() - startTime,\n },\n ],\n attemptedProviders: [providerName],\n wasPreprocessed: prepared.wasPreprocessed,\n ttsText: prepared.text,\n wasSummarized: prepared.wasSummarized,\n };\n}\n\n// ---- Streaming surface --------------------------------------------------\n\nexport interface SpeakStreamResult {\n /** Audio chunks. Caller is responsible for consuming the stream. */\n audioStream: ReadableStream<Uint8Array>;\n outputFormat: string;\n fileExtension: string;\n voiceCompatible: boolean;\n /** Provider id that produced this stream. */\n provider: TTSProvider;\n /** Cleanup hook (close sockets etc.). Always defined; no-op when nothing to release. */\n release: () => Promise<void>;\n /** Pre-flight metadata (after directive parse / preprocess / summarize). */\n ttsText: string;\n wasPreprocessed: boolean;\n wasSummarized: boolean;\n}\n\n/**\n * Streaming TTS — tries the chain and returns the first provider's stream.\n * Providers without `synthesizeStream` are wrapped via wrapBufferAsStream.\n */\nexport async function speakStream(\n text: string,\n config: TTSConfig,\n options?: SpeakOptions,\n): Promise<SpeakStreamResult> {\n if (!config.enabled) {\n throw new Error('TTS is not enabled');\n }\n if (config.trigger === 'tagged' && !/\\[\\[tts/.test(text)) {\n throw new Error('TTS trigger is tagged but no [[tts]] directive found');\n }\n\n const prepared = await prepareTextForSynthesis(text, config, options);\n const chain = resolveSpeechProviderChain(config);\n\n let lastError: Error | undefined;\n for (const resolved of chain) {\n try {\n const baseRequest: SpeechSynthesisStreamRequest = buildSynthesisRequest(\n resolved,\n prepared.text,\n prepared.ttsOptions,\n prepared.directiveOverrides,\n options?.appConfig,\n 'voice-note',\n );\n\n let streamResult: SpeechSynthesisStreamResult;\n if (typeof resolved.plugin.synthesizeStream === 'function') {\n streamResult = await resolved.plugin.synthesizeStream(baseRequest);\n } else if (typeof resolved.plugin.synthesize === 'function') {\n // Fallback: buffer-then-wrap. Documented behavior: callers see a\n // single-chunk stream with the same outputFormat.\n const bufferResult = await resolved.plugin.synthesize(baseRequest);\n streamResult = wrapBufferAsStream(bufferResult);\n } else {\n throw new Error(\n `Provider \"${resolved.providerId}\" implements neither synthesize nor synthesizeStream`,\n );\n }\n\n log.info(\n {\n provider: resolved.providerId,\n outputFormat: streamResult.outputFormat,\n voiceCompatible: streamResult.voiceCompatible,\n streamed: typeof resolved.plugin.synthesizeStream === 'function',\n },\n 'TTS stream ready',\n );\n\n const release = streamResult.release ?? (async () => {});\n return {\n audioStream: streamResult.audioStream,\n outputFormat: streamResult.outputFormat,\n fileExtension: streamResult.fileExtension,\n voiceCompatible: streamResult.voiceCompatible,\n provider: resolved.providerId,\n release,\n ttsText: prepared.text,\n wasPreprocessed: prepared.wasPreprocessed,\n wasSummarized: prepared.wasSummarized,\n };\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n log.warn(\n { err: lastError, provider: resolved.providerId },\n `TTS stream provider \"${resolved.providerId}\" failed, trying next`,\n );\n }\n }\n throw lastError ?? new Error('All TTS providers failed (streaming)');\n}\n\n/** Wrap a one-shot synthesis result into a single-chunk stream (no-op release). */\nfunction wrapBufferAsStream(result: SpeechSynthesisResult): SpeechSynthesisStreamResult {\n const { audioBuffer } = result;\n const audioStream = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(new Uint8Array(audioBuffer));\n controller.close();\n },\n });\n return {\n audioStream,\n outputFormat: result.outputFormat,\n fileExtension: result.fileExtension,\n voiceCompatible: result.voiceCompatible,\n };\n}\n"],"mappings":";;;;;;;aAkBqD;AAwBrD,MAAM,MAAM,aAAa,MAAM;;AAa/B,SAAS,sBACP,WACA,YACqC;AACrC,KAAI,CAAC,UAAW,QAAO,KAAA;CACvB,MAAM,SAAU,UAAsC;AACtD,KAAI,UAAU,OAAO,WAAW,SAC9B,QAAO;;;AAMX,SAAS,eACP,YACA,iBACqC;CACrC,MAAM,SAAkC,EAAE;AAC1C,KAAI,YAAY,MAAO,QAAO,QAAQ,WAAW;AACjD,KAAI,YAAY,MAAO,QAAO,QAAQ,WAAW;AACjD,KAAI,YAAY,MAAO,QAAO,QAAQ,WAAW;AACjD,KAAI,gBACF,QAAO,OAAO,QAAQ,gBAAgB;AAExC,QAAO,OAAO,KAAK,OAAO,CAAC,SAAS,IAAI,SAAS,KAAA;;AAGnD,SAAS,cAAc,OAAuC;AAC5D,KAAI,iBAAiB,OAAO;EAC1B,MAAM,UAAU,MAAM,QAAQ,aAAa;AAC3C,MAAI,QAAQ,SAAS,iBAAiB,IAAI,QAAQ,SAAS,UAAU,CACnE,QAAO;AAET,MAAI,QAAQ,SAAS,UAAU,IAAI,QAAQ,SAAS,YAAY,IAAI,QAAQ,SAAS,UAAU,CAC7F,QAAO;AAET,MAAI,QAAQ,SAAS,WAAW,IAAI,QAAQ,SAAS,cAAc,IAAI,QAAQ,SAAS,UAAU,CAChG,QAAO;AAET,SAAO;;AAET,QAAO;;;AAIT,eAAe,wBACb,MACA,QACA,SAQC;CACD,IAAI,UAAU;CACd,IAAI,aAAa,SAAS;CAC1B,IAAI;CACJ,IAAI,mBAAgC,OAAO;AAE3C,KAAI,SAAS,oBAAoB,SAAS,OAAO,gBAAgB,SAAS;EACxE,MAAM,kBAAkB,mBAAmB,MAAM,OAAO,eAAe;AACvE,YAAU,gBAAgB,WAAW,gBAAgB;AACrD,uBAAqB,gBAAgB;AACrC,MAAI,gBAAgB,WAAW,SAC7B,oBAAmB,gBAAgB,UAAU;;CAIjD,MAAM,YAAY,OAAO,iBAAiB;CAC1C,MAAM,oBAAuC;EAC3C;EACA,eAAe;EACf,qBAAqB;EACrB,GAAG,SAAS;EACb;CAED,IAAI,mBAAqC,eAAe,SAAS,kBAAkB;CACnF,MAAM,kBACJ,iBAAiB,gBAAgB,iBAAiB,mBAAmB,iBAAiB;CAExF,IAAI,gBAAgB;CACpB,MAAM,SAAS,OAAO;CACtB,MAAM,YAAY,QAAQ,aAAa;AACvC,KAAI,QAAQ,YAAY,SAAS,iBAAiB,KAAK,SAAS,WAAW;EACzE,MAAM,kBAAkB,MAAM,gBAAgB;GAC5C,MAAM,iBAAiB;GACvB,cAAc,QAAQ,gBAAgB;GACtC,QAAQ,SAAS;GACjB,UAAU,QAAQ;GACnB,CAAC;AACF,MAAI,gBAAgB,eAAe;AACjC,sBAAmB;IACjB,GAAG;IACH,MAAM,gBAAgB;IACtB,aAAa,gBAAgB;IAC7B,cAAc;IACf;AACD,mBAAgB;AAChB,OAAI,KACF;IACE,gBAAgB,gBAAgB;IAChC,eAAe,gBAAgB;IAChC,EACD,6BACD;;;AAIL,QAAO;EACL,MAAM,iBAAiB;EACvB;EACA;EACA;EACA;EACA;EACD;;;AAIH,SAAS,sBACP,UACA,cACA,YACA,oBACA,WACA,QACwB;CAExB,MAAM,oBAAoB,eAAe,YADjB,sBAAsB,oBAAoB,SAAS,WACP,CAAC;CACrE,MAAM,gBAAgB,YAAY,aAAa,SAAS;AACxD,QAAO;EACL,MAAM;EACN,KAAK;EACL,gBAAgB,SAAS;EACzB;EACA,WAAW;EACX,GAAI,oBAAoB,EAAE,mBAAmB,GAAG,EAAE;EACnD;;;AAIH,SAAS,YACP,UACA,QACA,iBACwE;AACxE,QAAO;EACL,OAAO,OAAO;EACd,QAAQ,OAAO;EACf,UAAU,SAAS;EACnB,GAAI,oBAAoB,KAAA,IAAY,EAAE,UAAU,iBAAiB,GAAG,EAAE;EACvE;;;;;AAQH,eAAsB,MACpB,MACA,QACA,SACgC;AAChC,KAAI,CAAC,OAAO,QACV,OAAM,IAAI,MAAM,qBAAqB;AAEvC,KAAI,OAAO,YAAY,YAAY,CAAC,UAAU,KAAK,KAAK,CACtD,OAAM,IAAI,MAAM,uDAAuD;CAGzE,MAAM,WAAW,MAAM,wBAAwB,MAAM,QAAQ,QAAQ;CACrE,MAAM,QAAQ,2BAA2B,OAAO;CAGhD,MAAM,sBAAsB;EAC1B,MAAM,MAAM,MAAM,WAAW,MAAM,EAAE,eAAe,SAAS,iBAAiB;AAC9E,MAAI,OAAO,EAAG,QAAO;EACrB,MAAM,YAAY,CAAC,GAAG,MAAM;EAC5B,MAAM,CAAC,UAAU,UAAU,OAAO,KAAK,EAAE;AACzC,MAAI,OACF,WAAU,QAAQ,OAAO;AAE3B,SAAO;KACL;CAEJ,MAAM,WAA8B,EAAE;CACtC,MAAM,qBAA+B,EAAE;CACvC,IAAI;AAEJ,MAAK,MAAM,YAAY,cAAc;EACnC,MAAM,YAAY,KAAK,KAAK;AAC5B,qBAAmB,KAAK,SAAS,WAAW;AAE5C,MAAI,OAAO,SAAS,OAAO,eAAe,YAAY;AACpD,YAAS,KAAK;IACZ,UAAU,SAAS;IACnB,SAAS;IACT,YAAY;IACZ,WAAW,KAAK,KAAK,GAAG;IACxB,OAAO,aAAa,SAAS,WAAW;IACzC,CAAC;AACF;;AAGF,MAAI;AACF,OAAI,MACF;IAAE,YAAY,SAAS,KAAK;IAAQ,UAAU,SAAS;IAAY,EACnE,4BACD;GACD,MAAM,UAAU,sBACd,UACA,SAAS,MACT,SAAS,YACT,SAAS,oBACT,SAAS,WACT,aACD;GACD,MAAM,SAAS,MAAM,SAAS,OAAO,WAAW,QAAQ;GACxD,MAAM,mBAAmB,KAAK,KAAK,GAAG,aAAa;AACnD,YAAS,KAAK;IACZ,UAAU,SAAS;IACnB,SAAS;IACT,YAAY;IACZ,WAAW,KAAK,KAAK,GAAG;IACzB,CAAC;GAEF,MAAM,kBAAkB,aAAa,GAAI;GACzC,MAAM,eAAe,SAAS,eAAe,kBAAkB,kBAAkB,KAAA;AACjF,OAAI,aACF,KAAI,KACF;IAAE,iBAAiB;IAAc,gBAAgB,SAAS;IAAY;IAAU,EAChF,6BACD;OAED,KAAI,KACF;IAAE,UAAU,SAAS;IAAY,QAAQ,OAAO;IAAc,MAAM,OAAO,YAAY;IAAQ,EAC/F,gBACD;AAGH,UAAO;IACL,GAAG,YAAY,UAAU,QAAQ,gBAAgB;IACjD;IACA,GAAI,eAAe,EAAE,cAAc,GAAG,EAAE;IACxC;IACA,iBAAiB,SAAS;IAC1B,SAAS,SAAS;IAClB,eAAe,SAAS;IACzB;WACM,OAAO;GACd,MAAM,YAAY,KAAK,KAAK,GAAG;GAC/B,MAAM,aAAa,cAAc,MAAM;GACvC,MAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACvE,YAAS,KAAK;IACZ,UAAU,SAAS;IACnB,SAAS;IACT;IACA;IACA,OAAO;IACR,CAAC;AACF,eAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AACrE,OAAI,KACF;IAAE,UAAU,SAAS;IAAY,cAAc;IAAU;IAAY;IAAW,EAChF,mCACD;;;AAIL,KAAI,MAAM;EAAE;EAAU;EAAoB,EAAE,2BAA2B;AACvE,OACE,6BAAa,IAAI,MAAM,6BAA6B,SAAS,KAAK,MAAM,EAAE,MAAM,CAAC,KAAK,KAAK,GAAG;;;AAKlG,eAAsB,kBACpB,MACA,QACA,cACA,SACgC;AAChC,KAAI,CAAC,OAAO,QACV,OAAM,IAAI,MAAM,qBAAqB;CAEvC,MAAM,WAAW,sBAAsB,cAAc,OAAO;AAC5D,KAAI,CAAC,SACH,OAAM,IAAI,MAAM,aAAa,aAAa,oBAAoB;AAEhE,KAAI,OAAO,SAAS,OAAO,eAAe,WACxC,OAAM,IAAI,MAAM,aAAa,aAAa,mCAAmC;CAG/E,MAAM,WAAW,MAAM,wBAAwB,MAAM,QAAQ,QAAQ;CACrE,MAAM,YAAY,KAAK,KAAK;CAC5B,MAAM,UAAU,sBACd,UACA,SAAS,MACT,SAAS,YACT,SAAS,oBACT,SAAS,WACT,aACD;AAID,QAAO;EACL,GAAG,YAAY,UAAU,MAJN,SAAS,OAAO,WAAW,QAAQ,GAC/B,KAAK,KAAK,GAAG,aAAa,IAGA;EACjD,UAAU,CACR;GACE,UAAU;GACV,SAAS;GACT,YAAY;GACZ,WAAW,KAAK,KAAK,GAAG;GACzB,CACF;EACD,oBAAoB,CAAC,aAAa;EAClC,iBAAiB,SAAS;EAC1B,SAAS,SAAS;EAClB,eAAe,SAAS;EACzB;;;;;;AAyBH,eAAsB,YACpB,MACA,QACA,SAC4B;AAC5B,KAAI,CAAC,OAAO,QACV,OAAM,IAAI,MAAM,qBAAqB;AAEvC,KAAI,OAAO,YAAY,YAAY,CAAC,UAAU,KAAK,KAAK,CACtD,OAAM,IAAI,MAAM,uDAAuD;CAGzE,MAAM,WAAW,MAAM,wBAAwB,MAAM,QAAQ,QAAQ;CACrE,MAAM,QAAQ,2BAA2B,OAAO;CAEhD,IAAI;AACJ,MAAK,MAAM,YAAY,MACrB,KAAI;EACF,MAAM,cAA4C,sBAChD,UACA,SAAS,MACT,SAAS,YACT,SAAS,oBACT,SAAS,WACT,aACD;EAED,IAAI;AACJ,MAAI,OAAO,SAAS,OAAO,qBAAqB,WAC9C,gBAAe,MAAM,SAAS,OAAO,iBAAiB,YAAY;WACzD,OAAO,SAAS,OAAO,eAAe,WAI/C,gBAAe,mBAAmB,MADP,SAAS,OAAO,WAAW,YAAY,CACnB;MAE/C,OAAM,IAAI,MACR,aAAa,SAAS,WAAW,sDAClC;AAGH,MAAI,KACF;GACE,UAAU,SAAS;GACnB,cAAc,aAAa;GAC3B,iBAAiB,aAAa;GAC9B,UAAU,OAAO,SAAS,OAAO,qBAAqB;GACvD,EACD,mBACD;EAED,MAAM,UAAU,aAAa,YAAY,YAAY;AACrD,SAAO;GACL,aAAa,aAAa;GAC1B,cAAc,aAAa;GAC3B,eAAe,aAAa;GAC5B,iBAAiB,aAAa;GAC9B,UAAU,SAAS;GACnB;GACA,SAAS,SAAS;GAClB,iBAAiB,SAAS;GAC1B,eAAe,SAAS;GACzB;UACM,OAAO;AACd,cAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AACrE,MAAI,KACF;GAAE,KAAK;GAAW,UAAU,SAAS;GAAY,EACjD,wBAAwB,SAAS,WAAW,uBAC7C;;AAGL,OAAM,6BAAa,IAAI,MAAM,uCAAuC;;;AAItE,SAAS,mBAAmB,QAA4D;CACtF,MAAM,EAAE,gBAAgB;AAOxB,QAAO;EACL,aAAA,IAPsB,eAA2B,EACjD,MAAM,YAAY;AAChB,cAAW,QAAQ,IAAI,WAAW,YAAY,CAAC;AAC/C,cAAW,OAAO;KAErB,CAEY;EACX,cAAc,OAAO;EACrB,eAAe,OAAO;EACtB,iBAAiB,OAAO;EACzB"}
@@ -128,8 +128,8 @@ export interface SpeechProviderPlugin {
128
128
  /** Canonical provider id. Must be unique within the registry. */
129
129
  id: SpeechProviderId;
130
130
  /**
131
- * Optional aliases (e.g. `["openai-tts"]` for back-compat with old config keys).
132
- * Aliases participate in lookup but `id` is the canonical persisted name.
131
+ * Optional aliases for provider lookup. Aliases participate in lookup but
132
+ * `id` is the canonical persisted name.
133
133
  */
134
134
  aliases?: readonly string[];
135
135
  /**
@@ -1,7 +1,7 @@
1
1
  //#region src/voice/tts/types.ts
2
2
  const DEFAULT_TTS_CONFIG = {
3
3
  enabled: false,
4
- provider: "openai",
4
+ provider: "edge",
5
5
  trigger: "always",
6
6
  fallback: {
7
7
  enabled: true,
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../../src/voice/tts/types.ts"],"sourcesContent":["/**\n * TTS (Text-to-Speech) Types\n */\n\n/** Built-in provider ids shipped in core. Extensions add more at runtime. */\nexport type BuiltinTTSProvider = 'openai' | 'alibaba' | 'edge' | 'minimax' | 'tts-local-cli';\n\n/** Any registered SpeechProviderPlugin id (built-in or extension). */\nexport type TTSProvider = string;\n\nexport type TTSAutoMode = 'off' | 'always' | 'inbound' | 'tagged';\n\nexport interface TTSOptions {\n /** Voice ID */\n voice?: string;\n /** Speed (0.25 - 4.0) */\n speed?: number;\n /** Output format */\n format?: 'opus' | 'mp3' | 'wav';\n /** Model ID (for provider-specific model selection) */\n model?: string;\n /** Request timeout in milliseconds */\n timeoutMs?: number;\n}\n\nexport interface TTSResult {\n /** Audio buffer */\n audio: Buffer;\n /** Format */\n format: string;\n /** Duration in seconds (if available) */\n duration?: number;\n /** Provider that generated the speech */\n provider: string;\n}\n\nexport interface TTSProviderInterface {\n /** Provider name */\n name: string;\n /** Convert text to speech */\n speak(text: string, options?: TTSOptions): Promise<TTSResult>;\n /** Check if provider is configured */\n isConfigured(): boolean;\n}\n\nexport interface TTSModelOverrideConfig {\n /** Enable model-provided overrides for TTS */\n enabled?: boolean;\n /** Allow model-provided TTS text blocks */\n allowText?: boolean;\n /** Allow model-provided provider override (default: false) */\n allowProvider?: boolean;\n /** Allow model-provided voice/voiceId override */\n allowVoice?: boolean;\n /** Allow model-provided modelId override */\n allowModelId?: boolean;\n /** Allow model-provided voice settings override */\n allowVoiceSettings?: boolean;\n /** Allow model-provided normalization or language overrides */\n allowNormalization?: boolean;\n /** Allow model-provided seed override */\n allowSeed?: boolean;\n}\n\nexport interface TTSConfig {\n enabled: boolean;\n provider: TTSProvider;\n /** Trigger mode: auto = reply with voice when user sends voice */\n trigger: TTSAutoMode;\n /** Fallback configuration */\n fallback?: {\n enabled: boolean;\n order: TTSProvider[];\n };\n /** Maximum text length for TTS */\n maxTextLength?: number;\n /** API request timeout (ms) */\n timeoutMs?: number;\n /** Long-text summarization before TTS */\n summarization?: TTSSummarizationConfig;\n /** Allow model to override TTS parameters */\n modelOverrides?: TTSModelOverrideConfig;\n /** Provider settings map (`messages.tts.providers.<id>`). */\n providers?: Record<string, Record<string, unknown>>;\n}\n\nexport interface TTSSummarizationConfig {\n /** When true (default), long text is summarized via LLM before TTS */\n enabled?: boolean;\n targetLength?: number;\n threshold?: number;\n model?: string;\n}\n\nexport const DEFAULT_TTS_CONFIG: TTSConfig = {\n enabled: false,\n provider: 'openai',\n trigger: 'always',\n fallback: {\n enabled: true,\n order: ['openai', 'alibaba', 'minimax', 'edge'],\n },\n maxTextLength: 512, // Conservative default to accommodate all providers (Alibaba limit is 512)\n timeoutMs: 30000,\n summarization: {\n enabled: true,\n },\n modelOverrides: {\n enabled: true,\n allowText: true,\n allowProvider: false,\n allowVoice: true,\n allowModelId: true,\n allowVoiceSettings: false,\n allowNormalization: false,\n allowSeed: false,\n },\n providers: {\n alibaba: { model: 'qwen-tts', voice: 'Cherry' },\n openai: { model: 'tts-1', voice: 'alloy' },\n edge: {\n enabled: true,\n voice: 'en-US-MichelleNeural',\n lang: 'en-US',\n outputFormat: 'audio-24khz-48kbitrate-mono-mp3',\n },\n minimax: { model: 'speech-2.8-hd', voice: 'male-qn-qingse' },\n },\n};\n\n/** TTS directive parse result */\nexport interface TtsDirectiveParseResult {\n cleanedText: string;\n ttsText?: string;\n hasDirective: boolean;\n overrides: TtsDirectiveOverrides;\n warnings: string[];\n}\n\n/** TTS directive overrides */\nexport interface TtsDirectiveOverrides {\n ttsText?: string;\n provider?: TTSProvider;\n openai?: {\n voice?: string;\n model?: string;\n };\n alibaba?: {\n voice?: string;\n model?: string;\n };\n edge?: {\n voice?: string;\n };\n minimax?: {\n voice?: string;\n model?: string;\n };\n}\n\n/** Provider attempt result (TTS fallback chain) */\nexport type ProviderAttemptOutcome = 'success' | 'skipped' | 'failed';\n\n/** Provider failure reason classification */\nexport type ProviderFailureReason =\n | 'success'\n | 'not_configured'\n | 'timeout'\n | 'provider_error'\n | 'text_too_long'\n | 'unknown';\n\n/** Single provider attempt in a TTS fallback chain */\nexport interface ProviderAttempt {\n provider: string;\n outcome: ProviderAttemptOutcome;\n reasonCode: ProviderFailureReason;\n latencyMs: number;\n error?: string;\n}\n\n/** TTS result including fallback / preprocessing metadata */\nexport interface TTSResultWithTracking extends TTSResult {\n attempts: ProviderAttempt[];\n fallbackFrom?: string;\n attemptedProviders: string[];\n wasPreprocessed?: boolean;\n ttsText?: string;\n wasSummarized?: boolean;\n}\n\n/** Last TTS call diagnostics (memory-only) */\nexport interface TtsStatusEntry {\n timestamp: number;\n success: boolean;\n provider?: string;\n latencyMs?: number;\n error?: string;\n textLength?: number;\n audioSize?: number;\n audioFormat?: string;\n usedFallback?: boolean;\n wasSummarized?: boolean;\n}\n\nexport interface TtsRuntimeStatus {\n lastAttempt?: TtsStatusEntry;\n recentSuccessRate?: number;\n totalCalls: number;\n totalSuccesses: number;\n totalFailures: number;\n}\n"],"mappings":";AA8FA,MAAa,qBAAgC;CAC3C,SAAS;CACT,UAAU;CACV,SAAS;CACT,UAAU;EACR,SAAS;EACT,OAAO;GAAC;GAAU;GAAW;GAAW;GAAO;EAChD;CACD,eAAe;CACf,WAAW;CACX,eAAe,EACb,SAAS,MACV;CACD,gBAAgB;EACd,SAAS;EACT,WAAW;EACX,eAAe;EACf,YAAY;EACZ,cAAc;EACd,oBAAoB;EACpB,oBAAoB;EACpB,WAAW;EACZ;CACD,WAAW;EACT,SAAS;GAAE,OAAO;GAAY,OAAO;GAAU;EAC/C,QAAQ;GAAE,OAAO;GAAS,OAAO;GAAS;EAC1C,MAAM;GACJ,SAAS;GACT,OAAO;GACP,MAAM;GACN,cAAc;GACf;EACD,SAAS;GAAE,OAAO;GAAiB,OAAO;GAAkB;EAC7D;CACF"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../../src/voice/tts/types.ts"],"sourcesContent":["/**\n * TTS (Text-to-Speech) Types\n */\n\n/** Built-in provider ids shipped in core. Extensions add more at runtime. */\nexport type BuiltinTTSProvider = 'openai' | 'alibaba' | 'edge' | 'minimax' | 'tts-local-cli';\n\n/** Any registered SpeechProviderPlugin id (built-in or extension). */\nexport type TTSProvider = string;\n\nexport type TTSAutoMode = 'off' | 'always' | 'inbound' | 'tagged';\n\nexport interface TTSOptions {\n /** Voice ID */\n voice?: string;\n /** Speed (0.25 - 4.0) */\n speed?: number;\n /** Output format */\n format?: 'opus' | 'mp3' | 'wav';\n /** Model ID (for provider-specific model selection) */\n model?: string;\n /** Request timeout in milliseconds */\n timeoutMs?: number;\n}\n\nexport interface TTSResult {\n /** Audio buffer */\n audio: Buffer;\n /** Format */\n format: string;\n /** Duration in seconds (if available) */\n duration?: number;\n /** Provider that generated the speech */\n provider: string;\n}\n\nexport interface TTSProviderInterface {\n /** Provider name */\n name: string;\n /** Convert text to speech */\n speak(text: string, options?: TTSOptions): Promise<TTSResult>;\n /** Check if provider is configured */\n isConfigured(): boolean;\n}\n\nexport interface TTSModelOverrideConfig {\n /** Enable model-provided overrides for TTS */\n enabled?: boolean;\n /** Allow model-provided TTS text blocks */\n allowText?: boolean;\n /** Allow model-provided provider override (default: false) */\n allowProvider?: boolean;\n /** Allow model-provided voice/voiceId override */\n allowVoice?: boolean;\n /** Allow model-provided modelId override */\n allowModelId?: boolean;\n /** Allow model-provided voice settings override */\n allowVoiceSettings?: boolean;\n /** Allow model-provided normalization or language overrides */\n allowNormalization?: boolean;\n /** Allow model-provided seed override */\n allowSeed?: boolean;\n}\n\nexport interface TTSConfig {\n enabled: boolean;\n provider: TTSProvider;\n /** Trigger mode: auto = reply with voice when user sends voice */\n trigger: TTSAutoMode;\n /** Fallback configuration */\n fallback?: {\n enabled: boolean;\n order: TTSProvider[];\n };\n /** Maximum text length for TTS */\n maxTextLength?: number;\n /** API request timeout (ms) */\n timeoutMs?: number;\n /** Long-text summarization before TTS */\n summarization?: TTSSummarizationConfig;\n /** Allow model to override TTS parameters */\n modelOverrides?: TTSModelOverrideConfig;\n /** Provider settings map (`messages.tts.providers.<id>`). */\n providers?: Record<string, Record<string, unknown>>;\n}\n\nexport interface TTSSummarizationConfig {\n /** When true (default), long text is summarized via LLM before TTS */\n enabled?: boolean;\n targetLength?: number;\n threshold?: number;\n model?: string;\n}\n\nexport const DEFAULT_TTS_CONFIG: TTSConfig = {\n enabled: false,\n provider: 'edge',\n trigger: 'always',\n fallback: {\n enabled: true,\n order: ['openai', 'alibaba', 'minimax', 'edge'],\n },\n maxTextLength: 512, // Conservative default to accommodate all providers (Alibaba limit is 512)\n timeoutMs: 30000,\n summarization: {\n enabled: true,\n },\n modelOverrides: {\n enabled: true,\n allowText: true,\n allowProvider: false,\n allowVoice: true,\n allowModelId: true,\n allowVoiceSettings: false,\n allowNormalization: false,\n allowSeed: false,\n },\n providers: {\n alibaba: { model: 'qwen-tts', voice: 'Cherry' },\n openai: { model: 'tts-1', voice: 'alloy' },\n edge: {\n enabled: true,\n voice: 'en-US-MichelleNeural',\n lang: 'en-US',\n outputFormat: 'audio-24khz-48kbitrate-mono-mp3',\n },\n minimax: { model: 'speech-2.8-hd', voice: 'male-qn-qingse' },\n },\n};\n\n/** TTS directive parse result */\nexport interface TtsDirectiveParseResult {\n cleanedText: string;\n ttsText?: string;\n hasDirective: boolean;\n overrides: TtsDirectiveOverrides;\n warnings: string[];\n}\n\n/** TTS directive overrides */\nexport interface TtsDirectiveOverrides {\n ttsText?: string;\n provider?: TTSProvider;\n openai?: {\n voice?: string;\n model?: string;\n };\n alibaba?: {\n voice?: string;\n model?: string;\n };\n edge?: {\n voice?: string;\n };\n minimax?: {\n voice?: string;\n model?: string;\n };\n}\n\n/** Provider attempt result (TTS fallback chain) */\nexport type ProviderAttemptOutcome = 'success' | 'skipped' | 'failed';\n\n/** Provider failure reason classification */\nexport type ProviderFailureReason =\n | 'success'\n | 'not_configured'\n | 'timeout'\n | 'provider_error'\n | 'text_too_long'\n | 'unknown';\n\n/** Single provider attempt in a TTS fallback chain */\nexport interface ProviderAttempt {\n provider: string;\n outcome: ProviderAttemptOutcome;\n reasonCode: ProviderFailureReason;\n latencyMs: number;\n error?: string;\n}\n\n/** TTS result including fallback / preprocessing metadata */\nexport interface TTSResultWithTracking extends TTSResult {\n attempts: ProviderAttempt[];\n fallbackFrom?: string;\n attemptedProviders: string[];\n wasPreprocessed?: boolean;\n ttsText?: string;\n wasSummarized?: boolean;\n}\n\n/** Last TTS call diagnostics (memory-only) */\nexport interface TtsStatusEntry {\n timestamp: number;\n success: boolean;\n provider?: string;\n latencyMs?: number;\n error?: string;\n textLength?: number;\n audioSize?: number;\n audioFormat?: string;\n usedFallback?: boolean;\n wasSummarized?: boolean;\n}\n\nexport interface TtsRuntimeStatus {\n lastAttempt?: TtsStatusEntry;\n recentSuccessRate?: number;\n totalCalls: number;\n totalSuccesses: number;\n totalFailures: number;\n}\n"],"mappings":";AA8FA,MAAa,qBAAgC;CAC3C,SAAS;CACT,UAAU;CACV,SAAS;CACT,UAAU;EACR,SAAS;EACT,OAAO;GAAC;GAAU;GAAW;GAAW;GAAO;EAChD;CACD,eAAe;CACf,WAAW;CACX,eAAe,EACb,SAAS,MACV;CACD,gBAAgB;EACd,SAAS;EACT,WAAW;EACX,eAAe;EACf,YAAY;EACZ,cAAc;EACd,oBAAoB;EACpB,oBAAoB;EACpB,WAAW;EACZ;CACD,WAAW;EACT,SAAS;GAAE,OAAO;GAAY,OAAO;GAAU;EAC/C,QAAQ;GAAE,OAAO;GAAS,OAAO;GAAS;EAC1C,MAAM;GACJ,SAAS;GACT,OAAO;GACP,MAAM;GACN,cAAc;GACf;EACD,SAAS;GAAE,OAAO;GAAiB,OAAO;GAAkB;EAC7D;CACF"}
@@ -6,7 +6,6 @@ export interface WorkflowResultEnvelope {
6
6
  artifacts?: WorkflowArtifactRef[];
7
7
  followUps?: WorkflowFollowUp[];
8
8
  structuredOutput?: unknown;
9
- raw?: unknown;
10
9
  }
11
10
  /** Product-level outcome contract for reusable workflow deliverables. */
12
11
  export interface WorkflowOutcome {
@@ -1,6 +1,6 @@
1
- import { getAgentDefaultModelRef, init_schema } from "../../config/schema.js";
2
1
  import { createLogger } from "../../utils/logger/index.js";
3
2
  import { init_logger } from "../../utils/logger.js";
3
+ import { getAgentDefaultModelRef, init_schema } from "../../config/schema.js";
4
4
  import { getDefaultModelSync, init_providers, resolveModel } from "../../providers/index.js";
5
5
  import { resolveModelRef } from "../../config/agent-typed-models.js";
6
6
  import { buildWorkflowDraftPrompt, buildWorkflowDraftRepairPrompt } from "./workflow-draft-prompt.js";
@@ -156,8 +156,9 @@ var WorkflowEngine = class {
156
156
  }), options.timeoutSec ?? definition.defaults.timeoutSec);
157
157
  await eventQueue;
158
158
  if (currentPhaseId) await appendEvent("phase_completed", { phaseId: currentPhaseId });
159
- assertWorkflowOutput(definition, runtimeResult.result);
160
- await appendEvent("run_completed", { result: toWorkflowResultEnvelope(runtimeResult.result) });
159
+ const result = requireWorkflowResultEnvelope(runtimeResult.result);
160
+ assertWorkflowOutput(definition, result);
161
+ await appendEvent("run_completed", { result });
161
162
  await this.callHooks((hook) => hook.afterRun?.({
162
163
  runId,
163
164
  status: "succeeded"
@@ -659,27 +660,6 @@ function truncate(value, maxLength) {
659
660
  if (trimmed.length <= maxLength) return trimmed;
660
661
  return `${trimmed.slice(0, maxLength - 1)}…`;
661
662
  }
662
- function toWorkflowResultEnvelope(value) {
663
- if (isWorkflowResultEnvelope(value)) return value;
664
- if (typeof value === "string") return {
665
- summary: truncate(value, 800),
666
- sections: [{
667
- kind: "text",
668
- title: "Result",
669
- content: value
670
- }],
671
- raw: value
672
- };
673
- return {
674
- summary: "Workflow completed.",
675
- sections: [{
676
- kind: "json",
677
- title: "Result",
678
- value
679
- }],
680
- raw: value
681
- };
682
- }
683
663
  function assertWorkflowOutput(definition, value) {
684
664
  const validation = validateWorkflowJsonSchema(definition.outputSchema, value);
685
665
  if (validation.ok) return;
@@ -690,6 +670,10 @@ function isWorkflowResultEnvelope(value) {
690
670
  const record = value;
691
671
  return typeof record.summary === "string" && Array.isArray(record.sections);
692
672
  }
673
+ function requireWorkflowResultEnvelope(value) {
674
+ if (isWorkflowResultEnvelope(value)) return value;
675
+ throw new WorkflowEngineRunError("result_validation_failed", "Workflow scripts must return a WorkflowResultEnvelope with summary and sections.", false);
676
+ }
693
677
  function toWorkflowRunError(err, wasAborted, abortReason) {
694
678
  if (err instanceof WorkflowEngineRunError) return {
695
679
  code: err.code,