@xopcai/xopc 0.0.143 → 0.0.145

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 (259) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/Combination-DolwnuKi.js +41 -0
  4. package/dist/gateway/static/root/assets/{activity-center-store-jITiX2vJ.js → activity-center-store-3l9ahUqb.js} +1 -1
  5. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DiME9u_P.js → agent-avatar-dicebear-BEUxdhZx.js} +1 -1
  6. package/dist/gateway/static/root/assets/agents-1bpT0xRO.js +2 -0
  7. package/dist/gateway/static/root/assets/app-management-settings-panel-CRHSZJWi.js +1 -0
  8. package/dist/gateway/static/root/assets/{appearance-settings-DUfdNQqk.js → appearance-settings-BunADYJb.js} +1 -1
  9. package/dist/gateway/static/root/assets/apps-page-Cc4JgVUQ.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-B-Qk6cGr.js → archive-plugin-B6vTkH2A.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-Di6EEbQX.js → attachment-load-BjZXzaNv.js} +1 -1
  12. package/dist/gateway/static/root/assets/{automation-suggestion-card-DGxvRxtO.js → automation-suggestion-card-ZDA-Stg1.js} +1 -1
  13. package/dist/gateway/static/root/assets/automations-page-mb0fdVpq.js +6 -0
  14. package/dist/gateway/static/root/assets/{binary-plugins-ZaOZDH2I.js → binary-plugins-DYZfLvPz.js} +1 -1
  15. package/dist/gateway/static/root/assets/{block-editor-BTFhjWfg.js → block-editor-C3MUlYC9.js} +46 -46
  16. package/dist/gateway/static/root/assets/browser-settings-page-Bb0xhaMN.js +8 -0
  17. package/dist/gateway/static/root/assets/capability-presets-api-BV76yN9a.js +1 -0
  18. package/dist/gateway/static/root/assets/capability-presets-settings-panel-CUcGKZqC.js +2 -0
  19. package/dist/gateway/static/root/assets/channel-recipient-api-fTlTnWB4.js +1 -0
  20. package/dist/gateway/static/root/assets/channels-settings-BlSDMGdy.js +1 -0
  21. package/dist/gateway/static/root/assets/channels-status-swr-CJauF1CW.js +1 -0
  22. package/dist/gateway/static/root/assets/{code-editor--tMO38Tr.js → code-editor-D4_k-6QN.js} +1 -1
  23. package/dist/gateway/static/root/assets/connector-logo-BVFNT2zW.js +1 -0
  24. package/dist/gateway/static/root/assets/connectors-page-CMgAFGvb.js +2 -0
  25. package/dist/gateway/static/root/assets/copy-to-clipboard-HOJ79TWv.js +1 -0
  26. package/dist/gateway/static/root/assets/{desktop-pet---yzVxVR.js → desktop-pet-_jUmoXd3.js} +1 -1
  27. package/dist/gateway/static/root/assets/{desktop-pet-settings-Dc3gqBJE.js → desktop-pet-settings-DwGOy4AQ.js} +1 -1
  28. package/dist/gateway/static/root/assets/{directory-picker-path-field-DCmOGLtK.js → directory-picker-path-field-akvF0F-u.js} +1 -1
  29. package/dist/gateway/static/root/assets/dist-C99RGfAH.js +1 -0
  30. package/dist/gateway/static/root/assets/dreaming-settings-BLetQiYY.js +3 -0
  31. package/dist/gateway/static/root/assets/{extension-debug-page-ByaKTTq4.js → extension-debug-page-Cof2ncxE.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-page-BmRzHAZr.js → extension-page-CwWktRNz.js} +1 -1
  33. package/dist/gateway/static/root/assets/extension-settings-page-CuggWOIl.js +1 -0
  34. package/dist/gateway/static/root/assets/gateway-config-api-B7sxIbza.js +1 -0
  35. package/dist/gateway/static/root/assets/gateway-settings-DIv0eKMf.js +1 -0
  36. package/dist/gateway/static/root/assets/gateway-startup-retry-DtoLihQE.js +1 -0
  37. package/dist/gateway/static/root/assets/goal-create-dialog-C4Kw0mfS.js +1 -0
  38. package/dist/gateway/static/root/assets/goal-detail-page-BMmzYpZL.js +4 -0
  39. package/dist/gateway/static/root/assets/goals-config-api-B0jm5JaA.js +1 -0
  40. package/dist/gateway/static/root/assets/goals-page-CGeT77PC.js +2 -0
  41. package/dist/gateway/static/root/assets/goals-settings-N5_iL7es.js +1 -0
  42. package/dist/gateway/static/root/assets/heartbeat-settings-XzH3lbRR.js +1 -0
  43. package/dist/gateway/static/root/assets/{html-workspace-editor-COqmtESs.js → html-workspace-editor-C3DKAkMc.js} +1 -1
  44. package/dist/gateway/static/root/assets/index-CSLF84-Q.css +1 -0
  45. package/dist/gateway/static/root/assets/index-I-ZCenVw.js +259 -0
  46. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-x_v0nrIi.js +1 -0
  47. package/dist/gateway/static/root/assets/locale-store-CiM-D9jA.js +1 -0
  48. package/dist/gateway/static/root/assets/logs-page-BSh1t8cD.js +7 -0
  49. package/dist/gateway/static/root/assets/markdown-editor-BvUebh3C.js +2 -0
  50. package/dist/gateway/static/root/assets/{markdown-split-Dw-yE2-y.js → markdown-split-5rJx5flj.js} +1 -1
  51. package/dist/gateway/static/root/assets/{media-plugins-D9NNNUgE.js → media-plugins-DYXccwAd.js} +1 -1
  52. package/dist/gateway/static/root/assets/messages-Cb1VMeqP.js +5 -0
  53. package/dist/gateway/static/root/assets/models-hub-panel-BNpTenAI.js +2 -0
  54. package/dist/gateway/static/root/assets/{notes-page-CRgbJGgk.js → notes-page-BgLyndCO.js} +1 -1
  55. package/dist/gateway/static/root/assets/notes-workbench-teklkhcE.js +4 -0
  56. package/dist/gateway/static/root/assets/{page-tabs-D4hkWVt_.js → page-tabs-6Al9_sGz.js} +1 -1
  57. package/dist/gateway/static/root/assets/{preview-open-alternatives-CIckv0uy.js → preview-open-alternatives-Cm3G72Hv.js} +1 -1
  58. package/dist/gateway/static/root/assets/project-detail-page-CCB_dKoh.js +1 -0
  59. package/dist/gateway/static/root/assets/projects-page-VOAFAHYj.js +1 -0
  60. package/dist/gateway/static/root/assets/remote-access-hub-USYu64Ru.js +1 -0
  61. package/dist/gateway/static/root/assets/{save-bar-controls-B0b42kB2.js → save-bar-controls-BYtfzu_F.js} +1 -1
  62. package/dist/gateway/static/root/assets/{schedule-field-BtjEkczK.js → schedule-field-CvIRc6yK.js} +1 -1
  63. package/dist/gateway/static/root/assets/{schema-form-Bf0bk3i6.js → schema-form-CQuKx0n7.js} +1 -1
  64. package/dist/gateway/static/root/assets/sessions-page-oGC8O5h_.js +1 -0
  65. package/dist/gateway/static/root/assets/settings-advanced-gate-D4rP66R1.js +1 -0
  66. package/dist/gateway/static/root/assets/{settings-collapsible-section-BTKTdTNX.js → settings-collapsible-section-CF3HHM-t.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-form-section-DL4P0sAj.js → settings-form-section-CwWjoRN6.js} +1 -1
  68. package/dist/gateway/static/root/assets/{settings-loading-skeleton-BV6KFNj4.js → settings-loading-skeleton-aUFmJa13.js} +1 -1
  69. package/dist/gateway/static/root/assets/{settings-page-B_4DsX5W.js → settings-page-B2UUU9TF.js} +1 -1
  70. package/dist/gateway/static/root/assets/{settings-page-layout-CyGlrInB.js → settings-page-layout-DqpbTWqI.js} +1 -1
  71. package/dist/gateway/static/root/assets/setup-status-panel-Ck9keROI.js +1 -0
  72. package/dist/gateway/static/root/assets/share-preview-page-BOhlPsS1.js +2 -0
  73. package/dist/gateway/static/root/assets/shares-settings-BYLsCuqj.js +1 -0
  74. package/dist/gateway/static/root/assets/skill-reload-api-BNHUyVML.js +1 -0
  75. package/dist/gateway/static/root/assets/skills-page-CpXbQH4n.js +2 -0
  76. package/dist/gateway/static/root/assets/{system-settings-panel-D85fBBuI.js → system-settings-panel-CWxSvuwD.js} +1 -1
  77. package/dist/gateway/static/root/assets/{theme-store-Bt6vAkSB.js → theme-store-C9Dxcppm.js} +1 -1
  78. package/dist/gateway/static/root/assets/{typed-models-lib-CwEuYrHX.js → typed-models-lib-qROAvqTw.js} +1 -1
  79. package/dist/gateway/static/root/assets/use-channel-catalog-DE9cpn4G.js +1 -0
  80. package/dist/gateway/static/root/assets/user-context-page-DbNEK4Yp.js +1 -0
  81. package/dist/gateway/static/root/assets/user-profile-fields-editor-CWxc0ghP.js +1 -0
  82. package/dist/gateway/static/root/assets/{user-profile-settings-panel-CWM0fEpb.js → user-profile-settings-panel-DW-Up6vt.js} +1 -1
  83. package/dist/gateway/static/root/assets/{utils--BO6xT3u.js → utils-D53uRXVr.js} +1 -1
  84. package/dist/gateway/static/root/assets/{vendor-codemirror-C1WKjCk5.js → vendor-codemirror--gTq3YAI.js} +17 -17
  85. package/dist/gateway/static/root/assets/voice-api-key-field-Cs7MOrg0.js +1 -0
  86. package/dist/gateway/static/root/assets/work-item-detail-page-D1ziG1Pp.js +1 -0
  87. package/dist/gateway/static/root/assets/workflow-run-setup-panel-D_x9ELJp.js +3 -0
  88. package/dist/gateway/static/root/assets/workflows-page-DHGV_n6i.js +11 -0
  89. package/dist/gateway/static/root/index.html +10 -10
  90. package/dist/package.js +1 -1
  91. package/dist/src/agent/embedded/session-runner.js +3 -3
  92. package/dist/src/agent/embedded/session-runner.js.map +1 -1
  93. package/dist/src/agent/embedded/xopc-auth-storage.d.ts +3 -1
  94. package/dist/src/agent/embedded/xopc-auth-storage.js +11 -1
  95. package/dist/src/agent/embedded/xopc-auth-storage.js.map +1 -1
  96. package/dist/src/agent/memory/dreaming/config.js +1 -1
  97. package/dist/src/agent-runtime/index.js +1 -1
  98. package/dist/src/agent-runtime/runtime-profile.js +1 -1
  99. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  100. package/dist/src/config/agent-profile.js +1 -1
  101. package/dist/src/config/index.js +2 -2
  102. package/dist/src/config/schema.d.ts +60 -5
  103. package/dist/src/config/schema.js +30 -9
  104. package/dist/src/config/schema.js.map +1 -1
  105. package/dist/src/connectors/composio-catalog.js +11 -2
  106. package/dist/src/connectors/composio-catalog.js.map +1 -1
  107. package/dist/src/connectors/connector-memory-sync.js +135 -26
  108. package/dist/src/connectors/connector-memory-sync.js.map +1 -1
  109. package/dist/src/gateway/agents-admin.js +1 -1
  110. package/dist/src/gateway/heartbeat/service.js +1 -1
  111. package/dist/src/gateway/hono/app.js +1 -1
  112. package/dist/src/gateway/hono/app.js.map +1 -1
  113. package/dist/src/gateway/hono/lib/config-payload.d.ts +11 -0
  114. package/dist/src/gateway/hono/lib/config-payload.js +1 -0
  115. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  116. package/dist/src/gateway/hono/routes/config-patch/misc.js +6 -1
  117. package/dist/src/gateway/hono/routes/config-patch/misc.js.map +1 -1
  118. package/dist/src/gateway/hono/routes/memory.js +11 -1
  119. package/dist/src/gateway/hono/routes/memory.js.map +1 -1
  120. package/dist/src/gateway/hono/routes/status.js +3 -0
  121. package/dist/src/gateway/hono/routes/status.js.map +1 -1
  122. package/dist/src/gateway/hono/routes/voice.d.ts +5 -5
  123. package/dist/src/gateway/hono/routes/voice.js +288 -62
  124. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  125. package/dist/src/gateway/hono/routes/you.js +5 -1
  126. package/dist/src/gateway/hono/routes/you.js.map +1 -1
  127. package/dist/src/gateway/service.d.ts +8 -0
  128. package/dist/src/gateway/service.js +41 -11
  129. package/dist/src/gateway/service.js.map +1 -1
  130. package/dist/src/heartbeat/index.js +1 -1
  131. package/dist/src/knowledge/connected-knowledge-pipeline.d.ts +21 -0
  132. package/dist/src/knowledge/connected-knowledge-pipeline.js +267 -0
  133. package/dist/src/knowledge/connected-knowledge-pipeline.js.map +1 -0
  134. package/dist/src/knowledge/coordinator.d.ts +10 -0
  135. package/dist/src/knowledge/coordinator.js +52 -0
  136. package/dist/src/knowledge/coordinator.js.map +1 -0
  137. package/dist/src/knowledge/index.d.ts +2 -0
  138. package/dist/src/knowledge/index.js +3 -1
  139. package/dist/src/knowledge/ingestion-service.d.ts +7 -1
  140. package/dist/src/knowledge/ingestion-service.js +28 -0
  141. package/dist/src/knowledge/ingestion-service.js.map +1 -1
  142. package/dist/src/knowledge/types.d.ts +18 -0
  143. package/dist/src/media-understanding/audio-transcription-runner.js +5 -3
  144. package/dist/src/media-understanding/audio-transcription-runner.js.map +1 -1
  145. package/dist/src/media-understanding/runner.js +8 -3
  146. package/dist/src/media-understanding/runner.js.map +1 -1
  147. package/dist/src/media-understanding/types.d.ts +7 -0
  148. package/dist/src/storage/sqlite/index.d.ts +1 -1
  149. package/dist/src/storage/sqlite/index.js +2 -2
  150. package/dist/src/storage/sqlite/knowledge-repository.d.ts +26 -1
  151. package/dist/src/storage/sqlite/knowledge-repository.js +120 -6
  152. package/dist/src/storage/sqlite/knowledge-repository.js.map +1 -1
  153. package/dist/src/storage/sqlite/migrations/037_connected_knowledge_pipeline.sql +47 -0
  154. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  155. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  156. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  157. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  158. package/dist/src/storage/sqlite/schema.js +1 -1
  159. package/dist/src/user-context/projection.d.ts +13 -4
  160. package/dist/src/user-context/projection.js +19 -7
  161. package/dist/src/user-context/projection.js.map +1 -1
  162. package/dist/src/voice/language-profile.d.ts +20 -0
  163. package/dist/src/voice/language-profile.js +156 -0
  164. package/dist/src/voice/language-profile.js.map +1 -0
  165. package/dist/src/voice/local/model-files.d.ts +14 -0
  166. package/dist/src/voice/local/model-files.js +94 -0
  167. package/dist/src/voice/local/model-files.js.map +1 -0
  168. package/dist/src/voice/local/model-manager.d.ts +18 -0
  169. package/dist/src/voice/local/model-manager.js +135 -0
  170. package/dist/src/voice/local/model-manager.js.map +1 -0
  171. package/dist/src/voice/local/models.d.ts +28 -0
  172. package/dist/src/voice/local/models.js +101 -0
  173. package/dist/src/voice/local/models.js.map +1 -0
  174. package/dist/src/voice/local/runtime-client.d.ts +26 -0
  175. package/dist/src/voice/local/runtime-client.js +166 -0
  176. package/dist/src/voice/local/runtime-client.js.map +1 -0
  177. package/dist/src/voice/local/runtime-dispatcher.d.ts +9 -0
  178. package/dist/src/voice/local/runtime-dispatcher.js +22 -0
  179. package/dist/src/voice/local/runtime-dispatcher.js.map +1 -0
  180. package/dist/src/voice/local/runtime-worker.d.ts +1 -0
  181. package/dist/src/voice/local/runtime-worker.js +249 -0
  182. package/dist/src/voice/local/runtime-worker.js.map +1 -0
  183. package/dist/src/voice/local/wav.d.ts +6 -0
  184. package/dist/src/voice/local/wav.js +69 -0
  185. package/dist/src/voice/local/wav.js.map +1 -0
  186. package/dist/src/voice/metadata/builtin.js +46 -0
  187. package/dist/src/voice/metadata/builtin.js.map +1 -1
  188. package/dist/src/voice/stt/factory.js +8 -2
  189. package/dist/src/voice/stt/factory.js.map +1 -1
  190. package/dist/src/voice/stt/providers/alibaba-transcription.js +14 -8
  191. package/dist/src/voice/stt/providers/alibaba-transcription.js.map +1 -1
  192. package/dist/src/voice/stt/providers/index.d.ts +1 -0
  193. package/dist/src/voice/stt/providers/index.js +2 -1
  194. package/dist/src/voice/stt/providers/local-transcription.d.ts +2 -0
  195. package/dist/src/voice/stt/providers/local-transcription.js +51 -0
  196. package/dist/src/voice/stt/providers/local-transcription.js.map +1 -0
  197. package/dist/src/voice/stt/providers/openai-transcription.js +5 -3
  198. package/dist/src/voice/stt/providers/openai-transcription.js.map +1 -1
  199. package/dist/src/voice/stt/transcribe-core.js +5 -4
  200. package/dist/src/voice/stt/transcribe-core.js.map +1 -1
  201. package/dist/src/voice/stt/types.d.ts +4 -0
  202. package/dist/src/voice/stt/types.js +5 -4
  203. package/dist/src/voice/stt/types.js.map +1 -1
  204. package/dist/src/voice/tts/providers/edge-speech.js +2 -2
  205. package/dist/src/voice/tts/providers/edge-speech.js.map +1 -1
  206. package/dist/src/voice/tts/types.js +3 -3
  207. package/dist/src/voice/tts/types.js.map +1 -1
  208. package/package.json +4 -2
  209. package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +0 -41
  210. package/dist/gateway/static/root/assets/agents-DzBXhZXO.js +0 -2
  211. package/dist/gateway/static/root/assets/app-management-settings-panel-Cuo7SbUP.js +0 -1
  212. package/dist/gateway/static/root/assets/apps-page-CKuKK5AU.js +0 -1
  213. package/dist/gateway/static/root/assets/automations-page-D-FF_WKV.js +0 -4
  214. package/dist/gateway/static/root/assets/browser-settings-page-BvKLdEQt.js +0 -8
  215. package/dist/gateway/static/root/assets/capability-presets-api-BU-pVZiZ.js +0 -1
  216. package/dist/gateway/static/root/assets/capability-presets-settings-panel-DgyeEwzh.js +0 -2
  217. package/dist/gateway/static/root/assets/channel-recipient-api-DuulnhIj.js +0 -1
  218. package/dist/gateway/static/root/assets/channels-settings-BbVsdD_I.js +0 -1
  219. package/dist/gateway/static/root/assets/channels-status-swr-B5lVrUsN.js +0 -1
  220. package/dist/gateway/static/root/assets/connectors-page-B8QqVy3I.js +0 -2
  221. package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +0 -1
  222. package/dist/gateway/static/root/assets/dreaming-settings-BgT5KZVe.js +0 -3
  223. package/dist/gateway/static/root/assets/extension-settings-page-JUtwIhbF.js +0 -1
  224. package/dist/gateway/static/root/assets/fetch-8QNN25Sh.js +0 -1
  225. package/dist/gateway/static/root/assets/gateway-config-api-CxicF50_.js +0 -1
  226. package/dist/gateway/static/root/assets/gateway-settings-Bz2I_EO5.js +0 -1
  227. package/dist/gateway/static/root/assets/gateway-startup-retry-5YBnS9Fj.js +0 -1
  228. package/dist/gateway/static/root/assets/goal-create-dialog-VcoyuZGN.js +0 -1
  229. package/dist/gateway/static/root/assets/goal-detail-page-Cilkap2G.js +0 -4
  230. package/dist/gateway/static/root/assets/goals-config-api-CwcHRdC0.js +0 -1
  231. package/dist/gateway/static/root/assets/goals-page-C6eoni4z.js +0 -2
  232. package/dist/gateway/static/root/assets/goals-settings-o4i-qAOS.js +0 -1
  233. package/dist/gateway/static/root/assets/heartbeat-settings-DQQkETX8.js +0 -1
  234. package/dist/gateway/static/root/assets/index-BKRqeRHm.js +0 -257
  235. package/dist/gateway/static/root/assets/index-CphxClbs.css +0 -1
  236. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-LT6qB-ua.js +0 -1
  237. package/dist/gateway/static/root/assets/locale-store-Ba72bFmP.js +0 -1
  238. package/dist/gateway/static/root/assets/logs-page-Dli2e3fS.js +0 -7
  239. package/dist/gateway/static/root/assets/markdown-editor-jD1LPiP-.js +0 -2
  240. package/dist/gateway/static/root/assets/messages-CzKxM6I4.js +0 -5
  241. package/dist/gateway/static/root/assets/models-hub-panel-DVn2Q1Ik.js +0 -2
  242. package/dist/gateway/static/root/assets/notes-workbench-BxZRHLWx.js +0 -2
  243. package/dist/gateway/static/root/assets/project-detail-page-Doz53xLn.js +0 -1
  244. package/dist/gateway/static/root/assets/projects-page-DoMtcRLg.js +0 -1
  245. package/dist/gateway/static/root/assets/remote-access-hub-BeHVEgrm.js +0 -1
  246. package/dist/gateway/static/root/assets/sessions-page-Bk4bQsBe.js +0 -1
  247. package/dist/gateway/static/root/assets/settings-advanced-gate-DdFyaRMm.js +0 -1
  248. package/dist/gateway/static/root/assets/setup-status-panel-DJ1_MLgX.js +0 -1
  249. package/dist/gateway/static/root/assets/share-preview-page-BQBV7YrV.js +0 -2
  250. package/dist/gateway/static/root/assets/shares-settings-0VuFLBeU.js +0 -1
  251. package/dist/gateway/static/root/assets/skill-reload-api-DO87Ckeo.js +0 -1
  252. package/dist/gateway/static/root/assets/skills-page-D3VdtxAn.js +0 -2
  253. package/dist/gateway/static/root/assets/use-channel-catalog-COXIfXcl.js +0 -1
  254. package/dist/gateway/static/root/assets/user-context-page-m9JVQ73F.js +0 -1
  255. package/dist/gateway/static/root/assets/user-profile-fields-editor-DtQiuY7E.js +0 -1
  256. package/dist/gateway/static/root/assets/voice-api-key-field-LoT3wgZ9.js +0 -1
  257. package/dist/gateway/static/root/assets/work-item-detail-page-DSTMz_mO.js +0 -1
  258. package/dist/gateway/static/root/assets/workflow-run-setup-panel-B35lJzvm.js +0 -3
  259. package/dist/gateway/static/root/assets/workflows-page-BfSm9oR8.js +0 -11
@@ -0,0 +1,249 @@
1
+ import { getLocalVoiceModel, resolveLocalVoiceModelDir } from "./models.js";
2
+ import { downloadLocalVoiceModelFiles } from "./model-files.js";
3
+ import { createRuntimeDispatcher } from "./runtime-dispatcher.js";
4
+ import { join, resolve, sep } from "node:path";
5
+ import { mkdir, rm } from "node:fs/promises";
6
+ import { EnvHttpProxyAgent, RetryAgent, setGlobalDispatcher } from "undici";
7
+ import { createInterface } from "node:readline";
8
+ //#region src/voice/local/runtime-worker.ts
9
+ const pipelines = /* @__PURE__ */ new Map();
10
+ const sherpaRecognizers = /* @__PURE__ */ new Map();
11
+ const networkAgent = new RetryAgent(new EnvHttpProxyAgent(), {
12
+ maxRetries: 4,
13
+ minTimeout: 500,
14
+ maxTimeout: 8e3,
15
+ timeoutFactor: 2,
16
+ retryAfter: true
17
+ });
18
+ setGlobalDispatcher(networkAgent);
19
+ function normalizeRemoteHost(value) {
20
+ const url = new URL(value);
21
+ if (url.username || url.password) throw new Error("Local voice model endpoint must not contain credentials");
22
+ if (url.protocol !== "https:" && url.hostname !== "127.0.0.1" && url.hostname !== "localhost") throw new Error("Local voice model endpoint must use HTTPS");
23
+ url.pathname = `${url.pathname.replace(/\/+$/, "")}/`;
24
+ return url.toString();
25
+ }
26
+ function resolveRemoteHosts() {
27
+ const configured = process.env.XOPC_VOICE_MODEL_ENDPOINT?.trim() || process.env.HF_ENDPOINT?.trim();
28
+ if (configured) return [normalizeRemoteHost(configured)];
29
+ return [normalizeRemoteHost("https://xopc.ai/api/voice/models"), normalizeRemoteHost("https://huggingface.co")];
30
+ }
31
+ function serializeError(error) {
32
+ if (!(error instanceof Error)) return { message: String(error) };
33
+ const withCode = error;
34
+ const nested = withCode.cause;
35
+ const nestedMessage = nested instanceof Error ? nested.message : void 0;
36
+ const nestedCode = nested && typeof nested === "object" && "code" in nested ? String(nested.code ?? "") : void 0;
37
+ return {
38
+ message: error.message,
39
+ ...typeof withCode.code === "string" ? { code: withCode.code } : {},
40
+ ...nestedCode ? { code: nestedCode } : {},
41
+ ...nestedMessage && nestedMessage !== error.message ? { cause: nestedMessage } : {}
42
+ };
43
+ }
44
+ function send(message) {
45
+ process.stdout.write(`${JSON.stringify(message)}\n`);
46
+ }
47
+ async function loadPipeline(modelId, allowRemote, cacheDir = resolveLocalVoiceModelDir(modelId), onProgress) {
48
+ const key = `${modelId}:${cacheDir}`;
49
+ const existing = pipelines.get(key);
50
+ if (existing) return existing;
51
+ const model = getLocalVoiceModel(modelId);
52
+ if (model.engine !== "transformers.js") throw new Error(`Model ${model.id} does not use the Transformers.js engine`);
53
+ const promise = (async () => {
54
+ const transformers = await import("@huggingface/transformers");
55
+ transformers.env.cacheDir = cacheDir;
56
+ transformers.env.allowRemoteModels = allowRemote;
57
+ transformers.env.allowLocalModels = true;
58
+ const remoteHosts = allowRemote ? resolveRemoteHosts() : [normalizeRemoteHost("https://xopc.ai")];
59
+ let lastError;
60
+ for (const [index, remoteHost] of remoteHosts.entries()) {
61
+ transformers.env.remoteHost = remoteHost;
62
+ try {
63
+ return await transformers.pipeline("automatic-speech-recognition", model.repository, {
64
+ revision: model.revision,
65
+ dtype: model.dtype,
66
+ progress_callback: onProgress
67
+ });
68
+ } catch (error) {
69
+ lastError = error;
70
+ if (index + 1 < remoteHosts.length) {
71
+ await rm(cacheDir, {
72
+ recursive: true,
73
+ force: true
74
+ });
75
+ await mkdir(cacheDir, { recursive: true });
76
+ }
77
+ }
78
+ }
79
+ throw lastError;
80
+ })();
81
+ pipelines.set(key, promise);
82
+ try {
83
+ return await promise;
84
+ } catch (error) {
85
+ pipelines.delete(key);
86
+ throw error;
87
+ }
88
+ }
89
+ function normalizeSenseVoiceLanguage(language) {
90
+ if (typeof language !== "string") return "auto";
91
+ const value = language.trim().toLowerCase().replace("_", "-");
92
+ if (!value || value === "auto") return "auto";
93
+ if (value === "cmn" || value.startsWith("zh")) return "zh";
94
+ if (value === "cantonese" || value.startsWith("yue")) return "yue";
95
+ if (value.startsWith("en")) return "en";
96
+ if (value.startsWith("ja")) return "ja";
97
+ if (value.startsWith("ko")) return "ko";
98
+ return "auto";
99
+ }
100
+ function cleanSenseVoiceTag(value) {
101
+ return value?.replace(/^<\|/, "").replace(/\|>$/, "").trim() || void 0;
102
+ }
103
+ async function loadSherpaRecognizer(modelId, language) {
104
+ const key = `${modelId}:${language}`;
105
+ const existing = sherpaRecognizers.get(key);
106
+ if (existing) return existing;
107
+ const model = getLocalVoiceModel(modelId);
108
+ if (model.engine !== "sherpa-onnx") throw new Error(`Model ${model.id} does not use the sherpa-onnx engine`);
109
+ const modelDir = resolveLocalVoiceModelDir(model.id);
110
+ const promise = (async () => {
111
+ const imported = await import("sherpa-onnx-node");
112
+ return new (imported.default ?? imported).OfflineRecognizer({
113
+ featConfig: {
114
+ sampleRate: 16e3,
115
+ featureDim: 80
116
+ },
117
+ modelConfig: {
118
+ senseVoice: {
119
+ model: join(modelDir, "model.int8.onnx"),
120
+ language,
121
+ useInverseTextNormalization: 1
122
+ },
123
+ tokens: join(modelDir, "tokens.txt"),
124
+ numThreads: Math.max(1, Math.min(4, Number(process.env.XOPC_VOICE_THREADS) || 2)),
125
+ provider: "cpu",
126
+ debug: 0
127
+ }
128
+ });
129
+ })();
130
+ sherpaRecognizers.set(key, promise);
131
+ try {
132
+ return await promise;
133
+ } catch (error) {
134
+ sherpaRecognizers.delete(key);
135
+ throw error;
136
+ }
137
+ }
138
+ async function transcribeWithSherpa(modelId, audio, language) {
139
+ const recognizer = await loadSherpaRecognizer(modelId, normalizeSenseVoiceLanguage(language));
140
+ const stream = recognizer.createStream();
141
+ stream.acceptWaveform({
142
+ sampleRate: 16e3,
143
+ samples: audio
144
+ });
145
+ const result = recognizer.decodeAsync ? await recognizer.decodeAsync(stream) : (recognizer.decode(stream), recognizer.getResult(stream));
146
+ return {
147
+ text: String(result.text ?? "").trim(),
148
+ ...cleanSenseVoiceTag(result.lang) ? { language: cleanSenseVoiceTag(result.lang) } : {},
149
+ ...cleanSenseVoiceTag(result.emotion) ? { emotion: cleanSenseVoiceTag(result.emotion) } : {},
150
+ ...cleanSenseVoiceTag(result.event) ? { event: cleanSenseVoiceTag(result.event) } : {}
151
+ };
152
+ }
153
+ async function handle(request) {
154
+ const params = request.params ?? {};
155
+ if (request.method === "health") return {
156
+ ok: true,
157
+ protocolVersion: 2,
158
+ engine: "transformers.js+sherpa-onnx",
159
+ engines: ["transformers.js", "sherpa-onnx"]
160
+ };
161
+ if (request.method === "model.install") {
162
+ const modelId = String(params.modelId ?? "");
163
+ const modelDir = resolve(resolveLocalVoiceModelDir(modelId));
164
+ const requestedCacheDir = typeof params.cacheDir === "string" ? resolve(params.cacheDir) : modelDir;
165
+ const allowedPrefix = `${resolve(modelDir, "..")}${sep}`;
166
+ if (requestedCacheDir !== modelDir && !requestedCacheDir.startsWith(allowedPrefix)) throw new Error("Local voice model cache directory is outside the managed root");
167
+ const model = getLocalVoiceModel(modelId);
168
+ if (model.engine === "sherpa-onnx") await downloadLocalVoiceModelFiles({
169
+ model,
170
+ destinationDir: requestedCacheDir,
171
+ remoteHosts: resolveRemoteHosts(),
172
+ onProgress: (data) => send({
173
+ id: request.id,
174
+ event: "progress",
175
+ data
176
+ })
177
+ });
178
+ else {
179
+ await loadPipeline(modelId, true, requestedCacheDir, (data) => send({
180
+ id: request.id,
181
+ event: "progress",
182
+ data
183
+ }));
184
+ pipelines.delete(`${modelId}:${requestedCacheDir}`);
185
+ }
186
+ return {
187
+ installed: true,
188
+ modelId
189
+ };
190
+ }
191
+ if (request.method === "model.unload") {
192
+ const modelId = String(params.modelId ?? "");
193
+ for (const key of pipelines.keys()) if (key.startsWith(`${modelId}:`)) pipelines.delete(key);
194
+ for (const key of sherpaRecognizers.keys()) if (key.startsWith(`${modelId}:`)) sherpaRecognizers.delete(key);
195
+ return { unloaded: true };
196
+ }
197
+ if (request.method === "transcribe") {
198
+ const modelId = String(params.modelId ?? "");
199
+ const bytes = Buffer.from(String(params.audioBase64 ?? ""), "base64");
200
+ const copied = Uint8Array.from(bytes);
201
+ const audio = new Float32Array(copied.buffer);
202
+ const language = typeof params.language === "string" && params.language ? params.language : void 0;
203
+ const model = getLocalVoiceModel(modelId);
204
+ if (model.engine === "sherpa-onnx") return {
205
+ ...await transcribeWithSherpa(model.id, audio, language),
206
+ modelId
207
+ };
208
+ const output = await (await loadPipeline(modelId, false))(audio, {
209
+ task: "transcribe",
210
+ ...language ? { language } : {},
211
+ chunk_length_s: 30,
212
+ stride_length_s: 5,
213
+ return_timestamps: false
214
+ });
215
+ return {
216
+ text: (output && typeof output === "object" && "text" in output ? String(output.text ?? "") : "").trim(),
217
+ modelId
218
+ };
219
+ }
220
+ throw new Error(`Unknown local voice runtime method: ${request.method}`);
221
+ }
222
+ const lines = createInterface({ input: process.stdin });
223
+ const dispatch = createRuntimeDispatcher({
224
+ execute: handle,
225
+ sendResult: (request, result) => send({
226
+ id: request.id,
227
+ result
228
+ }),
229
+ sendError: (request, error) => send({
230
+ id: request.id,
231
+ error: serializeError(error)
232
+ })
233
+ });
234
+ lines.on("line", (line) => {
235
+ let request;
236
+ try {
237
+ request = JSON.parse(line);
238
+ } catch {
239
+ return;
240
+ }
241
+ dispatch(request);
242
+ });
243
+ lines.once("close", () => {
244
+ networkAgent.close().finally(() => process.exit(0));
245
+ });
246
+ //#endregion
247
+ export {};
248
+
249
+ //# sourceMappingURL=runtime-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-worker.js","names":[],"sources":["../../../../src/voice/local/runtime-worker.ts"],"sourcesContent":["import { createInterface } from 'node:readline';\nimport { join, resolve, sep } from 'node:path';\nimport { mkdir, rm } from 'node:fs/promises';\n\nimport { EnvHttpProxyAgent, RetryAgent, setGlobalDispatcher } from 'undici';\n\nimport { getLocalVoiceModel, resolveLocalVoiceModelDir } from './models.js';\nimport { downloadLocalVoiceModelFiles } from './model-files.js';\nimport { createRuntimeDispatcher } from './runtime-dispatcher.js';\n\ntype RuntimeRequest = { id: number; method: string; params?: Record<string, unknown> };\ntype AsrPipeline = (audio: Float32Array, options?: Record<string, unknown>) => Promise<unknown>;\n\nconst pipelines = new Map<string, Promise<AsrPipeline>>();\n\ninterface SherpaOfflineStream {\n acceptWaveform(input: { sampleRate: number; samples: Float32Array }): void;\n setOption?(key: string, value: string): void;\n}\n\ninterface SherpaResult {\n text?: string;\n lang?: string;\n emotion?: string;\n event?: string;\n}\n\ninterface SherpaOfflineRecognizer {\n createStream(): SherpaOfflineStream;\n decodeAsync?(stream: SherpaOfflineStream): Promise<SherpaResult>;\n decode(stream: SherpaOfflineStream): void;\n getResult(stream: SherpaOfflineStream): SherpaResult;\n}\n\ntype SherpaModule = {\n OfflineRecognizer: new (config: Record<string, unknown>) => SherpaOfflineRecognizer;\n};\n\nconst sherpaRecognizers = new Map<string, Promise<SherpaOfflineRecognizer>>();\n\nconst networkAgent = new RetryAgent(new EnvHttpProxyAgent(), {\n maxRetries: 4,\n minTimeout: 500,\n maxTimeout: 8_000,\n timeoutFactor: 2,\n retryAfter: true,\n});\nsetGlobalDispatcher(networkAgent);\n\nfunction normalizeRemoteHost(value: string): string {\n const url = new URL(value);\n if (url.username || url.password) {\n throw new Error('Local voice model endpoint must not contain credentials');\n }\n if (url.protocol !== 'https:' && url.hostname !== '127.0.0.1' && url.hostname !== 'localhost') {\n throw new Error('Local voice model endpoint must use HTTPS');\n }\n url.pathname = `${url.pathname.replace(/\\/+$/, '')}/`;\n return url.toString();\n}\n\nfunction resolveRemoteHosts(): string[] {\n const configured = process.env.XOPC_VOICE_MODEL_ENDPOINT?.trim()\n || process.env.HF_ENDPOINT?.trim();\n if (configured) return [normalizeRemoteHost(configured)];\n return [\n normalizeRemoteHost('https://xopc.ai/api/voice/models'),\n normalizeRemoteHost('https://huggingface.co'),\n ];\n}\n\nfunction serializeError(error: unknown): { message: string; code?: string; cause?: string } {\n if (!(error instanceof Error)) return { message: String(error) };\n const withCode = error as Error & { code?: unknown; cause?: unknown };\n const nested = withCode.cause;\n const nestedMessage = nested instanceof Error ? nested.message : undefined;\n const nestedCode = nested && typeof nested === 'object' && 'code' in nested\n ? String((nested as { code?: unknown }).code ?? '')\n : undefined;\n return {\n message: error.message,\n ...(typeof withCode.code === 'string' ? { code: withCode.code } : {}),\n ...(nestedCode ? { code: nestedCode } : {}),\n ...(nestedMessage && nestedMessage !== error.message ? { cause: nestedMessage } : {}),\n };\n}\n\nfunction send(message: Record<string, unknown>): void {\n process.stdout.write(`${JSON.stringify(message)}\\n`);\n}\n\nasync function loadPipeline(\n modelId: string,\n allowRemote: boolean,\n cacheDir = resolveLocalVoiceModelDir(modelId),\n onProgress?: (data: Record<string, unknown>) => void,\n): Promise<AsrPipeline> {\n const key = `${modelId}:${cacheDir}`;\n const existing = pipelines.get(key);\n if (existing) return existing;\n const model = getLocalVoiceModel(modelId);\n if (model.engine !== 'transformers.js') {\n throw new Error(`Model ${model.id} does not use the Transformers.js engine`);\n }\n const promise = (async () => {\n const transformers = await import('@huggingface/transformers');\n transformers.env.cacheDir = cacheDir;\n transformers.env.allowRemoteModels = allowRemote;\n transformers.env.allowLocalModels = true;\n const remoteHosts = allowRemote ? resolveRemoteHosts() : [normalizeRemoteHost('https://xopc.ai')];\n let lastError: unknown;\n for (const [index, remoteHost] of remoteHosts.entries()) {\n transformers.env.remoteHost = remoteHost;\n try {\n const result = await transformers.pipeline('automatic-speech-recognition', model.repository, {\n revision: model.revision,\n dtype: model.dtype,\n progress_callback: onProgress,\n });\n return result as unknown as AsrPipeline;\n } catch (error) {\n lastError = error;\n if (index + 1 < remoteHosts.length) {\n await rm(cacheDir, { recursive: true, force: true });\n await mkdir(cacheDir, { recursive: true });\n }\n }\n }\n throw lastError;\n })();\n pipelines.set(key, promise);\n try {\n return await promise;\n } catch (error) {\n pipelines.delete(key);\n throw error;\n }\n}\n\nfunction normalizeSenseVoiceLanguage(language: unknown): string {\n if (typeof language !== 'string') return 'auto';\n const value = language.trim().toLowerCase().replace('_', '-');\n if (!value || value === 'auto') return 'auto';\n if (value === 'cmn' || value.startsWith('zh')) return 'zh';\n if (value === 'cantonese' || value.startsWith('yue')) return 'yue';\n if (value.startsWith('en')) return 'en';\n if (value.startsWith('ja')) return 'ja';\n if (value.startsWith('ko')) return 'ko';\n return 'auto';\n}\n\nfunction cleanSenseVoiceTag(value: string | undefined): string | undefined {\n const cleaned = value?.replace(/^<\\|/, '').replace(/\\|>$/, '').trim();\n return cleaned || undefined;\n}\n\nasync function loadSherpaRecognizer(\n modelId: string,\n language: string,\n): Promise<SherpaOfflineRecognizer> {\n const key = `${modelId}:${language}`;\n const existing = sherpaRecognizers.get(key);\n if (existing) return existing;\n const model = getLocalVoiceModel(modelId);\n if (model.engine !== 'sherpa-onnx') {\n throw new Error(`Model ${model.id} does not use the sherpa-onnx engine`);\n }\n const modelDir = resolveLocalVoiceModelDir(model.id);\n const promise = (async () => {\n const imported = await import('sherpa-onnx-node');\n const sherpa = ((imported as { default?: SherpaModule }).default ?? imported) as SherpaModule;\n return new sherpa.OfflineRecognizer({\n featConfig: { sampleRate: 16_000, featureDim: 80 },\n modelConfig: {\n senseVoice: {\n model: join(modelDir, 'model.int8.onnx'),\n language,\n useInverseTextNormalization: 1,\n },\n tokens: join(modelDir, 'tokens.txt'),\n numThreads: Math.max(1, Math.min(4, Number(process.env.XOPC_VOICE_THREADS) || 2)),\n provider: 'cpu',\n debug: 0,\n },\n });\n })();\n sherpaRecognizers.set(key, promise);\n try {\n return await promise;\n } catch (error) {\n sherpaRecognizers.delete(key);\n throw error;\n }\n}\n\nasync function transcribeWithSherpa(\n modelId: string,\n audio: Float32Array,\n language: unknown,\n): Promise<{ text: string; language?: string; emotion?: string; event?: string }> {\n const recognizer = await loadSherpaRecognizer(modelId, normalizeSenseVoiceLanguage(language));\n const stream = recognizer.createStream();\n stream.acceptWaveform({ sampleRate: 16_000, samples: audio });\n const result = recognizer.decodeAsync\n ? await recognizer.decodeAsync(stream)\n : (recognizer.decode(stream), recognizer.getResult(stream));\n return {\n text: String(result.text ?? '').trim(),\n ...(cleanSenseVoiceTag(result.lang) ? { language: cleanSenseVoiceTag(result.lang) } : {}),\n ...(cleanSenseVoiceTag(result.emotion) ? { emotion: cleanSenseVoiceTag(result.emotion) } : {}),\n ...(cleanSenseVoiceTag(result.event) ? { event: cleanSenseVoiceTag(result.event) } : {}),\n };\n}\n\nasync function handle(request: RuntimeRequest): Promise<unknown> {\n const params = request.params ?? {};\n if (request.method === 'health') {\n return {\n ok: true,\n protocolVersion: 2,\n engine: 'transformers.js+sherpa-onnx',\n engines: ['transformers.js', 'sherpa-onnx'],\n };\n }\n if (request.method === 'model.install') {\n const modelId = String(params.modelId ?? '');\n const modelDir = resolve(resolveLocalVoiceModelDir(modelId));\n const requestedCacheDir = typeof params.cacheDir === 'string' ? resolve(params.cacheDir) : modelDir;\n const allowedPrefix = `${resolve(modelDir, '..')}${sep}`;\n if (requestedCacheDir !== modelDir && !requestedCacheDir.startsWith(allowedPrefix)) {\n throw new Error('Local voice model cache directory is outside the managed root');\n }\n const model = getLocalVoiceModel(modelId);\n if (model.engine === 'sherpa-onnx') {\n await downloadLocalVoiceModelFiles({\n model,\n destinationDir: requestedCacheDir,\n remoteHosts: resolveRemoteHosts(),\n onProgress: (data) => send({ id: request.id, event: 'progress', data }),\n });\n } else {\n await loadPipeline(\n modelId,\n true,\n requestedCacheDir,\n (data) => send({ id: request.id, event: 'progress', data }),\n );\n pipelines.delete(`${modelId}:${requestedCacheDir}`);\n }\n return { installed: true, modelId };\n }\n if (request.method === 'model.unload') {\n const modelId = String(params.modelId ?? '');\n for (const key of pipelines.keys()) {\n if (key.startsWith(`${modelId}:`)) pipelines.delete(key);\n }\n for (const key of sherpaRecognizers.keys()) {\n if (key.startsWith(`${modelId}:`)) sherpaRecognizers.delete(key);\n }\n return { unloaded: true };\n }\n if (request.method === 'transcribe') {\n const modelId = String(params.modelId ?? '');\n const bytes = Buffer.from(String(params.audioBase64 ?? ''), 'base64');\n const copied = Uint8Array.from(bytes);\n const audio = new Float32Array(copied.buffer);\n const language = typeof params.language === 'string' && params.language ? params.language : undefined;\n const model = getLocalVoiceModel(modelId);\n if (model.engine === 'sherpa-onnx') {\n return { ...(await transcribeWithSherpa(model.id, audio, language)), modelId };\n }\n const pipeline = await loadPipeline(modelId, false);\n const output = await pipeline(audio, {\n task: 'transcribe',\n ...(language ? { language } : {}),\n chunk_length_s: 30,\n stride_length_s: 5,\n return_timestamps: false,\n });\n const text = output && typeof output === 'object' && 'text' in output\n ? String((output as { text?: unknown }).text ?? '')\n : '';\n return { text: text.trim(), modelId };\n }\n throw new Error(`Unknown local voice runtime method: ${request.method}`);\n}\n\nconst lines = createInterface({ input: process.stdin });\nconst dispatch = createRuntimeDispatcher<RuntimeRequest>({\n execute: handle,\n sendResult: (request, result) => send({ id: request.id, result }),\n sendError: (request, error) => send({ id: request.id, error: serializeError(error) }),\n});\nlines.on('line', (line) => {\n let request: RuntimeRequest;\n try {\n request = JSON.parse(line) as RuntimeRequest;\n } catch {\n return;\n }\n dispatch(request);\n});\nlines.once('close', () => {\n void networkAgent.close().finally(() => process.exit(0));\n});\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export interface DecodedPcmAudio {
2
+ samples: Float32Array;
3
+ sampleRate: number;
4
+ durationSeconds: number;
5
+ }
6
+ export declare function decodeWavToMonoFloat32(buffer: Buffer, targetRate?: number): DecodedPcmAudio;
@@ -0,0 +1,69 @@
1
+ //#region src/voice/local/wav.ts
2
+ function readChunkId(buffer, offset) {
3
+ return buffer.toString("ascii", offset, offset + 4);
4
+ }
5
+ function resampleLinear(input, sourceRate, targetRate) {
6
+ if (sourceRate === targetRate) return input;
7
+ const outputLength = Math.max(1, Math.round(input.length * targetRate / sourceRate));
8
+ const output = new Float32Array(outputLength);
9
+ const ratio = sourceRate / targetRate;
10
+ for (let i = 0; i < outputLength; i += 1) {
11
+ const sourcePosition = i * ratio;
12
+ const left = Math.min(input.length - 1, Math.floor(sourcePosition));
13
+ const right = Math.min(input.length - 1, left + 1);
14
+ const fraction = sourcePosition - left;
15
+ output[i] = input[left] * (1 - fraction) + input[right] * fraction;
16
+ }
17
+ return output;
18
+ }
19
+ function decodeWavToMonoFloat32(buffer, targetRate = 16e3) {
20
+ if (buffer.length < 44 || readChunkId(buffer, 0) !== "RIFF" || readChunkId(buffer, 8) !== "WAVE") throw new Error("Local STT currently requires PCM WAV audio");
21
+ let offset = 12;
22
+ let format;
23
+ let dataOffset = -1;
24
+ let dataLength = 0;
25
+ while (offset + 8 <= buffer.length) {
26
+ const id = readChunkId(buffer, offset);
27
+ const length = buffer.readUInt32LE(offset + 4);
28
+ const bodyOffset = offset + 8;
29
+ if (bodyOffset + length > buffer.length) break;
30
+ if (id === "fmt " && length >= 16) format = {
31
+ audioFormat: buffer.readUInt16LE(bodyOffset),
32
+ channels: buffer.readUInt16LE(bodyOffset + 2),
33
+ sampleRate: buffer.readUInt32LE(bodyOffset + 4),
34
+ bitsPerSample: buffer.readUInt16LE(bodyOffset + 14)
35
+ };
36
+ else if (id === "data") {
37
+ dataOffset = bodyOffset;
38
+ dataLength = length;
39
+ break;
40
+ }
41
+ offset = bodyOffset + length + length % 2;
42
+ }
43
+ if (!format || dataOffset < 0 || dataLength === 0) throw new Error("WAV audio is missing format or data chunks");
44
+ if (format.channels < 1 || format.channels > 8 || format.sampleRate < 8e3) throw new Error("Unsupported WAV channel count or sample rate");
45
+ const bytesPerSample = format.bitsPerSample / 8;
46
+ if (!Number.isInteger(bytesPerSample) || bytesPerSample < 2) throw new Error(`Unsupported WAV bit depth: ${format.bitsPerSample}`);
47
+ const frameBytes = bytesPerSample * format.channels;
48
+ const frameCount = Math.floor(dataLength / frameBytes);
49
+ const mono = new Float32Array(frameCount);
50
+ for (let frame = 0; frame < frameCount; frame += 1) {
51
+ let sum = 0;
52
+ for (let channel = 0; channel < format.channels; channel += 1) {
53
+ const sampleOffset = dataOffset + frame * frameBytes + channel * bytesPerSample;
54
+ if (format.audioFormat === 1 && format.bitsPerSample === 16) sum += buffer.readInt16LE(sampleOffset) / 32768;
55
+ else if (format.audioFormat === 3 && format.bitsPerSample === 32) sum += buffer.readFloatLE(sampleOffset);
56
+ else throw new Error(`Unsupported WAV encoding: format ${format.audioFormat}, ${format.bitsPerSample}-bit`);
57
+ }
58
+ mono[frame] = Math.max(-1, Math.min(1, sum / format.channels));
59
+ }
60
+ return {
61
+ samples: resampleLinear(mono, format.sampleRate, targetRate),
62
+ sampleRate: targetRate,
63
+ durationSeconds: frameCount / format.sampleRate
64
+ };
65
+ }
66
+ //#endregion
67
+ export { decodeWavToMonoFloat32 };
68
+
69
+ //# sourceMappingURL=wav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wav.js","names":[],"sources":["../../../../src/voice/local/wav.ts"],"sourcesContent":["export interface DecodedPcmAudio {\n samples: Float32Array;\n sampleRate: number;\n durationSeconds: number;\n}\n\nfunction readChunkId(buffer: Buffer, offset: number): string {\n return buffer.toString('ascii', offset, offset + 4);\n}\n\nfunction resampleLinear(input: Float32Array, sourceRate: number, targetRate: number): Float32Array {\n if (sourceRate === targetRate) return input;\n const outputLength = Math.max(1, Math.round((input.length * targetRate) / sourceRate));\n const output = new Float32Array(outputLength);\n const ratio = sourceRate / targetRate;\n for (let i = 0; i < outputLength; i += 1) {\n const sourcePosition = i * ratio;\n const left = Math.min(input.length - 1, Math.floor(sourcePosition));\n const right = Math.min(input.length - 1, left + 1);\n const fraction = sourcePosition - left;\n output[i] = input[left]! * (1 - fraction) + input[right]! * fraction;\n }\n return output;\n}\n\nexport function decodeWavToMonoFloat32(buffer: Buffer, targetRate = 16_000): DecodedPcmAudio {\n if (buffer.length < 44 || readChunkId(buffer, 0) !== 'RIFF' || readChunkId(buffer, 8) !== 'WAVE') {\n throw new Error('Local STT currently requires PCM WAV audio');\n }\n\n let offset = 12;\n let format: { audioFormat: number; channels: number; sampleRate: number; bitsPerSample: number } | undefined;\n let dataOffset = -1;\n let dataLength = 0;\n while (offset + 8 <= buffer.length) {\n const id = readChunkId(buffer, offset);\n const length = buffer.readUInt32LE(offset + 4);\n const bodyOffset = offset + 8;\n if (bodyOffset + length > buffer.length) break;\n if (id === 'fmt ' && length >= 16) {\n format = {\n audioFormat: buffer.readUInt16LE(bodyOffset),\n channels: buffer.readUInt16LE(bodyOffset + 2),\n sampleRate: buffer.readUInt32LE(bodyOffset + 4),\n bitsPerSample: buffer.readUInt16LE(bodyOffset + 14),\n };\n } else if (id === 'data') {\n dataOffset = bodyOffset;\n dataLength = length;\n break;\n }\n offset = bodyOffset + length + (length % 2);\n }\n\n if (!format || dataOffset < 0 || dataLength === 0) {\n throw new Error('WAV audio is missing format or data chunks');\n }\n if (format.channels < 1 || format.channels > 8 || format.sampleRate < 8_000) {\n throw new Error('Unsupported WAV channel count or sample rate');\n }\n const bytesPerSample = format.bitsPerSample / 8;\n if (!Number.isInteger(bytesPerSample) || bytesPerSample < 2) {\n throw new Error(`Unsupported WAV bit depth: ${format.bitsPerSample}`);\n }\n const frameBytes = bytesPerSample * format.channels;\n const frameCount = Math.floor(dataLength / frameBytes);\n const mono = new Float32Array(frameCount);\n for (let frame = 0; frame < frameCount; frame += 1) {\n let sum = 0;\n for (let channel = 0; channel < format.channels; channel += 1) {\n const sampleOffset = dataOffset + frame * frameBytes + channel * bytesPerSample;\n if (format.audioFormat === 1 && format.bitsPerSample === 16) {\n sum += buffer.readInt16LE(sampleOffset) / 32768;\n } else if (format.audioFormat === 3 && format.bitsPerSample === 32) {\n sum += buffer.readFloatLE(sampleOffset);\n } else {\n throw new Error(`Unsupported WAV encoding: format ${format.audioFormat}, ${format.bitsPerSample}-bit`);\n }\n }\n mono[frame] = Math.max(-1, Math.min(1, sum / format.channels));\n }\n\n const samples = resampleLinear(mono, format.sampleRate, targetRate);\n return {\n samples,\n sampleRate: targetRate,\n durationSeconds: frameCount / format.sampleRate,\n };\n}\n"],"mappings":";AAMA,SAAS,YAAY,QAAgB,QAAwB;AAC3D,QAAO,OAAO,SAAS,SAAS,QAAQ,SAAS,EAAE;;AAGrD,SAAS,eAAe,OAAqB,YAAoB,YAAkC;AACjG,KAAI,eAAe,WAAY,QAAO;CACtC,MAAM,eAAe,KAAK,IAAI,GAAG,KAAK,MAAO,MAAM,SAAS,aAAc,WAAW,CAAC;CACtF,MAAM,SAAS,IAAI,aAAa,aAAa;CAC7C,MAAM,QAAQ,aAAa;AAC3B,MAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK,GAAG;EACxC,MAAM,iBAAiB,IAAI;EAC3B,MAAM,OAAO,KAAK,IAAI,MAAM,SAAS,GAAG,KAAK,MAAM,eAAe,CAAC;EACnE,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS,GAAG,OAAO,EAAE;EAClD,MAAM,WAAW,iBAAiB;AAClC,SAAO,KAAK,MAAM,SAAU,IAAI,YAAY,MAAM,SAAU;;AAE9D,QAAO;;AAGT,SAAgB,uBAAuB,QAAgB,aAAa,MAAyB;AAC3F,KAAI,OAAO,SAAS,MAAM,YAAY,QAAQ,EAAE,KAAK,UAAU,YAAY,QAAQ,EAAE,KAAK,OACxF,OAAM,IAAI,MAAM,6CAA6C;CAG/D,IAAI,SAAS;CACb,IAAI;CACJ,IAAI,aAAa;CACjB,IAAI,aAAa;AACjB,QAAO,SAAS,KAAK,OAAO,QAAQ;EAClC,MAAM,KAAK,YAAY,QAAQ,OAAO;EACtC,MAAM,SAAS,OAAO,aAAa,SAAS,EAAE;EAC9C,MAAM,aAAa,SAAS;AAC5B,MAAI,aAAa,SAAS,OAAO,OAAQ;AACzC,MAAI,OAAO,UAAU,UAAU,GAC7B,UAAS;GACP,aAAa,OAAO,aAAa,WAAW;GAC5C,UAAU,OAAO,aAAa,aAAa,EAAE;GAC7C,YAAY,OAAO,aAAa,aAAa,EAAE;GAC/C,eAAe,OAAO,aAAa,aAAa,GAAG;GACpD;WACQ,OAAO,QAAQ;AACxB,gBAAa;AACb,gBAAa;AACb;;AAEF,WAAS,aAAa,SAAU,SAAS;;AAG3C,KAAI,CAAC,UAAU,aAAa,KAAK,eAAe,EAC9C,OAAM,IAAI,MAAM,6CAA6C;AAE/D,KAAI,OAAO,WAAW,KAAK,OAAO,WAAW,KAAK,OAAO,aAAa,IACpE,OAAM,IAAI,MAAM,+CAA+C;CAEjE,MAAM,iBAAiB,OAAO,gBAAgB;AAC9C,KAAI,CAAC,OAAO,UAAU,eAAe,IAAI,iBAAiB,EACxD,OAAM,IAAI,MAAM,8BAA8B,OAAO,gBAAgB;CAEvE,MAAM,aAAa,iBAAiB,OAAO;CAC3C,MAAM,aAAa,KAAK,MAAM,aAAa,WAAW;CACtD,MAAM,OAAO,IAAI,aAAa,WAAW;AACzC,MAAK,IAAI,QAAQ,GAAG,QAAQ,YAAY,SAAS,GAAG;EAClD,IAAI,MAAM;AACV,OAAK,IAAI,UAAU,GAAG,UAAU,OAAO,UAAU,WAAW,GAAG;GAC7D,MAAM,eAAe,aAAa,QAAQ,aAAa,UAAU;AACjE,OAAI,OAAO,gBAAgB,KAAK,OAAO,kBAAkB,GACvD,QAAO,OAAO,YAAY,aAAa,GAAG;YACjC,OAAO,gBAAgB,KAAK,OAAO,kBAAkB,GAC9D,QAAO,OAAO,YAAY,aAAa;OAEvC,OAAM,IAAI,MAAM,oCAAoC,OAAO,YAAY,IAAI,OAAO,cAAc,MAAM;;AAG1G,OAAK,SAAS,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,MAAM,OAAO,SAAS,CAAC;;AAIhE,QAAO;EACL,SAFc,eAAe,MAAM,OAAO,YAAY,WAE/C;EACP,YAAY;EACZ,iBAAiB,aAAa,OAAO;EACtC"}
@@ -30,6 +30,28 @@ const alibabaSttModels = [
30
30
  name: "Paraformer MTL v1 (Multilingual)"
31
31
  }
32
32
  ];
33
+ const localSttModels = [
34
+ {
35
+ id: "sensevoice-small",
36
+ name: "SenseVoice Small 中文(推荐)",
37
+ description: "约 230 MB · 普通话、粤语、中英混说"
38
+ },
39
+ {
40
+ id: "tiny",
41
+ name: "Whisper Tiny (Fastest)",
42
+ description: "About 45 MB"
43
+ },
44
+ {
45
+ id: "base",
46
+ name: "Whisper Base (Multilingual)",
47
+ description: "About 80 MB"
48
+ },
49
+ {
50
+ id: "small",
51
+ name: "Whisper Small (Higher quality)",
52
+ description: "About 245 MB"
53
+ }
54
+ ];
33
55
  const alibabaTtsModels = [
34
56
  {
35
57
  id: "qwen-tts",
@@ -133,6 +155,30 @@ const edgeVoices = [
133
155
  }
134
156
  ];
135
157
  const builtinVoiceProviderMetadata = [
158
+ {
159
+ id: "xopc-local",
160
+ capability: "stt",
161
+ displayName: "xopc Local Voice",
162
+ description: "Private on-device Chinese and multilingual transcription. Audio never leaves this device.",
163
+ aliases: ["local"],
164
+ models: localSttModels,
165
+ fields: [{
166
+ key: "model",
167
+ label: "Model",
168
+ type: "select",
169
+ options: localSttModels,
170
+ defaultValue: "sensevoice-small"
171
+ }, {
172
+ key: "language",
173
+ label: "Language hint",
174
+ type: "string",
175
+ placeholder: "zh"
176
+ }],
177
+ diagnostics: {
178
+ requiresApiKey: false,
179
+ configPath: "tools.media.audio.providers.xopc-local"
180
+ }
181
+ },
136
182
  {
137
183
  id: "alibaba",
138
184
  capability: "stt",
@@ -1 +1 @@
1
- {"version":3,"file":"builtin.js","names":[],"sources":["../../../../src/voice/metadata/builtin.ts"],"sourcesContent":["import { registerVoiceProviderMetadata } from './registry.js';\nimport type { VoiceOptionMetadata, VoiceProviderMetadata } from './types.js';\nimport { MINIMAX_TTS_MODELS, MINIMAX_TTS_VOICES, OPENAI_TTS_MODELS, OPENAI_TTS_VOICES } from '../tts/providers/index.js';\n\nfunction titleCase(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n\nfunction options(values: readonly string[], names?: Record<string, string>): VoiceOptionMetadata[] {\n return values.map((id) => ({ id, name: names?.[id] ?? titleCase(id) }));\n}\n\nconst alibabaSttModels = [\n { id: 'paraformer-v2', name: 'Paraformer v2 (Recommended)' },\n { id: 'paraformer-v1', name: 'Paraformer v1' },\n { id: 'paraformer-8k-v1', name: 'Paraformer 8k v1 (Phone)' },\n { id: 'paraformer-mtl-v1', name: 'Paraformer MTL v1 (Multilingual)' },\n];\n\nconst alibabaTtsModels = [\n { id: 'qwen-tts', name: 'Qwen TTS (Recommended)' },\n { id: 'qwen-tts-realtime', name: 'Qwen TTS Realtime' },\n { id: 'qwen3-tts-flash', name: 'Qwen3 TTS Flash' },\n { id: 'qwen3-tts-instruct-flash', name: 'Qwen3 TTS Instruct Flash' },\n];\n\nconst alibabaTtsVoices = [\n { id: 'Cherry', name: 'Cherry' },\n { id: 'Ethan', name: 'Ethan' },\n { id: 'Serena', name: 'Serena' },\n { id: 'Chelsie', name: 'Chelsie' },\n { id: 'longxiaochun', name: 'Long Xiao Chun (龙小春)' },\n { id: 'longxiaobai', name: 'Long Xiao Bai (龙小白)' },\n { id: 'longwan', name: 'Long Wan (龙婉)' },\n { id: 'longcheng', name: 'Long Cheng (龙呈)' },\n];\n\nconst edgeVoices = [\n { id: 'en-US-MichelleNeural', name: 'Michelle (US English, Female)' },\n { id: 'en-US-JennyNeural', name: 'Jenny (US English, Female)' },\n { id: 'en-US-GuyNeural', name: 'Guy (US English, Male)' },\n { id: 'en-GB-SoniaNeural', name: 'Sonia (UK English, Female)' },\n { id: 'en-GB-RyanNeural', name: 'Ryan (UK English, Male)' },\n { id: 'zh-CN-XiaoxiaoNeural', name: '晓晓 (中文, 女声)' },\n { id: 'zh-CN-YunyangNeural', name: '云扬 (中文, 男声)' },\n { id: 'zh-CN-XiaoyiNeural', name: '晓伊 (中文, 女声)' },\n { id: 'ja-JP-NanamiNeural', name: '七海 (日本語, 女性)' },\n { id: 'de-DE-KatjaNeural', name: 'Katja (Deutsch, Weiblich)' },\n { id: 'fr-FR-DeniseNeural', name: 'Denise (Français, Féminin)' },\n { id: 'es-ES-ElviraNeural', name: 'Elvira (Español, Femenino)' },\n];\n\nexport const builtinVoiceProviderMetadata: VoiceProviderMetadata[] = [\n {\n id: 'alibaba',\n capability: 'stt',\n displayName: 'Alibaba DashScope',\n description: 'DashScope Paraformer speech-to-text, strong Chinese recognition.',\n aliases: ['dashscope', 'paraformer'],\n models: alibabaSttModels,\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'DASHSCOPE_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: alibabaSttModels, defaultValue: 'paraformer-v2' },\n { key: 'language', label: 'Language hint', type: 'string', placeholder: 'zh' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['DASHSCOPE_API_KEY'], configPath: 'tools.media.audio.providers.alibaba' },\n },\n {\n id: 'openai',\n capability: 'stt',\n displayName: 'OpenAI Whisper',\n description: 'OpenAI Whisper transcription endpoint.',\n models: [{ id: 'whisper-1', name: 'Whisper-1' }],\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'OPENAI_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: [{ id: 'whisper-1', name: 'Whisper-1' }], defaultValue: 'whisper-1' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.openai.com/v1' },\n { key: 'language', label: 'Language hint', type: 'string', placeholder: 'en' },\n { key: 'prompt', label: 'Prompt', type: 'textarea' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['OPENAI_API_KEY'], configPath: 'tools.media.audio.providers.openai' },\n },\n {\n id: 'openai',\n capability: 'tts',\n displayName: 'OpenAI',\n description: 'OpenAI speech synthesis with multiple voices.',\n models: options(OPENAI_TTS_MODELS, {\n 'gpt-4o-mini-tts': 'GPT-4o Mini TTS (Latest)',\n 'tts-1': 'TTS-1 (Fast)',\n 'tts-1-hd': 'TTS-1 HD (High Quality)',\n }),\n voices: options(OPENAI_TTS_VOICES),\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'OPENAI_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: options(OPENAI_TTS_MODELS), defaultValue: 'tts-1' },\n { key: 'voice', label: 'Voice', type: 'select', options: options(OPENAI_TTS_VOICES), defaultValue: 'alloy' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.openai.com/v1' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['OPENAI_API_KEY'], configPath: 'messages.tts.providers.openai' },\n },\n {\n id: 'alibaba',\n capability: 'tts',\n displayName: 'Alibaba DashScope',\n description: 'DashScope Qwen TTS, strong Chinese voice quality.',\n models: alibabaTtsModels,\n voices: alibabaTtsVoices,\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'DASHSCOPE_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: alibabaTtsModels, defaultValue: 'qwen-tts' },\n { key: 'voice', label: 'Voice', type: 'select', options: alibabaTtsVoices, defaultValue: 'longxiaochun' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['DASHSCOPE_API_KEY'], configPath: 'messages.tts.providers.alibaba' },\n },\n {\n id: 'minimax',\n capability: 'tts',\n displayName: 'MiniMax',\n description: 'MiniMax high-quality async text-to-speech.',\n models: options(MINIMAX_TTS_MODELS),\n voices: options(MINIMAX_TTS_VOICES),\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'eyJ...', description: 'MINIMAX_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: options(MINIMAX_TTS_MODELS), defaultValue: 'speech-2.8-hd' },\n { key: 'voice', label: 'Voice', type: 'select', options: options(MINIMAX_TTS_VOICES), defaultValue: 'male-qn-qingse' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.minimaxi.com/v1' },\n { key: 'groupId', label: 'Group ID', type: 'string' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['MINIMAX_API_KEY'], configPath: 'messages.tts.providers.minimax' },\n },\n {\n id: 'edge',\n capability: 'tts',\n displayName: 'Microsoft Edge',\n description: 'Free Edge TTS endpoint, no API key required.',\n models: [{ id: 'edge-default', name: 'Edge TTS (Free)' }],\n voices: edgeVoices,\n fields: [\n { key: 'voice', label: 'Voice', type: 'select', options: edgeVoices, defaultValue: 'en-US-MichelleNeural' },\n { key: 'lang', label: 'Language', type: 'string', placeholder: 'en-US' },\n { key: 'outputFormat', label: 'Output format', type: 'string', placeholder: 'audio-24khz-48kbitrate-mono-mp3' },\n { key: 'rate', label: 'Rate', type: 'string', placeholder: '+0%' },\n { key: 'pitch', label: 'Pitch', type: 'string', placeholder: '+0Hz' },\n { key: 'volume', label: 'Volume', type: 'string', placeholder: '+0%' },\n { key: 'proxy', label: 'Proxy', type: 'string' },\n ],\n diagnostics: { requiresApiKey: false, configPath: 'messages.tts.providers.edge' },\n },\n];\n\nfor (const metadata of builtinVoiceProviderMetadata) {\n registerVoiceProviderMetadata(metadata);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"builtin.js","names":[],"sources":["../../../../src/voice/metadata/builtin.ts"],"sourcesContent":["import { registerVoiceProviderMetadata } from './registry.js';\nimport type { VoiceOptionMetadata, VoiceProviderMetadata } from './types.js';\nimport { MINIMAX_TTS_MODELS, MINIMAX_TTS_VOICES, OPENAI_TTS_MODELS, OPENAI_TTS_VOICES } from '../tts/providers/index.js';\n\nfunction titleCase(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n\nfunction options(values: readonly string[], names?: Record<string, string>): VoiceOptionMetadata[] {\n return values.map((id) => ({ id, name: names?.[id] ?? titleCase(id) }));\n}\n\nconst alibabaSttModels = [\n { id: 'paraformer-v2', name: 'Paraformer v2 (Recommended)' },\n { id: 'paraformer-v1', name: 'Paraformer v1' },\n { id: 'paraformer-8k-v1', name: 'Paraformer 8k v1 (Phone)' },\n { id: 'paraformer-mtl-v1', name: 'Paraformer MTL v1 (Multilingual)' },\n];\n\nconst localSttModels = [\n {\n id: 'sensevoice-small',\n name: 'SenseVoice Small 中文(推荐)',\n description: '约 230 MB · 普通话、粤语、中英混说',\n },\n { id: 'tiny', name: 'Whisper Tiny (Fastest)', description: 'About 45 MB' },\n { id: 'base', name: 'Whisper Base (Multilingual)', description: 'About 80 MB' },\n { id: 'small', name: 'Whisper Small (Higher quality)', description: 'About 245 MB' },\n];\n\nconst alibabaTtsModels = [\n { id: 'qwen-tts', name: 'Qwen TTS (Recommended)' },\n { id: 'qwen-tts-realtime', name: 'Qwen TTS Realtime' },\n { id: 'qwen3-tts-flash', name: 'Qwen3 TTS Flash' },\n { id: 'qwen3-tts-instruct-flash', name: 'Qwen3 TTS Instruct Flash' },\n];\n\nconst alibabaTtsVoices = [\n { id: 'Cherry', name: 'Cherry' },\n { id: 'Ethan', name: 'Ethan' },\n { id: 'Serena', name: 'Serena' },\n { id: 'Chelsie', name: 'Chelsie' },\n { id: 'longxiaochun', name: 'Long Xiao Chun (龙小春)' },\n { id: 'longxiaobai', name: 'Long Xiao Bai (龙小白)' },\n { id: 'longwan', name: 'Long Wan (龙婉)' },\n { id: 'longcheng', name: 'Long Cheng (龙呈)' },\n];\n\nconst edgeVoices = [\n { id: 'en-US-MichelleNeural', name: 'Michelle (US English, Female)' },\n { id: 'en-US-JennyNeural', name: 'Jenny (US English, Female)' },\n { id: 'en-US-GuyNeural', name: 'Guy (US English, Male)' },\n { id: 'en-GB-SoniaNeural', name: 'Sonia (UK English, Female)' },\n { id: 'en-GB-RyanNeural', name: 'Ryan (UK English, Male)' },\n { id: 'zh-CN-XiaoxiaoNeural', name: '晓晓 (中文, 女声)' },\n { id: 'zh-CN-YunyangNeural', name: '云扬 (中文, 男声)' },\n { id: 'zh-CN-XiaoyiNeural', name: '晓伊 (中文, 女声)' },\n { id: 'ja-JP-NanamiNeural', name: '七海 (日本語, 女性)' },\n { id: 'de-DE-KatjaNeural', name: 'Katja (Deutsch, Weiblich)' },\n { id: 'fr-FR-DeniseNeural', name: 'Denise (Français, Féminin)' },\n { id: 'es-ES-ElviraNeural', name: 'Elvira (Español, Femenino)' },\n];\n\nexport const builtinVoiceProviderMetadata: VoiceProviderMetadata[] = [\n {\n id: 'xopc-local',\n capability: 'stt',\n displayName: 'xopc Local Voice',\n description: 'Private on-device Chinese and multilingual transcription. Audio never leaves this device.',\n aliases: ['local'],\n models: localSttModels,\n fields: [\n { key: 'model', label: 'Model', type: 'select', options: localSttModels, defaultValue: 'sensevoice-small' },\n { key: 'language', label: 'Language hint', type: 'string', placeholder: 'zh' },\n ],\n diagnostics: { requiresApiKey: false, configPath: 'tools.media.audio.providers.xopc-local' },\n },\n {\n id: 'alibaba',\n capability: 'stt',\n displayName: 'Alibaba DashScope',\n description: 'DashScope Paraformer speech-to-text, strong Chinese recognition.',\n aliases: ['dashscope', 'paraformer'],\n models: alibabaSttModels,\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'DASHSCOPE_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: alibabaSttModels, defaultValue: 'paraformer-v2' },\n { key: 'language', label: 'Language hint', type: 'string', placeholder: 'zh' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['DASHSCOPE_API_KEY'], configPath: 'tools.media.audio.providers.alibaba' },\n },\n {\n id: 'openai',\n capability: 'stt',\n displayName: 'OpenAI Whisper',\n description: 'OpenAI Whisper transcription endpoint.',\n models: [{ id: 'whisper-1', name: 'Whisper-1' }],\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'OPENAI_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: [{ id: 'whisper-1', name: 'Whisper-1' }], defaultValue: 'whisper-1' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.openai.com/v1' },\n { key: 'language', label: 'Language hint', type: 'string', placeholder: 'en' },\n { key: 'prompt', label: 'Prompt', type: 'textarea' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['OPENAI_API_KEY'], configPath: 'tools.media.audio.providers.openai' },\n },\n {\n id: 'openai',\n capability: 'tts',\n displayName: 'OpenAI',\n description: 'OpenAI speech synthesis with multiple voices.',\n models: options(OPENAI_TTS_MODELS, {\n 'gpt-4o-mini-tts': 'GPT-4o Mini TTS (Latest)',\n 'tts-1': 'TTS-1 (Fast)',\n 'tts-1-hd': 'TTS-1 HD (High Quality)',\n }),\n voices: options(OPENAI_TTS_VOICES),\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'OPENAI_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: options(OPENAI_TTS_MODELS), defaultValue: 'tts-1' },\n { key: 'voice', label: 'Voice', type: 'select', options: options(OPENAI_TTS_VOICES), defaultValue: 'alloy' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.openai.com/v1' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['OPENAI_API_KEY'], configPath: 'messages.tts.providers.openai' },\n },\n {\n id: 'alibaba',\n capability: 'tts',\n displayName: 'Alibaba DashScope',\n description: 'DashScope Qwen TTS, strong Chinese voice quality.',\n models: alibabaTtsModels,\n voices: alibabaTtsVoices,\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'DASHSCOPE_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: alibabaTtsModels, defaultValue: 'qwen-tts' },\n { key: 'voice', label: 'Voice', type: 'select', options: alibabaTtsVoices, defaultValue: 'longxiaochun' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['DASHSCOPE_API_KEY'], configPath: 'messages.tts.providers.alibaba' },\n },\n {\n id: 'minimax',\n capability: 'tts',\n displayName: 'MiniMax',\n description: 'MiniMax high-quality async text-to-speech.',\n models: options(MINIMAX_TTS_MODELS),\n voices: options(MINIMAX_TTS_VOICES),\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'eyJ...', description: 'MINIMAX_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: options(MINIMAX_TTS_MODELS), defaultValue: 'speech-2.8-hd' },\n { key: 'voice', label: 'Voice', type: 'select', options: options(MINIMAX_TTS_VOICES), defaultValue: 'male-qn-qingse' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.minimaxi.com/v1' },\n { key: 'groupId', label: 'Group ID', type: 'string' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['MINIMAX_API_KEY'], configPath: 'messages.tts.providers.minimax' },\n },\n {\n id: 'edge',\n capability: 'tts',\n displayName: 'Microsoft Edge',\n description: 'Free Edge TTS endpoint, no API key required.',\n models: [{ id: 'edge-default', name: 'Edge TTS (Free)' }],\n voices: edgeVoices,\n fields: [\n { key: 'voice', label: 'Voice', type: 'select', options: edgeVoices, defaultValue: 'en-US-MichelleNeural' },\n { key: 'lang', label: 'Language', type: 'string', placeholder: 'en-US' },\n { key: 'outputFormat', label: 'Output format', type: 'string', placeholder: 'audio-24khz-48kbitrate-mono-mp3' },\n { key: 'rate', label: 'Rate', type: 'string', placeholder: '+0%' },\n { key: 'pitch', label: 'Pitch', type: 'string', placeholder: '+0Hz' },\n { key: 'volume', label: 'Volume', type: 'string', placeholder: '+0%' },\n { key: 'proxy', label: 'Proxy', type: 'string' },\n ],\n diagnostics: { requiresApiKey: false, configPath: 'messages.tts.providers.edge' },\n },\n];\n\nfor (const metadata of builtinVoiceProviderMetadata) {\n registerVoiceProviderMetadata(metadata);\n}\n"],"mappings":"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"}
@@ -19,7 +19,13 @@ function resolveSTTProviderConfig(providerId, config, entry) {
19
19
  }
20
20
  const fields = readSttProviderFields(resolveSttProviderConfigSlice(providerId, config), entry);
21
21
  const apiKey = resolveApiKey(providerId, fields, plugin.envKey);
22
- if (plugin.requiresApiKey !== false && !apiKey) {
22
+ const requiresApiKey = plugin.requiresApiKey !== false;
23
+ const model = fields.model ?? entry?.model ?? plugin.defaultModels?.audio;
24
+ if (plugin.isConfigured && !plugin.isConfigured({ model })) {
25
+ log.debug({ providerId }, `STT provider "${providerId}" runtime is not ready; skipping`);
26
+ return null;
27
+ }
28
+ if (requiresApiKey && !apiKey) {
23
29
  log.debug({ providerId }, `STT provider "${providerId}" missing API key; skipping`);
24
30
  return null;
25
31
  }
@@ -28,7 +34,7 @@ function resolveSTTProviderConfig(providerId, config, entry) {
28
34
  ...apiKey ? { apiKey } : {},
29
35
  ...fields.baseUrl ? { baseUrl: fields.baseUrl } : {},
30
36
  ...fields.headers ? { headers: fields.headers } : {},
31
- ...fields.model ?? entry?.model ?? plugin.defaultModels?.audio ? { model: fields.model ?? entry?.model ?? plugin.defaultModels?.audio } : {},
37
+ ...model ? { model } : {},
32
38
  ...fields.language ? { language: fields.language } : {},
33
39
  ...fields.prompt ? { prompt: fields.prompt } : {}
34
40
  };
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","names":[],"sources":["../../../../src/voice/stt/factory.ts"],"sourcesContent":["/**\n * STT factory — registry-driven provider chain construction.\n *\n * Resolves STTConfig slices into `AudioProviderResolvedConfig` arrays that are\n * consumed by `runAudioTranscription`. Side-effect import of `./providers/index.js`\n * ensures all built-in STT providers self-register with the media-understanding\n * registry before the first lookup.\n */\n\nimport { createLogger } from '../../utils/logger.js';\n\nimport './providers/index.js'; // side-effect: register built-in STT providers\nimport { resolveModelEntries } from '../../media-understanding/resolve-entries.js';\nimport {\n getMediaUnderstandingProvider,\n listMediaUnderstandingProviders,\n} from '../../media-understanding/registry.js';\nimport type { AudioProviderResolvedConfig } from '../../media-understanding/audio-transcription-runner.js';\nimport {\n STT_LEGACY_ENV_KEYS,\n readSttProviderFields,\n resolveSttProviderConfigSlice,\n} from './config-slice.js';\nimport type { MediaUnderstandingModelEntry, STTConfig } from './types.js';\n\nconst log = createLogger('STT:Factory');\n\nfunction resolveApiKey(\n providerId: string,\n fields: ReturnType<typeof readSttProviderFields>,\n envKey?: string,\n): string | undefined {\n return (\n fields.apiKey ??\n (envKey ? process.env[envKey] : undefined) ??\n process.env[STT_LEGACY_ENV_KEYS[providerId] ?? '']\n );\n}\n\n/** Resolve a single STT provider config slice → runner-shaped resolved config, or null when unavailable. */\nexport function resolveSTTProviderConfig(\n providerId: string,\n config: STTConfig,\n entry?: MediaUnderstandingModelEntry,\n): AudioProviderResolvedConfig | null {\n const plugin = getMediaUnderstandingProvider(providerId);\n if (!plugin || typeof plugin.transcribeAudio !== 'function') {\n log.warn(\n { providerId },\n `STT provider \"${providerId}\" is not registered or does not implement transcribeAudio`,\n );\n return null;\n }\n\n const slice = resolveSttProviderConfigSlice(providerId, config);\n const fields = readSttProviderFields(slice, entry);\n const apiKey = resolveApiKey(providerId, fields, plugin.envKey);\n const requiresApiKey = plugin.requiresApiKey !== false;\n if (requiresApiKey && !apiKey) {\n log.debug({ providerId }, `STT provider \"${providerId}\" missing API key; skipping`);\n return null;\n }\n\n return {\n id: providerId,\n ...(apiKey ? { apiKey } : {}),\n ...(fields.baseUrl ? { baseUrl: fields.baseUrl } : {}),\n ...(fields.headers ? { headers: fields.headers } : {}),\n ...(fields.model ?? entry?.model ?? plugin.defaultModels?.audio\n ? { model: fields.model ?? entry?.model ?? plugin.defaultModels?.audio }\n : {}),\n ...(fields.language ? { language: fields.language } : {}),\n ...(fields.prompt ? { prompt: fields.prompt } : {}),\n };\n}\n\nfunction sortAudioProvidersByAutoPriority(\n plugins: ReturnType<typeof listMediaUnderstandingProviders>,\n): string[] {\n return [...plugins]\n .filter(\n (plugin) =>\n plugin.capabilities?.includes('audio') && typeof plugin.transcribeAudio === 'function',\n )\n .sort((left, right) => {\n const leftOrder = left.autoPriority?.audio ?? Number.MAX_SAFE_INTEGER;\n const rightOrder = right.autoPriority?.audio ?? Number.MAX_SAFE_INTEGER;\n if (leftOrder !== rightOrder) {\n return leftOrder - rightOrder;\n }\n return left.id.localeCompare(right.id);\n })\n .map((plugin) => plugin.id);\n}\n\n/** Resolve provider order: explicit fallback when enabled, otherwise auto-select by registry order. */\nexport function resolveSTTProviderOrder(\n primary: string,\n fallback: STTConfig['fallback'] | undefined,\n config: STTConfig,\n): string[] {\n if (fallback?.enabled && fallback.order.length > 0) {\n const order: string[] = [primary];\n for (const providerId of fallback.order) {\n if (providerId !== primary && !order.includes(providerId)) {\n order.push(providerId);\n }\n }\n return order;\n }\n\n const configured = sortAudioProvidersByAutoPriority(listMediaUnderstandingProviders()).filter(\n (providerId) => resolveSTTProviderConfig(providerId, config) !== null,\n );\n\n const order: string[] = [primary];\n for (const providerId of configured) {\n if (providerId !== primary && !order.includes(providerId)) {\n order.push(providerId);\n }\n }\n return order;\n}\n\nfunction resolveModelEntryChain(config: STTConfig): AudioProviderResolvedConfig[] {\n const entries = resolveModelEntries({\n capability: 'audio',\n capabilityModels: config.models,\n sharedModels: config.sharedModels,\n });\n const chain: AudioProviderResolvedConfig[] = [];\n for (const entry of entries) {\n const providerId = entry.provider?.trim();\n if (!providerId) {\n continue;\n }\n const resolved = resolveSTTProviderConfig(providerId, config, entry);\n if (resolved) {\n chain.push(resolved);\n }\n }\n return chain;\n}\n\n/** Build the full chain of resolved provider configs in priority order. */\nexport function resolveSTTProviderChain(config: STTConfig): AudioProviderResolvedConfig[] {\n if (!config.enabled) {\n return [];\n }\n\n const modelChain = resolveModelEntryChain(config);\n if (modelChain.length > 0) {\n return modelChain;\n }\n\n const order = resolveSTTProviderOrder(config.provider, config.fallback, config);\n const chain: AudioProviderResolvedConfig[] = [];\n for (const providerId of order) {\n const resolved = resolveSTTProviderConfig(providerId, config);\n if (resolved) {\n chain.push(resolved);\n }\n }\n return chain;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"factory.js","names":[],"sources":["../../../../src/voice/stt/factory.ts"],"sourcesContent":["/**\n * STT factory — registry-driven provider chain construction.\n *\n * Resolves STTConfig slices into `AudioProviderResolvedConfig` arrays that are\n * consumed by `runAudioTranscription`. Side-effect import of `./providers/index.js`\n * ensures all built-in STT providers self-register with the media-understanding\n * registry before the first lookup.\n */\n\nimport { createLogger } from '../../utils/logger.js';\n\nimport './providers/index.js'; // side-effect: register built-in STT providers\nimport { resolveModelEntries } from '../../media-understanding/resolve-entries.js';\nimport {\n getMediaUnderstandingProvider,\n listMediaUnderstandingProviders,\n} from '../../media-understanding/registry.js';\nimport type { AudioProviderResolvedConfig } from '../../media-understanding/audio-transcription-runner.js';\nimport {\n STT_LEGACY_ENV_KEYS,\n readSttProviderFields,\n resolveSttProviderConfigSlice,\n} from './config-slice.js';\nimport type { MediaUnderstandingModelEntry, STTConfig } from './types.js';\n\nconst log = createLogger('STT:Factory');\n\nfunction resolveApiKey(\n providerId: string,\n fields: ReturnType<typeof readSttProviderFields>,\n envKey?: string,\n): string | undefined {\n return (\n fields.apiKey ??\n (envKey ? process.env[envKey] : undefined) ??\n process.env[STT_LEGACY_ENV_KEYS[providerId] ?? '']\n );\n}\n\n/** Resolve a single STT provider config slice → runner-shaped resolved config, or null when unavailable. */\nexport function resolveSTTProviderConfig(\n providerId: string,\n config: STTConfig,\n entry?: MediaUnderstandingModelEntry,\n): AudioProviderResolvedConfig | null {\n const plugin = getMediaUnderstandingProvider(providerId);\n if (!plugin || typeof plugin.transcribeAudio !== 'function') {\n log.warn(\n { providerId },\n `STT provider \"${providerId}\" is not registered or does not implement transcribeAudio`,\n );\n return null;\n }\n\n const slice = resolveSttProviderConfigSlice(providerId, config);\n const fields = readSttProviderFields(slice, entry);\n const apiKey = resolveApiKey(providerId, fields, plugin.envKey);\n const requiresApiKey = plugin.requiresApiKey !== false;\n const model = fields.model ?? entry?.model ?? plugin.defaultModels?.audio;\n if (plugin.isConfigured && !plugin.isConfigured({ model })) {\n log.debug({ providerId }, `STT provider \"${providerId}\" runtime is not ready; skipping`);\n return null;\n }\n if (requiresApiKey && !apiKey) {\n log.debug({ providerId }, `STT provider \"${providerId}\" missing API key; skipping`);\n return null;\n }\n\n return {\n id: providerId,\n ...(apiKey ? { apiKey } : {}),\n ...(fields.baseUrl ? { baseUrl: fields.baseUrl } : {}),\n ...(fields.headers ? { headers: fields.headers } : {}),\n ...(model ? { model } : {}),\n ...(fields.language ? { language: fields.language } : {}),\n ...(fields.prompt ? { prompt: fields.prompt } : {}),\n };\n}\n\nfunction sortAudioProvidersByAutoPriority(\n plugins: ReturnType<typeof listMediaUnderstandingProviders>,\n): string[] {\n return [...plugins]\n .filter(\n (plugin) =>\n plugin.capabilities?.includes('audio') && typeof plugin.transcribeAudio === 'function',\n )\n .sort((left, right) => {\n const leftOrder = left.autoPriority?.audio ?? Number.MAX_SAFE_INTEGER;\n const rightOrder = right.autoPriority?.audio ?? Number.MAX_SAFE_INTEGER;\n if (leftOrder !== rightOrder) {\n return leftOrder - rightOrder;\n }\n return left.id.localeCompare(right.id);\n })\n .map((plugin) => plugin.id);\n}\n\n/** Resolve provider order: explicit fallback when enabled, otherwise auto-select by registry order. */\nexport function resolveSTTProviderOrder(\n primary: string,\n fallback: STTConfig['fallback'] | undefined,\n config: STTConfig,\n): string[] {\n if (fallback?.enabled && fallback.order.length > 0) {\n const order: string[] = [primary];\n for (const providerId of fallback.order) {\n if (providerId !== primary && !order.includes(providerId)) {\n order.push(providerId);\n }\n }\n return order;\n }\n\n const configured = sortAudioProvidersByAutoPriority(listMediaUnderstandingProviders()).filter(\n (providerId) => resolveSTTProviderConfig(providerId, config) !== null,\n );\n\n const order: string[] = [primary];\n for (const providerId of configured) {\n if (providerId !== primary && !order.includes(providerId)) {\n order.push(providerId);\n }\n }\n return order;\n}\n\nfunction resolveModelEntryChain(config: STTConfig): AudioProviderResolvedConfig[] {\n const entries = resolveModelEntries({\n capability: 'audio',\n capabilityModels: config.models,\n sharedModels: config.sharedModels,\n });\n const chain: AudioProviderResolvedConfig[] = [];\n for (const entry of entries) {\n const providerId = entry.provider?.trim();\n if (!providerId) {\n continue;\n }\n const resolved = resolveSTTProviderConfig(providerId, config, entry);\n if (resolved) {\n chain.push(resolved);\n }\n }\n return chain;\n}\n\n/** Build the full chain of resolved provider configs in priority order. */\nexport function resolveSTTProviderChain(config: STTConfig): AudioProviderResolvedConfig[] {\n if (!config.enabled) {\n return [];\n }\n\n const modelChain = resolveModelEntryChain(config);\n if (modelChain.length > 0) {\n return modelChain;\n }\n\n const order = resolveSTTProviderOrder(config.provider, config.fallback, config);\n const chain: AudioProviderResolvedConfig[] = [];\n for (const providerId of order) {\n const resolved = resolveSTTProviderConfig(providerId, config);\n if (resolved) {\n chain.push(resolved);\n }\n }\n return chain;\n}\n"],"mappings":"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"}