@vellumai/assistant 0.6.3 → 0.6.4

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 (667) hide show
  1. package/ARCHITECTURE.md +273 -10
  2. package/Dockerfile +2 -3
  3. package/bun.lock +5 -13
  4. package/docs/backup-troubleshooting.md +52 -0
  5. package/docs/browser-use-architecture-phase2.md +174 -0
  6. package/docs/stt-provider-onboarding.md +120 -0
  7. package/knip.json +12 -2
  8. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  9. package/node_modules/@vellumai/ces-contracts/package.json +3 -3
  10. package/openapi.yaml +982 -72
  11. package/package.json +4 -6
  12. package/scripts/generate-openapi.ts +0 -1
  13. package/scripts/test.sh +73 -18
  14. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  15. package/src/__tests__/agent-loop.test.ts +123 -0
  16. package/src/__tests__/anthropic-provider.test.ts +263 -10
  17. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  18. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  19. package/src/__tests__/browser-fill-credential.test.ts +11 -0
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/browser-skill-endstate.test.ts +31 -7
  22. package/src/__tests__/btw-routes.test.ts +7 -0
  23. package/src/__tests__/call-controller.test.ts +581 -20
  24. package/src/__tests__/catalog-files.test.ts +138 -0
  25. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  26. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  27. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  28. package/src/__tests__/checker.test.ts +157 -10
  29. package/src/__tests__/clawhub-files.test.ts +347 -0
  30. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  31. package/src/__tests__/config-analysis.test.ts +100 -0
  32. package/src/__tests__/config-schema.test.ts +1013 -66
  33. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  34. package/src/__tests__/config-watcher.test.ts +43 -8
  35. package/src/__tests__/contact-store-user-file.test.ts +512 -0
  36. package/src/__tests__/contacts-write.test.ts +197 -0
  37. package/src/__tests__/context-window-manager.test.ts +88 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +98 -2
  41. package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
  42. package/src/__tests__/conversation-error.test.ts +70 -0
  43. package/src/__tests__/conversation-history-web-search.test.ts +11 -4
  44. package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
  45. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  46. package/src/__tests__/conversation-list-source.test.ts +145 -0
  47. package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  49. package/src/__tests__/conversation-queue.test.ts +901 -60
  50. package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
  51. package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
  52. package/src/__tests__/conversation-skill-tools.test.ts +7 -4
  53. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +89 -18
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  59. package/src/__tests__/credential-health-service.test.ts +352 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  61. package/src/__tests__/credential-vault-unit.test.ts +379 -3
  62. package/src/__tests__/credentials-cli.test.ts +40 -16
  63. package/src/__tests__/cross-provider-web-search.test.ts +146 -35
  64. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  65. package/src/__tests__/device-id.test.ts +112 -0
  66. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  67. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  68. package/src/__tests__/email-html-renderer.test.ts +71 -0
  69. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  70. package/src/__tests__/emit-event-signal.test.ts +71 -0
  71. package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
  72. package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
  73. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  74. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  75. package/src/__tests__/gemini-provider.test.ts +64 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  77. package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
  78. package/src/__tests__/gmail-archive-gate.test.ts +246 -0
  79. package/src/__tests__/gmail-preferences.test.ts +117 -0
  80. package/src/__tests__/headless-browser-interactions.test.ts +43 -0
  81. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  82. package/src/__tests__/headless-browser-navigate.test.ts +142 -5
  83. package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
  84. package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
  85. package/src/__tests__/heartbeat-service.test.ts +70 -17
  86. package/src/__tests__/home-state-routes.test.ts +162 -0
  87. package/src/__tests__/host-bash-proxy.test.ts +0 -5
  88. package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
  89. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
  90. package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
  91. package/src/__tests__/host-cu-proxy.test.ts +0 -5
  92. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  93. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  94. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  95. package/src/__tests__/llm-context-normalization.test.ts +488 -0
  96. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  97. package/src/__tests__/llm-usage-store.test.ts +363 -0
  98. package/src/__tests__/media-stream-output.test.ts +555 -0
  99. package/src/__tests__/media-stream-parser.test.ts +374 -0
  100. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  101. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  102. package/src/__tests__/media-turn-detector.test.ts +440 -0
  103. package/src/__tests__/message-queue.test.ts +125 -0
  104. package/src/__tests__/migration-export-http.test.ts +6 -6
  105. package/src/__tests__/migration-import-commit-http.test.ts +8 -6
  106. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  107. package/src/__tests__/migration-validate-http.test.ts +3 -3
  108. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  109. package/src/__tests__/model-intents.test.ts +2 -2
  110. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  111. package/src/__tests__/oauth-cli.test.ts +2 -0
  112. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  115. package/src/__tests__/oauth-store.test.ts +85 -0
  116. package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
  117. package/src/__tests__/onboarding-template-contract.test.ts +6 -13
  118. package/src/__tests__/openai-provider.test.ts +176 -0
  119. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  120. package/src/__tests__/openai-responses-provider.test.ts +1105 -0
  121. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  122. package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
  123. package/src/__tests__/persona-resolver.test.ts +251 -0
  124. package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
  125. package/src/__tests__/platform.test.ts +92 -1
  126. package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
  127. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  128. package/src/__tests__/pricing.test.ts +174 -0
  129. package/src/__tests__/qdrant-manager.test.ts +29 -8
  130. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  131. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  132. package/src/__tests__/relay-server.test.ts +423 -5
  133. package/src/__tests__/search-skills-unified.test.ts +118 -0
  134. package/src/__tests__/secret-scanner-executor.test.ts +4 -0
  135. package/src/__tests__/secure-keys.test.ts +107 -0
  136. package/src/__tests__/send-endpoint-busy.test.ts +5 -1
  137. package/src/__tests__/sequence-store.test.ts +1 -1
  138. package/src/__tests__/server-history-render.test.ts +49 -0
  139. package/src/__tests__/settings-routes.test.ts +201 -0
  140. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  141. package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
  142. package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
  143. package/src/__tests__/skills.test.ts +5 -2
  144. package/src/__tests__/skillssh-files.test.ts +446 -0
  145. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  146. package/src/__tests__/slack-channel-config.test.ts +564 -1
  147. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  148. package/src/__tests__/stt-stream-session.test.ts +535 -0
  149. package/src/__tests__/system-prompt.test.ts +112 -26
  150. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  151. package/src/__tests__/terminal-tools.test.ts +18 -7
  152. package/src/__tests__/test-preload.ts +18 -0
  153. package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
  154. package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
  155. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  156. package/src/__tests__/tool-executor.test.ts +33 -24
  157. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  158. package/src/__tests__/trust-store.test.ts +7 -1
  159. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  160. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  161. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  162. package/src/__tests__/twilio-routes.test.ts +376 -0
  163. package/src/__tests__/unicode.test.ts +293 -0
  164. package/src/__tests__/update-bulletin-format.test.ts +59 -0
  165. package/src/__tests__/update-bulletin.test.ts +206 -5
  166. package/src/__tests__/usage-routes.test.ts +25 -4
  167. package/src/__tests__/user-reference.test.ts +46 -61
  168. package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
  169. package/src/__tests__/voice-config-update.test.ts +403 -0
  170. package/src/__tests__/voice-quality.test.ts +434 -19
  171. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  172. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  173. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  174. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  175. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  176. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  177. package/src/__tests__/workspace-policy.test.ts +2 -0
  178. package/src/agent/image-optimize.ts +24 -12
  179. package/src/agent/loop.ts +43 -3
  180. package/src/backup/__tests__/backup-key.test.ts +152 -0
  181. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  182. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  183. package/src/backup/__tests__/local-writer.test.ts +218 -0
  184. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  185. package/src/backup/__tests__/paths.test.ts +300 -0
  186. package/src/backup/__tests__/restore.test.ts +498 -0
  187. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  188. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  189. package/src/backup/backup-key.ts +137 -0
  190. package/src/backup/backup-worker.ts +459 -0
  191. package/src/backup/list-snapshots.ts +147 -0
  192. package/src/backup/local-writer.ts +133 -0
  193. package/src/backup/offsite-writer.ts +222 -0
  194. package/src/backup/paths.ts +226 -0
  195. package/src/backup/restore.ts +322 -0
  196. package/src/backup/snapshot-lock.ts +431 -0
  197. package/src/backup/stream-crypt.ts +263 -0
  198. package/src/bundler/package-resolver.ts +4 -0
  199. package/src/calls/audio-store.ts +11 -5
  200. package/src/calls/call-controller.ts +226 -71
  201. package/src/calls/call-domain.ts +9 -0
  202. package/src/calls/call-speech-output.ts +190 -0
  203. package/src/calls/call-transport.ts +77 -0
  204. package/src/calls/media-stream-audio-transcode.ts +173 -0
  205. package/src/calls/media-stream-output.ts +660 -0
  206. package/src/calls/media-stream-parser.ts +300 -0
  207. package/src/calls/media-stream-protocol.ts +166 -0
  208. package/src/calls/media-stream-server.ts +592 -0
  209. package/src/calls/media-stream-stt-session.ts +460 -0
  210. package/src/calls/media-turn-detector.ts +230 -0
  211. package/src/calls/relay-server.ts +90 -75
  212. package/src/calls/resolve-call-tts-provider.ts +136 -0
  213. package/src/calls/telephony-stt-routing.ts +145 -0
  214. package/src/calls/tts-call-strategy.ts +161 -0
  215. package/src/calls/tts-text-sanitizer.ts +32 -16
  216. package/src/calls/twilio-routes.ts +281 -17
  217. package/src/calls/voice-quality.ts +78 -35
  218. package/src/calls/voice-session-bridge.ts +8 -1
  219. package/src/channels/types.ts +16 -0
  220. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  221. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  222. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  223. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  224. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  225. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  226. package/src/cli/commands/__tests__/email-list.test.ts +22 -4
  227. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  228. package/src/cli/commands/__tests__/email-send.test.ts +37 -4
  229. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  230. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  231. package/src/cli/commands/backup.ts +993 -0
  232. package/src/cli/commands/conversations.ts +77 -0
  233. package/src/cli/commands/credentials.ts +0 -1
  234. package/src/cli/commands/domain.ts +210 -0
  235. package/src/cli/commands/email.ts +255 -3
  236. package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
  237. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
  238. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
  239. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
  240. package/src/cli/commands/oauth/mode.ts +12 -3
  241. package/src/cli/commands/oauth/providers.ts +15 -0
  242. package/src/cli/commands/oauth/shared.ts +2 -1
  243. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
  244. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
  245. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  246. package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
  247. package/src/cli/program.ts +30 -4
  248. package/src/config/__tests__/backup-schema.test.ts +134 -0
  249. package/src/config/assistant-feature-flags.ts +61 -62
  250. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
  251. package/src/config/bundled-skills/browser/SKILL.md +30 -5
  252. package/src/config/bundled-skills/browser/TOOLS.json +123 -0
  253. package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
  254. package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
  255. package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
  256. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +2 -2
  258. package/src/config/bundled-skills/gmail/SKILL.md +53 -7
  259. package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
  260. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
  261. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
  262. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
  263. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
  264. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
  265. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
  266. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  267. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  268. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  269. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  270. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  271. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  272. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  273. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  274. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  275. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  276. package/src/config/bundled-skills/outlook/SKILL.md +2 -2
  277. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
  278. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  279. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
  280. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  281. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  282. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  283. package/src/config/bundled-skills/slack/SKILL.md +1 -0
  284. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  285. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  286. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  287. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  288. package/src/config/bundled-tool-registry.ts +8 -0
  289. package/src/config/env-registry.ts +24 -0
  290. package/src/config/env.ts +34 -10
  291. package/src/config/feature-flag-registry.json +46 -14
  292. package/src/config/loader.ts +26 -12
  293. package/src/config/schema.ts +35 -10
  294. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  295. package/src/config/schemas/analysis.ts +51 -0
  296. package/src/config/schemas/backup.ts +72 -0
  297. package/src/config/schemas/calls.ts +1 -26
  298. package/src/config/schemas/elevenlabs.ts +0 -59
  299. package/src/config/schemas/filing.ts +47 -7
  300. package/src/config/schemas/heartbeat.ts +27 -5
  301. package/src/config/schemas/host-browser.ts +47 -1
  302. package/src/config/schemas/inference.ts +1 -1
  303. package/src/config/schemas/memory-lifecycle.ts +14 -2
  304. package/src/config/schemas/services.ts +44 -0
  305. package/src/config/schemas/stt.ts +59 -0
  306. package/src/config/schemas/tts.ts +230 -0
  307. package/src/config/schemas/updates.ts +14 -0
  308. package/src/config/skills.ts +4 -0
  309. package/src/config/types.ts +4 -0
  310. package/src/contacts/contact-store.ts +56 -11
  311. package/src/contacts/contacts-write.ts +38 -1
  312. package/src/context/post-turn-tool-result-truncation.ts +3 -2
  313. package/src/context/tool-result-truncation.ts +2 -1
  314. package/src/context/window-manager.ts +45 -12
  315. package/src/credential-execution/executable-discovery.ts +12 -2
  316. package/src/credential-execution/process-manager.ts +33 -2
  317. package/src/credential-health/credential-health-service.ts +366 -0
  318. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  319. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  320. package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
  321. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  322. package/src/daemon/config-watcher.ts +99 -5
  323. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  324. package/src/daemon/conversation-agent-loop.ts +101 -24
  325. package/src/daemon/conversation-error.ts +11 -0
  326. package/src/daemon/conversation-history.ts +40 -6
  327. package/src/daemon/conversation-launch.ts +220 -0
  328. package/src/daemon/conversation-lifecycle.ts +59 -9
  329. package/src/daemon/conversation-messaging.ts +37 -3
  330. package/src/daemon/conversation-notifiers.ts +5 -0
  331. package/src/daemon/conversation-process.ts +581 -19
  332. package/src/daemon/conversation-queue-manager.ts +24 -0
  333. package/src/daemon/conversation-runtime-assembly.ts +11 -1
  334. package/src/daemon/conversation-slash.ts +36 -0
  335. package/src/daemon/conversation-surfaces.ts +94 -4
  336. package/src/daemon/conversation-tool-setup.ts +25 -0
  337. package/src/daemon/conversation-usage.ts +7 -4
  338. package/src/daemon/conversation.ts +86 -28
  339. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  340. package/src/daemon/handlers/conversations.ts +4 -1
  341. package/src/daemon/handlers/shared.ts +22 -0
  342. package/src/daemon/handlers/skills.ts +321 -77
  343. package/src/daemon/host-browser-proxy.ts +2 -1
  344. package/src/daemon/lifecycle.ts +122 -25
  345. package/src/daemon/message-protocol.ts +6 -0
  346. package/src/daemon/message-types/conversations.ts +34 -1
  347. package/src/daemon/message-types/home.ts +40 -0
  348. package/src/daemon/message-types/meet.ts +143 -0
  349. package/src/daemon/message-types/messages.ts +14 -0
  350. package/src/daemon/message-types/schedules.ts +34 -2
  351. package/src/daemon/message-types/skills.ts +16 -0
  352. package/src/daemon/message-types/surfaces.ts +2 -0
  353. package/src/daemon/server.ts +347 -2
  354. package/src/daemon/shutdown-handlers.ts +32 -4
  355. package/src/daemon/shutdown-registry.ts +40 -0
  356. package/src/daemon/tool-side-effects.ts +9 -0
  357. package/src/email/html-renderer.ts +76 -0
  358. package/src/heartbeat/heartbeat-service.ts +93 -7
  359. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  360. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  361. package/src/home/__tests__/feed-scheduler.test.ts +194 -0
  362. package/src/home/__tests__/feed-types.test.ts +275 -0
  363. package/src/home/__tests__/feed-writer.test.ts +688 -0
  364. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  365. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  366. package/src/home/__tests__/progress-formula.test.ts +213 -0
  367. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  368. package/src/home/__tests__/rollup-producer.test.ts +398 -0
  369. package/src/home/assistant-feed-authoring.ts +124 -0
  370. package/src/home/emit-feed-event.ts +158 -0
  371. package/src/home/feed-scheduler.ts +247 -0
  372. package/src/home/feed-types.ts +181 -0
  373. package/src/home/feed-writer.ts +469 -0
  374. package/src/home/platform-gmail-digest.ts +163 -0
  375. package/src/home/progress-formula.ts +86 -0
  376. package/src/home/relationship-state-writer.ts +824 -0
  377. package/src/home/relationship-state.ts +143 -0
  378. package/src/home/rollup-producer.ts +384 -0
  379. package/src/hooks/runner.ts +7 -0
  380. package/src/inbound/platform-callback-registration.ts +12 -3
  381. package/src/inbound/public-ingress-urls.ts +12 -0
  382. package/src/instrument.ts +1 -1
  383. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  384. package/src/ipc/cli-client.ts +151 -0
  385. package/src/ipc/cli-server.ts +234 -0
  386. package/src/ipc/gateway-client.ts +180 -0
  387. package/src/ipc/routes/index.ts +5 -0
  388. package/src/ipc/routes/wake-conversation.ts +19 -0
  389. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  390. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  391. package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
  392. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  393. package/src/memory/app-store.ts +1 -1
  394. package/src/memory/attachments-store.ts +70 -0
  395. package/src/memory/auto-analysis-enqueue.ts +127 -0
  396. package/src/memory/auto-analysis-guard.ts +27 -0
  397. package/src/memory/cleanup-schedule-state.ts +37 -0
  398. package/src/memory/conversation-analyze-job.ts +73 -0
  399. package/src/memory/conversation-crud.ts +99 -0
  400. package/src/memory/conversation-disk-view.ts +7 -0
  401. package/src/memory/conversation-group-migration.ts +34 -2
  402. package/src/memory/conversation-queries.ts +6 -5
  403. package/src/memory/db-init.ts +6 -0
  404. package/src/memory/db-maintenance.ts +108 -0
  405. package/src/memory/db.ts +1 -0
  406. package/src/memory/graph/conversation-graph-memory.ts +15 -0
  407. package/src/memory/graph/extraction.test.ts +23 -0
  408. package/src/memory/graph/extraction.ts +8 -0
  409. package/src/memory/graph/retriever.ts +27 -18
  410. package/src/memory/graph/scoring.test.ts +186 -0
  411. package/src/memory/graph/scoring.ts +31 -1
  412. package/src/memory/graph/tools.ts +1 -1
  413. package/src/memory/group-crud.ts +6 -1
  414. package/src/memory/indexer.ts +95 -16
  415. package/src/memory/job-handlers/cleanup.ts +11 -8
  416. package/src/memory/job-handlers/conversation-starters.ts +16 -10
  417. package/src/memory/jobs-store.ts +64 -4
  418. package/src/memory/jobs-worker.ts +22 -9
  419. package/src/memory/llm-usage-store.ts +92 -56
  420. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  421. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  422. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  423. package/src/memory/migrations/index.ts +6 -0
  424. package/src/memory/migrations/registry.ts +8 -0
  425. package/src/memory/qdrant-manager.ts +43 -16
  426. package/src/memory/schema/conversations.ts +2 -0
  427. package/src/memory/schema/oauth.ts +3 -0
  428. package/src/memory/usage-buckets.ts +396 -0
  429. package/src/messaging/providers/gmail/client.ts +57 -6
  430. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  431. package/src/messaging/providers/slack/adapter.ts +143 -38
  432. package/src/messaging/providers/slack/client.ts +16 -0
  433. package/src/messaging/providers/slack/types.ts +4 -0
  434. package/src/notifications/decision-engine.ts +3 -3
  435. package/src/notifications/signal.ts +5 -0
  436. package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
  437. package/src/oauth/byo-connection.test.ts +18 -1
  438. package/src/oauth/byo-connection.ts +3 -1
  439. package/src/oauth/connect-orchestrator.ts +2 -0
  440. package/src/oauth/connection-resolver.ts +6 -2
  441. package/src/oauth/connection.ts +2 -0
  442. package/src/oauth/oauth-store.ts +9 -0
  443. package/src/oauth/platform-connection.test.ts +98 -0
  444. package/src/oauth/platform-connection.ts +52 -31
  445. package/src/oauth/seed-providers.ts +7 -0
  446. package/src/permissions/checker.ts +16 -6
  447. package/src/permissions/defaults.ts +49 -1
  448. package/src/permissions/trust-store.ts +3 -3
  449. package/src/permissions/workspace-policy.ts +3 -0
  450. package/src/platform/client.test.ts +10 -0
  451. package/src/platform/sync-identity.ts +129 -0
  452. package/src/prompts/persona-resolver.ts +126 -2
  453. package/src/prompts/system-prompt.ts +59 -18
  454. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  455. package/src/prompts/templates/SOUL.md +3 -1
  456. package/src/prompts/templates/UPDATES.md +12 -0
  457. package/src/prompts/templates/channels/slack.md +20 -0
  458. package/src/prompts/update-bulletin-format.ts +26 -9
  459. package/src/prompts/update-bulletin.ts +34 -23
  460. package/src/prompts/user-reference.ts +20 -17
  461. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  462. package/src/providers/anthropic/client.ts +157 -61
  463. package/src/providers/fireworks/client.ts +2 -2
  464. package/src/providers/gemini/client.ts +9 -1
  465. package/src/providers/model-catalog.ts +6 -0
  466. package/src/providers/model-intents.ts +4 -4
  467. package/src/providers/ollama/client.ts +2 -2
  468. package/src/providers/openai/chat-completions-provider.ts +474 -0
  469. package/src/providers/openai/client.ts +25 -440
  470. package/src/providers/openai/responses-provider.ts +502 -0
  471. package/src/providers/openrouter/client.ts +101 -4
  472. package/src/providers/provider-secret-catalog.ts +139 -0
  473. package/src/providers/registry.ts +2 -2
  474. package/src/providers/retry.ts +14 -3
  475. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  476. package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
  477. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  478. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  479. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  480. package/src/providers/speech-to-text/deepgram.ts +115 -0
  481. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  482. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  483. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  484. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  485. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  486. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  487. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  488. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  489. package/src/providers/speech-to-text/provider-catalog.ts +306 -0
  490. package/src/providers/speech-to-text/resolve.ts +386 -6
  491. package/src/providers/types.ts +9 -0
  492. package/src/runtime/AGENTS.md +43 -1
  493. package/src/runtime/__tests__/agent-wake.test.ts +831 -0
  494. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  495. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  496. package/src/runtime/agent-wake.ts +512 -0
  497. package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
  498. package/src/runtime/auth/route-policy.ts +30 -5
  499. package/src/runtime/auth/token-service.ts +56 -1
  500. package/src/runtime/btw-sidechain.ts +2 -0
  501. package/src/runtime/capability-tokens.ts +10 -10
  502. package/src/runtime/channel-invite-transport.ts +1 -1
  503. package/src/runtime/channel-invite-transports/email.ts +14 -6
  504. package/src/runtime/channel-readiness-service.ts +12 -22
  505. package/src/runtime/chrome-extension-registry.ts +38 -2
  506. package/src/runtime/http-server.ts +395 -10
  507. package/src/runtime/http-types.ts +6 -2
  508. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
  509. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  510. package/src/runtime/migrations/migration-transport.ts +1 -0
  511. package/src/runtime/migrations/migration-wizard.ts +1 -0
  512. package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
  513. package/src/runtime/migrations/vbundle-importer.ts +34 -0
  514. package/src/runtime/pending-interactions.ts +0 -11
  515. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  516. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
  517. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
  518. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  519. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  520. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  521. package/src/runtime/routes/app-management-routes.ts +12 -18
  522. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  523. package/src/runtime/routes/attachment-routes.ts +216 -17
  524. package/src/runtime/routes/backup-routes.ts +519 -0
  525. package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
  526. package/src/runtime/routes/btw-routes.ts +8 -6
  527. package/src/runtime/routes/contact-routes.test.ts +298 -0
  528. package/src/runtime/routes/contact-routes.ts +132 -5
  529. package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
  530. package/src/runtime/routes/conversation-management-routes.ts +115 -0
  531. package/src/runtime/routes/conversation-routes.ts +367 -146
  532. package/src/runtime/routes/filing-routes.ts +93 -0
  533. package/src/runtime/routes/home-feed-routes.ts +334 -0
  534. package/src/runtime/routes/home-state-routes.ts +138 -0
  535. package/src/runtime/routes/host-browser-routes.ts +3 -14
  536. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  537. package/src/runtime/routes/identity-routes.ts +3 -17
  538. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  539. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  540. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  541. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  542. package/src/runtime/routes/integrations/slack/channel.ts +11 -3
  543. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  544. package/src/runtime/routes/llm-context-normalization.ts +303 -0
  545. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  546. package/src/runtime/routes/migration-routes.ts +40 -5
  547. package/src/runtime/routes/settings-routes.ts +22 -5
  548. package/src/runtime/routes/skills-routes.ts +76 -7
  549. package/src/runtime/routes/stt-routes.ts +233 -0
  550. package/src/runtime/routes/surface-action-routes.ts +41 -2
  551. package/src/runtime/routes/tts-routes.ts +108 -24
  552. package/src/runtime/routes/usage-routes.ts +30 -2
  553. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  554. package/src/runtime/routes/user-routes.ts +13 -1
  555. package/src/runtime/routes/work-items-routes.ts +8 -1
  556. package/src/runtime/runtime-mode.ts +33 -0
  557. package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
  558. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  559. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  560. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  561. package/src/runtime/services/analyze-conversation.ts +344 -0
  562. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  563. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  564. package/src/runtime/skill-route-registry.ts +49 -0
  565. package/src/runtime/slack-block-formatting.ts +437 -10
  566. package/src/schedule/scheduler.ts +50 -0
  567. package/src/security/oauth2.ts +26 -4
  568. package/src/security/secure-keys.ts +25 -2
  569. package/src/security/token-manager.ts +8 -0
  570. package/src/sequence/engine.ts +23 -0
  571. package/src/sequence/types.ts +1 -1
  572. package/src/skills/catalog-files.ts +64 -2
  573. package/src/skills/category-inference.ts +122 -0
  574. package/src/skills/clawhub-files.ts +213 -0
  575. package/src/skills/clawhub.ts +84 -23
  576. package/src/skills/skill-file-provider.ts +40 -0
  577. package/src/skills/skillssh-files.ts +395 -0
  578. package/src/skills/skillssh-registry.ts +4 -4
  579. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
  580. package/src/stt/__tests__/types.test.ts +89 -0
  581. package/src/stt/daemon-batch-transcriber.ts +195 -0
  582. package/src/stt/stt-stream-session.ts +499 -0
  583. package/src/stt/types.ts +330 -0
  584. package/src/stt/wav-encoder.test.ts +373 -0
  585. package/src/stt/wav-encoder.ts +175 -0
  586. package/src/subagent/manager.ts +38 -14
  587. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  588. package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
  589. package/src/tools/browser/browser-execution.ts +1163 -23
  590. package/src/tools/browser/browser-manager.ts +45 -0
  591. package/src/tools/browser/browser-mode-constants.ts +12 -0
  592. package/src/tools/browser/browser-mode.ts +92 -0
  593. package/src/tools/browser/browser-status-constants.ts +33 -0
  594. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
  595. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
  596. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
  597. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
  598. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
  599. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
  600. package/src/tools/browser/cdp-client/errors.ts +15 -0
  601. package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
  602. package/src/tools/browser/cdp-client/factory.ts +797 -87
  603. package/src/tools/browser/cdp-client/index.ts +16 -2
  604. package/src/tools/browser/cdp-client/types.ts +68 -0
  605. package/src/tools/credentials/vault.ts +35 -6
  606. package/src/tools/network/web-fetch.ts +5 -2
  607. package/src/tools/network/web-search.ts +5 -2
  608. package/src/tools/shared/shell-output.ts +3 -1
  609. package/src/tools/side-effects.ts +2 -0
  610. package/src/tools/skills/sandbox-runner.ts +3 -2
  611. package/src/tools/terminal/safe-env.ts +10 -2
  612. package/src/tools/terminal/shell.ts +15 -4
  613. package/src/tools/tool-manifest.ts +21 -0
  614. package/src/tools/types.ts +17 -0
  615. package/src/tools/ui-surface/definitions.ts +6 -1
  616. package/src/tts/__tests__/provider-adapters.test.ts +834 -0
  617. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  618. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  619. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  620. package/src/tts/provider-catalog.ts +201 -0
  621. package/src/tts/provider-registry.ts +73 -0
  622. package/src/tts/providers/deepgram-provider.ts +219 -0
  623. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  624. package/src/tts/providers/fish-audio-provider.ts +183 -0
  625. package/src/tts/providers/index.ts +42 -0
  626. package/src/tts/providers/register-builtins.ts +130 -0
  627. package/src/tts/synthesize-text.ts +110 -0
  628. package/src/tts/tts-config-resolver.ts +78 -0
  629. package/src/tts/types.ts +153 -0
  630. package/src/types/onboarding-context.ts +7 -0
  631. package/src/util/abort-reasons.ts +58 -0
  632. package/src/util/device-id.ts +32 -16
  633. package/src/util/errors.ts +9 -1
  634. package/src/util/platform.ts +54 -10
  635. package/src/util/pricing.ts +66 -3
  636. package/src/util/spawn.ts +1 -1
  637. package/src/util/truncate.ts +4 -2
  638. package/src/util/unicode.ts +201 -0
  639. package/src/version.ts +19 -24
  640. package/src/watcher/engine.ts +23 -0
  641. package/src/watcher/watcher-store.ts +31 -0
  642. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  643. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  644. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  645. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  646. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  647. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  648. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  649. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  650. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  651. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  652. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  653. package/src/workspace/migrations/registry.ts +16 -0
  654. package/src/workspace/top-level-renderer.ts +13 -1
  655. package/src/workspace/turn-commit.ts +31 -0
  656. package/src/__tests__/email-cli.test.ts +0 -297
  657. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  658. package/src/cli/commands/browser-relay.ts +0 -466
  659. package/src/email/guardrails.ts +0 -221
  660. package/src/email/provider.ts +0 -117
  661. package/src/email/providers/agentmail.ts +0 -361
  662. package/src/email/providers/index.ts +0 -65
  663. package/src/email/service.ts +0 -384
  664. package/src/email/types.ts +0 -126
  665. package/src/prompts/templates/USER.md +0 -13
  666. package/src/providers/speech-to-text/types.ts +0 -17
  667. package/src/runtime/routes/browser-cdp-routes.ts +0 -229
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Runtime TTS provider registry.
3
+ *
4
+ * Providers self-register at startup via `registerTtsProvider`. Callers
5
+ * resolve a provider by ID with `getTtsProvider`, which throws an explicit
6
+ * error for unknown IDs so misconfiguration surfaces immediately rather
7
+ * than producing silent fallback behavior.
8
+ */
9
+
10
+ import type { TtsProvider, TtsProviderId } from "./types.js";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Internal state
14
+ // ---------------------------------------------------------------------------
15
+
16
+ /** Insertion-ordered map of registered providers. */
17
+ const providers = new Map<TtsProviderId, TtsProvider>();
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Public API
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /**
24
+ * Register a TTS provider.
25
+ *
26
+ * @throws if a provider with the same ID is already registered — this
27
+ * prevents silent overwrites caused by duplicate registrations.
28
+ */
29
+ export function registerTtsProvider(provider: TtsProvider): void {
30
+ if (providers.has(provider.id)) {
31
+ throw new Error(
32
+ `TTS provider "${provider.id}" is already registered. ` +
33
+ "Duplicate registrations are not allowed.",
34
+ );
35
+ }
36
+ providers.set(provider.id, provider);
37
+ }
38
+
39
+ /**
40
+ * Look up a registered TTS provider by ID.
41
+ *
42
+ * @throws if no provider with the given ID has been registered.
43
+ */
44
+ export function getTtsProvider(id: TtsProviderId): TtsProvider {
45
+ const provider = providers.get(id);
46
+ if (!provider) {
47
+ const known = [...providers.keys()];
48
+ const knownList =
49
+ known.length > 0 ? ` Known providers: ${known.join(", ")}` : "";
50
+ throw new Error(`Unknown TTS provider "${id}".${knownList}`);
51
+ }
52
+ return provider;
53
+ }
54
+
55
+ /**
56
+ * List all registered providers in deterministic (registration) order.
57
+ */
58
+ export function listTtsProviders(): TtsProvider[] {
59
+ return [...providers.values()];
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Test helpers
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /**
67
+ * Clear all registered providers.
68
+ *
69
+ * **Test-only** — must not be called in production code.
70
+ */
71
+ export function _resetTtsProviderRegistry(): void {
72
+ providers.clear();
73
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Deepgram TTS provider adapter.
3
+ *
4
+ * Wraps the Deepgram REST text-to-speech API (`/v1/speak`) behind the uniform
5
+ * {@link TtsProvider} interface. Reads the API key from the secure credential
6
+ * store using the shared `deepgram` bare key (shared with STT) and the model
7
+ * configuration from `services.tts.providers.deepgram` config section.
8
+ */
9
+
10
+ import { getConfig } from "../../config/loader.js";
11
+ import type { TtsDeepgramProviderConfig } from "../../config/schemas/tts.js";
12
+ import { getProviderKeyAsync } from "../../security/secure-keys.js";
13
+ import { getLogger } from "../../util/logger.js";
14
+ import type {
15
+ TtsProvider,
16
+ TtsProviderCapabilities,
17
+ TtsSynthesisRequest,
18
+ TtsSynthesisResult,
19
+ } from "../types.js";
20
+
21
+ const log = getLogger("tts:deepgram");
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Error types
25
+ // ---------------------------------------------------------------------------
26
+
27
+ export type DeepgramTtsErrorCode =
28
+ | "DEEPGRAM_TTS_NO_API_KEY"
29
+ | "DEEPGRAM_TTS_HTTP_ERROR"
30
+ | "DEEPGRAM_TTS_EMPTY_RESPONSE"
31
+ | "DEEPGRAM_TTS_REQUEST_FAILED";
32
+
33
+ export class DeepgramTtsError extends Error {
34
+ readonly code: DeepgramTtsErrorCode;
35
+ readonly statusCode?: number;
36
+
37
+ constructor(
38
+ code: DeepgramTtsErrorCode,
39
+ message: string,
40
+ statusCode?: number,
41
+ ) {
42
+ super(message);
43
+ this.name = "DeepgramTtsError";
44
+ this.code = code;
45
+ this.statusCode = statusCode;
46
+ }
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Constants
51
+ // ---------------------------------------------------------------------------
52
+
53
+ const DEEPGRAM_API_BASE = "https://api.deepgram.com";
54
+
55
+ /** Map from Deepgram encoding names to MIME content types. */
56
+ const FORMAT_CONTENT_TYPE: Record<string, string> = {
57
+ mp3: "audio/mpeg",
58
+ wav: "audio/wav",
59
+ opus: "audio/opus",
60
+ linear16: "audio/pcm",
61
+ };
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Provider implementation
65
+ // ---------------------------------------------------------------------------
66
+
67
+ /** Parameters for Deepgram's `/v1/speak` encoding query string. */
68
+ interface DeepgramOutputParams {
69
+ /** Deepgram encoding name (e.g. `mp3`, `linear16`, `opus`). */
70
+ encoding: string;
71
+ /** Container override (`wav` or `none`). Omitted lets Deepgram choose. */
72
+ container?: string;
73
+ /** Sample rate in Hz. Required for raw PCM to avoid Deepgram's 24 kHz default. */
74
+ sample_rate?: number;
75
+ /** Content-type key for the FORMAT_CONTENT_TYPE lookup. */
76
+ contentTypeKey: string;
77
+ }
78
+
79
+ /**
80
+ * Resolve the Deepgram output encoding, container, and sample rate based on
81
+ * the synthesis request and provider config.
82
+ *
83
+ * **PCM path** (`outputFormat: "pcm"`):
84
+ * The media-stream transport needs raw headerless PCM for mu-law transcoding.
85
+ * We request `encoding=linear16&container=none&sample_rate=16000` — 16-bit
86
+ * signed little-endian at 16 kHz with no WAV header. This matches the
87
+ * ElevenLabs `pcm_16000` convention and the downstream
88
+ * `audioBufferToFrames` expectation (16 kHz -> 8 kHz downsample).
89
+ *
90
+ * **WAV path** (`config.format === "wav"`):
91
+ * Deepgram treats WAV as a container, not an encoding. We translate to
92
+ * `encoding=linear16&container=wav` so the API returns a valid WAV file.
93
+ *
94
+ * **Other formats** (mp3, opus):
95
+ * Passed through directly as encoding values.
96
+ */
97
+ function resolveOutputParams(
98
+ request: TtsSynthesisRequest,
99
+ config: TtsDeepgramProviderConfig,
100
+ ): DeepgramOutputParams {
101
+ if (request.outputFormat === "pcm") {
102
+ return {
103
+ encoding: "linear16",
104
+ container: "none",
105
+ sample_rate: 16_000,
106
+ contentTypeKey: "linear16",
107
+ };
108
+ }
109
+
110
+ if (config.format === "wav") {
111
+ return {
112
+ encoding: "linear16",
113
+ container: "wav",
114
+ contentTypeKey: "wav",
115
+ };
116
+ }
117
+
118
+ return { encoding: config.format, contentTypeKey: config.format };
119
+ }
120
+
121
+ export function createDeepgramProvider(): TtsProvider {
122
+ const capabilities: TtsProviderCapabilities = {
123
+ supportsStreaming: false,
124
+ supportedFormats: ["mp3", "wav", "opus"],
125
+ };
126
+
127
+ return {
128
+ id: "deepgram",
129
+ capabilities,
130
+
131
+ async synthesize(
132
+ request: TtsSynthesisRequest,
133
+ ): Promise<TtsSynthesisResult> {
134
+ const apiKey = await getProviderKeyAsync("deepgram");
135
+ if (!apiKey) {
136
+ throw new DeepgramTtsError(
137
+ "DEEPGRAM_TTS_NO_API_KEY",
138
+ "Deepgram API key not configured. " +
139
+ "Add it in Settings → Voice or via: assistant keys set deepgram <key>",
140
+ );
141
+ }
142
+
143
+ const config = getConfig().services.tts.providers.deepgram;
144
+ const outputParams = resolveOutputParams(request, config);
145
+ const model = config.model;
146
+
147
+ const params = new URLSearchParams({
148
+ model,
149
+ encoding: outputParams.encoding,
150
+ });
151
+ if (outputParams.container) {
152
+ params.set("container", outputParams.container);
153
+ }
154
+ if (outputParams.sample_rate != null) {
155
+ params.set("sample_rate", String(outputParams.sample_rate));
156
+ }
157
+ const url = `${DEEPGRAM_API_BASE}/v1/speak?${params.toString()}`;
158
+
159
+ log.info(
160
+ {
161
+ model,
162
+ encoding: outputParams.encoding,
163
+ container: outputParams.container,
164
+ textLength: request.text.length,
165
+ },
166
+ "Starting Deepgram TTS synthesis",
167
+ );
168
+
169
+ let response: Response;
170
+ try {
171
+ response = await fetch(url, {
172
+ method: "POST",
173
+ headers: {
174
+ "Content-Type": "application/json",
175
+ Authorization: `Token ${apiKey}`,
176
+ },
177
+ body: JSON.stringify({ text: request.text }),
178
+ signal: request.signal,
179
+ });
180
+ } catch (err) {
181
+ if (err instanceof Error && err.name === "AbortError") throw err;
182
+ throw new DeepgramTtsError(
183
+ "DEEPGRAM_TTS_REQUEST_FAILED",
184
+ `Deepgram TTS request failed: ${err instanceof Error ? err.message : String(err)}`,
185
+ );
186
+ }
187
+
188
+ if (!response.ok) {
189
+ const errorText = await response.text().catch(() => "");
190
+ throw new DeepgramTtsError(
191
+ "DEEPGRAM_TTS_HTTP_ERROR",
192
+ `Deepgram TTS returned ${response.status}: ${errorText}`,
193
+ response.status,
194
+ );
195
+ }
196
+
197
+ const arrayBuffer = await response.arrayBuffer();
198
+ if (arrayBuffer.byteLength === 0) {
199
+ throw new DeepgramTtsError(
200
+ "DEEPGRAM_TTS_EMPTY_RESPONSE",
201
+ "Deepgram TTS returned an empty audio response",
202
+ );
203
+ }
204
+
205
+ const contentType =
206
+ FORMAT_CONTENT_TYPE[outputParams.contentTypeKey] ?? "audio/mpeg";
207
+
208
+ log.debug(
209
+ { bytes: arrayBuffer.byteLength },
210
+ "Deepgram TTS synthesis complete",
211
+ );
212
+
213
+ return {
214
+ audio: Buffer.from(arrayBuffer),
215
+ contentType,
216
+ };
217
+ },
218
+ };
219
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * ElevenLabs TTS provider adapter.
3
+ *
4
+ * Wraps the ElevenLabs REST text-to-speech API (`/v1/text-to-speech/:voiceId`)
5
+ * behind the uniform {@link TtsProvider} interface. Reads the API key from the
6
+ * secure credential store (`elevenlabs/api_key`) and the voice configuration
7
+ * from `services.tts.providers.elevenlabs` config section.
8
+ */
9
+
10
+ import { getConfig } from "../../config/loader.js";
11
+ import { DEFAULT_ELEVENLABS_VOICE_ID } from "../../config/schemas/elevenlabs.js";
12
+ import type { TtsElevenLabsProviderConfig } from "../../config/schemas/tts.js";
13
+ import { credentialKey } from "../../security/credential-key.js";
14
+ import { getSecureKeyAsync } from "../../security/secure-keys.js";
15
+ import { getLogger } from "../../util/logger.js";
16
+ import type {
17
+ TtsProvider,
18
+ TtsProviderCapabilities,
19
+ TtsSynthesisRequest,
20
+ TtsSynthesisResult,
21
+ } from "../types.js";
22
+
23
+ const log = getLogger("tts:elevenlabs");
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Error types
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export type ElevenLabsTtsErrorCode =
30
+ | "ELEVENLABS_TTS_NO_API_KEY"
31
+ | "ELEVENLABS_TTS_NO_VOICE_ID"
32
+ | "ELEVENLABS_TTS_HTTP_ERROR"
33
+ | "ELEVENLABS_TTS_EMPTY_RESPONSE"
34
+ | "ELEVENLABS_TTS_REQUEST_FAILED";
35
+
36
+ export class ElevenLabsTtsError extends Error {
37
+ readonly code: ElevenLabsTtsErrorCode;
38
+ readonly statusCode?: number;
39
+
40
+ constructor(
41
+ code: ElevenLabsTtsErrorCode,
42
+ message: string,
43
+ statusCode?: number,
44
+ ) {
45
+ super(message);
46
+ this.name = "ElevenLabsTtsError";
47
+ this.code = code;
48
+ this.statusCode = statusCode;
49
+ }
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Constants
54
+ // ---------------------------------------------------------------------------
55
+
56
+ const ELEVENLABS_API_BASE = "https://api.elevenlabs.io";
57
+
58
+ /** Map from request output format identifiers to MIME content types. */
59
+ const FORMAT_CONTENT_TYPE: Record<string, string> = {
60
+ mp3_44100_128: "audio/mpeg",
61
+ mp3_22050_32: "audio/mpeg",
62
+ pcm_16000: "audio/pcm",
63
+ pcm_22050: "audio/pcm",
64
+ pcm_24000: "audio/pcm",
65
+ pcm_44100: "audio/pcm",
66
+ ulaw_8000: "audio/basic",
67
+ };
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Provider implementation
71
+ // ---------------------------------------------------------------------------
72
+
73
+ /**
74
+ * Resolve the effective voice ID for a synthesis request.
75
+ *
76
+ * Priority: request-level `voiceId` > config `voiceId` > built-in default.
77
+ */
78
+ function resolveVoiceId(
79
+ request: TtsSynthesisRequest,
80
+ config: TtsElevenLabsProviderConfig,
81
+ ): string {
82
+ const voiceId =
83
+ request.voiceId?.trim() || config.voiceId || DEFAULT_ELEVENLABS_VOICE_ID;
84
+ if (!voiceId) {
85
+ throw new ElevenLabsTtsError(
86
+ "ELEVENLABS_TTS_NO_VOICE_ID",
87
+ "No voice ID provided and no default configured. " +
88
+ "Set services.tts.providers.elevenlabs.voiceId in config or pass voiceId in the request.",
89
+ );
90
+ }
91
+ return voiceId;
92
+ }
93
+
94
+ /**
95
+ * Choose the ElevenLabs output format based on the use case and optional
96
+ * format hint.
97
+ *
98
+ * When the caller requests `outputFormat: "pcm"` (e.g. the media-stream
99
+ * transport which needs raw PCM for mu-law transcoding), we use `pcm_16000`
100
+ * — 16-bit signed little-endian at 16 kHz. The media-stream transport's
101
+ * `audioBufferToFrames` handles the 16 kHz -> 8 kHz downsample.
102
+ *
103
+ * Otherwise:
104
+ * - Phone calls benefit from lower-latency, smaller payloads (mp3 at 22050/32).
105
+ * - Message playback uses higher quality (mp3 at 44100/128).
106
+ */
107
+ function resolveOutputFormat(request: TtsSynthesisRequest): string {
108
+ if (request.outputFormat === "pcm") {
109
+ return "pcm_16000";
110
+ }
111
+ return request.useCase === "phone-call" ? "mp3_22050_32" : "mp3_44100_128";
112
+ }
113
+
114
+ export function createElevenLabsProvider(): TtsProvider {
115
+ const capabilities: TtsProviderCapabilities = {
116
+ supportsStreaming: false,
117
+ supportedFormats: ["mp3", "pcm"],
118
+ };
119
+
120
+ return {
121
+ id: "elevenlabs",
122
+ capabilities,
123
+
124
+ async synthesize(
125
+ request: TtsSynthesisRequest,
126
+ ): Promise<TtsSynthesisResult> {
127
+ const apiKey = await getSecureKeyAsync(
128
+ credentialKey("elevenlabs", "api_key"),
129
+ );
130
+ if (!apiKey) {
131
+ throw new ElevenLabsTtsError(
132
+ "ELEVENLABS_TTS_NO_API_KEY",
133
+ "ElevenLabs API key not configured. " +
134
+ "Add it in Settings → Voice or via: assistant credentials set --service elevenlabs --field api_key <key>",
135
+ );
136
+ }
137
+
138
+ const config = getConfig().services.tts.providers.elevenlabs;
139
+ const voiceId = resolveVoiceId(request, config);
140
+ const outputFormat = resolveOutputFormat(request);
141
+
142
+ const url = `${ELEVENLABS_API_BASE}/v1/text-to-speech/${voiceId}`;
143
+
144
+ const body: Record<string, unknown> = {
145
+ text: request.text,
146
+ model_id: config.voiceModelId?.trim() || "eleven_multilingual_v2",
147
+ voice_settings: {
148
+ stability: config.stability,
149
+ similarity_boost: config.similarityBoost,
150
+ speed: config.speed,
151
+ },
152
+ };
153
+
154
+ log.info(
155
+ { voiceId, outputFormat, textLength: request.text.length },
156
+ "Starting ElevenLabs TTS synthesis",
157
+ );
158
+
159
+ const acceptType = FORMAT_CONTENT_TYPE[outputFormat] ?? "audio/mpeg";
160
+
161
+ let response: Response;
162
+ try {
163
+ response = await fetch(`${url}?output_format=${outputFormat}`, {
164
+ method: "POST",
165
+ headers: {
166
+ "Content-Type": "application/json",
167
+ "xi-api-key": apiKey,
168
+ Accept: acceptType,
169
+ },
170
+ body: JSON.stringify(body),
171
+ signal: request.signal,
172
+ });
173
+ } catch (err) {
174
+ if (err instanceof Error && err.name === "AbortError") throw err;
175
+ throw new ElevenLabsTtsError(
176
+ "ELEVENLABS_TTS_REQUEST_FAILED",
177
+ `ElevenLabs TTS request failed: ${err instanceof Error ? err.message : String(err)}`,
178
+ );
179
+ }
180
+
181
+ if (!response.ok) {
182
+ const errorText = await response.text().catch(() => "");
183
+ throw new ElevenLabsTtsError(
184
+ "ELEVENLABS_TTS_HTTP_ERROR",
185
+ `ElevenLabs TTS returned ${response.status}: ${errorText}`,
186
+ response.status,
187
+ );
188
+ }
189
+
190
+ const arrayBuffer = await response.arrayBuffer();
191
+ if (arrayBuffer.byteLength === 0) {
192
+ throw new ElevenLabsTtsError(
193
+ "ELEVENLABS_TTS_EMPTY_RESPONSE",
194
+ "ElevenLabs TTS returned an empty audio response",
195
+ );
196
+ }
197
+
198
+ const contentType = FORMAT_CONTENT_TYPE[outputFormat] ?? "audio/mpeg";
199
+
200
+ log.debug(
201
+ { bytes: arrayBuffer.byteLength },
202
+ "ElevenLabs TTS synthesis complete",
203
+ );
204
+
205
+ return {
206
+ audio: Buffer.from(arrayBuffer),
207
+ contentType,
208
+ };
209
+ },
210
+ };
211
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Fish Audio TTS provider adapter.
3
+ *
4
+ * Wraps the existing {@link synthesizeWithFishAudio} function behind the
5
+ * uniform {@link TtsProvider} interface, preserving its streaming chunk
6
+ * callbacks for real-time call playback.
7
+ *
8
+ * Config comes from `services.tts.providers['fish-audio']`. The API key is read
9
+ * from the secure credential store (`fish-audio/api_key`) by the underlying
10
+ * client.
11
+ */
12
+
13
+ import { synthesizeWithFishAudio } from "../../calls/fish-audio-client.js";
14
+ import { getConfig } from "../../config/loader.js";
15
+ import type { TtsFishAudioProviderConfig } from "../../config/schemas/tts.js";
16
+ import { getLogger } from "../../util/logger.js";
17
+ import type {
18
+ TtsProvider,
19
+ TtsProviderCapabilities,
20
+ TtsSynthesisRequest,
21
+ TtsSynthesisResult,
22
+ } from "../types.js";
23
+
24
+ const log = getLogger("tts:fish-audio");
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Error types
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export type FishAudioTtsErrorCode =
31
+ | "FISH_AUDIO_TTS_NO_REFERENCE_ID"
32
+ | "FISH_AUDIO_TTS_SYNTHESIS_FAILED";
33
+
34
+ export class FishAudioTtsError extends Error {
35
+ readonly code: FishAudioTtsErrorCode;
36
+
37
+ constructor(code: FishAudioTtsErrorCode, message: string) {
38
+ super(message);
39
+ this.name = "FishAudioTtsError";
40
+ this.code = code;
41
+ }
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Helpers
46
+ // ---------------------------------------------------------------------------
47
+
48
+ /** Map Fish Audio format names to MIME content types. */
49
+ const FORMAT_CONTENT_TYPE: Record<string, string> = {
50
+ mp3: "audio/mpeg",
51
+ wav: "audio/wav",
52
+ opus: "audio/opus",
53
+ };
54
+
55
+ /**
56
+ * Resolve the effective reference ID.
57
+ *
58
+ * Priority: request-level `voiceId` > config `referenceId`.
59
+ */
60
+ function resolveReferenceId(
61
+ request: TtsSynthesisRequest,
62
+ config: TtsFishAudioProviderConfig,
63
+ ): string {
64
+ const referenceId = request.voiceId?.trim() || config.referenceId;
65
+ if (!referenceId) {
66
+ throw new FishAudioTtsError(
67
+ "FISH_AUDIO_TTS_NO_REFERENCE_ID",
68
+ "No Fish Audio reference ID provided. " +
69
+ "Set services.tts.providers.fish-audio.referenceId in config or pass voiceId in the request.",
70
+ );
71
+ }
72
+ return referenceId;
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Provider implementation
77
+ // ---------------------------------------------------------------------------
78
+
79
+ export function createFishAudioProvider(): TtsProvider {
80
+ const capabilities: TtsProviderCapabilities = {
81
+ supportsStreaming: true,
82
+ supportedFormats: ["mp3", "wav", "opus"],
83
+ };
84
+
85
+ return {
86
+ id: "fish-audio",
87
+ capabilities,
88
+
89
+ async synthesize(
90
+ request: TtsSynthesisRequest,
91
+ ): Promise<TtsSynthesisResult> {
92
+ const config = getConfig().services.tts.providers["fish-audio"];
93
+ const referenceId = resolveReferenceId(request, config);
94
+
95
+ // When PCM output is requested, override to WAV. Fish Audio
96
+ // doesn't support raw PCM, but WAV gives us PCM in a container
97
+ // that audioBufferToFrames can extract.
98
+ const effectiveFormat =
99
+ request.outputFormat === "pcm" ? "wav" : config.format;
100
+
101
+ // Build an effective config with the resolved reference ID
102
+ // and the potentially overridden format.
103
+ const effectiveConfig: TtsFishAudioProviderConfig = {
104
+ ...config,
105
+ referenceId,
106
+ format: effectiveFormat,
107
+ };
108
+
109
+ log.info(
110
+ {
111
+ referenceId,
112
+ format: effectiveFormat,
113
+ textLength: request.text.length,
114
+ },
115
+ "Starting Fish Audio TTS synthesis",
116
+ );
117
+
118
+ let audio: Buffer;
119
+ try {
120
+ audio = await synthesizeWithFishAudio(request.text, effectiveConfig, {
121
+ signal: request.signal,
122
+ });
123
+ } catch (err) {
124
+ if (err instanceof Error && err.name === "AbortError") throw err;
125
+ throw new FishAudioTtsError(
126
+ "FISH_AUDIO_TTS_SYNTHESIS_FAILED",
127
+ `Fish Audio TTS synthesis failed: ${err instanceof Error ? err.message : String(err)}`,
128
+ );
129
+ }
130
+
131
+ const contentType = FORMAT_CONTENT_TYPE[effectiveFormat] ?? "audio/mpeg";
132
+
133
+ return { audio, contentType };
134
+ },
135
+
136
+ async synthesizeStream(
137
+ request: TtsSynthesisRequest,
138
+ onChunk: (chunk: Uint8Array) => void,
139
+ ): Promise<TtsSynthesisResult> {
140
+ const config = getConfig().services.tts.providers["fish-audio"];
141
+ const referenceId = resolveReferenceId(request, config);
142
+
143
+ // When PCM output is requested, override to WAV. Fish Audio
144
+ // doesn't support raw PCM, but WAV gives us PCM in a container
145
+ // that audioBufferToFrames can extract.
146
+ const effectiveFormat =
147
+ request.outputFormat === "pcm" ? "wav" : config.format;
148
+
149
+ const effectiveConfig: TtsFishAudioProviderConfig = {
150
+ ...config,
151
+ referenceId,
152
+ format: effectiveFormat,
153
+ };
154
+
155
+ log.info(
156
+ {
157
+ referenceId,
158
+ format: effectiveFormat,
159
+ textLength: request.text.length,
160
+ },
161
+ "Starting Fish Audio TTS streaming synthesis",
162
+ );
163
+
164
+ let audio: Buffer;
165
+ try {
166
+ audio = await synthesizeWithFishAudio(request.text, effectiveConfig, {
167
+ onChunk,
168
+ signal: request.signal,
169
+ });
170
+ } catch (err) {
171
+ if (err instanceof Error && err.name === "AbortError") throw err;
172
+ throw new FishAudioTtsError(
173
+ "FISH_AUDIO_TTS_SYNTHESIS_FAILED",
174
+ `Fish Audio TTS streaming synthesis failed: ${err instanceof Error ? err.message : String(err)}`,
175
+ );
176
+ }
177
+
178
+ const contentType = FORMAT_CONTENT_TYPE[effectiveFormat] ?? "audio/mpeg";
179
+
180
+ return { audio, contentType };
181
+ },
182
+ };
183
+ }