@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.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/Combination-DolwnuKi.js +41 -0
- package/dist/gateway/static/root/assets/{activity-center-store-jITiX2vJ.js → activity-center-store-3l9ahUqb.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DiME9u_P.js → agent-avatar-dicebear-BEUxdhZx.js} +1 -1
- package/dist/gateway/static/root/assets/agents-1bpT0xRO.js +2 -0
- package/dist/gateway/static/root/assets/app-management-settings-panel-CRHSZJWi.js +1 -0
- package/dist/gateway/static/root/assets/{appearance-settings-DUfdNQqk.js → appearance-settings-BunADYJb.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-Cc4JgVUQ.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-B-Qk6cGr.js → archive-plugin-B6vTkH2A.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-Di6EEbQX.js → attachment-load-BjZXzaNv.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-DGxvRxtO.js → automation-suggestion-card-ZDA-Stg1.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-mb0fdVpq.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-ZaOZDH2I.js → binary-plugins-DYZfLvPz.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-BTFhjWfg.js → block-editor-C3MUlYC9.js} +46 -46
- package/dist/gateway/static/root/assets/browser-settings-page-Bb0xhaMN.js +8 -0
- package/dist/gateway/static/root/assets/capability-presets-api-BV76yN9a.js +1 -0
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-CUcGKZqC.js +2 -0
- package/dist/gateway/static/root/assets/channel-recipient-api-fTlTnWB4.js +1 -0
- package/dist/gateway/static/root/assets/channels-settings-BlSDMGdy.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-CJauF1CW.js +1 -0
- package/dist/gateway/static/root/assets/{code-editor--tMO38Tr.js → code-editor-D4_k-6QN.js} +1 -1
- package/dist/gateway/static/root/assets/connector-logo-BVFNT2zW.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-CMgAFGvb.js +2 -0
- package/dist/gateway/static/root/assets/copy-to-clipboard-HOJ79TWv.js +1 -0
- package/dist/gateway/static/root/assets/{desktop-pet---yzVxVR.js → desktop-pet-_jUmoXd3.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-Dc3gqBJE.js → desktop-pet-settings-DwGOy4AQ.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-DCmOGLtK.js → directory-picker-path-field-akvF0F-u.js} +1 -1
- package/dist/gateway/static/root/assets/dist-C99RGfAH.js +1 -0
- package/dist/gateway/static/root/assets/dreaming-settings-BLetQiYY.js +3 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-ByaKTTq4.js → extension-debug-page-Cof2ncxE.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-BmRzHAZr.js → extension-page-CwWktRNz.js} +1 -1
- package/dist/gateway/static/root/assets/extension-settings-page-CuggWOIl.js +1 -0
- package/dist/gateway/static/root/assets/gateway-config-api-B7sxIbza.js +1 -0
- package/dist/gateway/static/root/assets/gateway-settings-DIv0eKMf.js +1 -0
- package/dist/gateway/static/root/assets/gateway-startup-retry-DtoLihQE.js +1 -0
- package/dist/gateway/static/root/assets/goal-create-dialog-C4Kw0mfS.js +1 -0
- package/dist/gateway/static/root/assets/goal-detail-page-BMmzYpZL.js +4 -0
- package/dist/gateway/static/root/assets/goals-config-api-B0jm5JaA.js +1 -0
- package/dist/gateway/static/root/assets/goals-page-CGeT77PC.js +2 -0
- package/dist/gateway/static/root/assets/goals-settings-N5_iL7es.js +1 -0
- package/dist/gateway/static/root/assets/heartbeat-settings-XzH3lbRR.js +1 -0
- package/dist/gateway/static/root/assets/{html-workspace-editor-COqmtESs.js → html-workspace-editor-C3DKAkMc.js} +1 -1
- package/dist/gateway/static/root/assets/index-CSLF84-Q.css +1 -0
- package/dist/gateway/static/root/assets/index-I-ZCenVw.js +259 -0
- package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-x_v0nrIi.js +1 -0
- package/dist/gateway/static/root/assets/locale-store-CiM-D9jA.js +1 -0
- package/dist/gateway/static/root/assets/logs-page-BSh1t8cD.js +7 -0
- package/dist/gateway/static/root/assets/markdown-editor-BvUebh3C.js +2 -0
- package/dist/gateway/static/root/assets/{markdown-split-Dw-yE2-y.js → markdown-split-5rJx5flj.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-D9NNNUgE.js → media-plugins-DYXccwAd.js} +1 -1
- package/dist/gateway/static/root/assets/messages-Cb1VMeqP.js +5 -0
- package/dist/gateway/static/root/assets/models-hub-panel-BNpTenAI.js +2 -0
- package/dist/gateway/static/root/assets/{notes-page-CRgbJGgk.js → notes-page-BgLyndCO.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-teklkhcE.js +4 -0
- package/dist/gateway/static/root/assets/{page-tabs-D4hkWVt_.js → page-tabs-6Al9_sGz.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-CIckv0uy.js → preview-open-alternatives-Cm3G72Hv.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-CCB_dKoh.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-VOAFAHYj.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-USYu64Ru.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-B0b42kB2.js → save-bar-controls-BYtfzu_F.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-BtjEkczK.js → schedule-field-CvIRc6yK.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-Bf0bk3i6.js → schema-form-CQuKx0n7.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-oGC8O5h_.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-D4rP66R1.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-BTKTdTNX.js → settings-collapsible-section-CF3HHM-t.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-DL4P0sAj.js → settings-form-section-CwWjoRN6.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-BV6KFNj4.js → settings-loading-skeleton-aUFmJa13.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-B_4DsX5W.js → settings-page-B2UUU9TF.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-layout-CyGlrInB.js → settings-page-layout-DqpbTWqI.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-Ck9keROI.js +1 -0
- package/dist/gateway/static/root/assets/share-preview-page-BOhlPsS1.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-BYLsCuqj.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-BNHUyVML.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-CpXbQH4n.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-D85fBBuI.js → system-settings-panel-CWxSvuwD.js} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-Bt6vAkSB.js → theme-store-C9Dxcppm.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-CwEuYrHX.js → typed-models-lib-qROAvqTw.js} +1 -1
- package/dist/gateway/static/root/assets/use-channel-catalog-DE9cpn4G.js +1 -0
- package/dist/gateway/static/root/assets/user-context-page-DbNEK4Yp.js +1 -0
- package/dist/gateway/static/root/assets/user-profile-fields-editor-CWxc0ghP.js +1 -0
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-CWM0fEpb.js → user-profile-settings-panel-DW-Up6vt.js} +1 -1
- package/dist/gateway/static/root/assets/{utils--BO6xT3u.js → utils-D53uRXVr.js} +1 -1
- package/dist/gateway/static/root/assets/{vendor-codemirror-C1WKjCk5.js → vendor-codemirror--gTq3YAI.js} +17 -17
- package/dist/gateway/static/root/assets/voice-api-key-field-Cs7MOrg0.js +1 -0
- package/dist/gateway/static/root/assets/work-item-detail-page-D1ziG1Pp.js +1 -0
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-D_x9ELJp.js +3 -0
- package/dist/gateway/static/root/assets/workflows-page-DHGV_n6i.js +11 -0
- package/dist/gateway/static/root/index.html +10 -10
- package/dist/package.js +1 -1
- package/dist/src/agent/embedded/session-runner.js +3 -3
- package/dist/src/agent/embedded/session-runner.js.map +1 -1
- package/dist/src/agent/embedded/xopc-auth-storage.d.ts +3 -1
- package/dist/src/agent/embedded/xopc-auth-storage.js +11 -1
- package/dist/src/agent/embedded/xopc-auth-storage.js.map +1 -1
- package/dist/src/agent/memory/dreaming/config.js +1 -1
- package/dist/src/agent-runtime/index.js +1 -1
- package/dist/src/agent-runtime/runtime-profile.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/config/index.js +2 -2
- package/dist/src/config/schema.d.ts +60 -5
- package/dist/src/config/schema.js +30 -9
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/connectors/composio-catalog.js +11 -2
- package/dist/src/connectors/composio-catalog.js.map +1 -1
- package/dist/src/connectors/connector-memory-sync.js +135 -26
- package/dist/src/connectors/connector-memory-sync.js.map +1 -1
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/app.js +1 -1
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.d.ts +11 -0
- package/dist/src/gateway/hono/lib/config-payload.js +1 -0
- package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +6 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js.map +1 -1
- package/dist/src/gateway/hono/routes/memory.js +11 -1
- package/dist/src/gateway/hono/routes/memory.js.map +1 -1
- package/dist/src/gateway/hono/routes/status.js +3 -0
- package/dist/src/gateway/hono/routes/status.js.map +1 -1
- package/dist/src/gateway/hono/routes/voice.d.ts +5 -5
- package/dist/src/gateway/hono/routes/voice.js +288 -62
- package/dist/src/gateway/hono/routes/voice.js.map +1 -1
- package/dist/src/gateway/hono/routes/you.js +5 -1
- package/dist/src/gateway/hono/routes/you.js.map +1 -1
- package/dist/src/gateway/service.d.ts +8 -0
- package/dist/src/gateway/service.js +41 -11
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/knowledge/connected-knowledge-pipeline.d.ts +21 -0
- package/dist/src/knowledge/connected-knowledge-pipeline.js +267 -0
- package/dist/src/knowledge/connected-knowledge-pipeline.js.map +1 -0
- package/dist/src/knowledge/coordinator.d.ts +10 -0
- package/dist/src/knowledge/coordinator.js +52 -0
- package/dist/src/knowledge/coordinator.js.map +1 -0
- package/dist/src/knowledge/index.d.ts +2 -0
- package/dist/src/knowledge/index.js +3 -1
- package/dist/src/knowledge/ingestion-service.d.ts +7 -1
- package/dist/src/knowledge/ingestion-service.js +28 -0
- package/dist/src/knowledge/ingestion-service.js.map +1 -1
- package/dist/src/knowledge/types.d.ts +18 -0
- package/dist/src/media-understanding/audio-transcription-runner.js +5 -3
- package/dist/src/media-understanding/audio-transcription-runner.js.map +1 -1
- package/dist/src/media-understanding/runner.js +8 -3
- package/dist/src/media-understanding/runner.js.map +1 -1
- package/dist/src/media-understanding/types.d.ts +7 -0
- package/dist/src/storage/sqlite/index.d.ts +1 -1
- package/dist/src/storage/sqlite/index.js +2 -2
- package/dist/src/storage/sqlite/knowledge-repository.d.ts +26 -1
- package/dist/src/storage/sqlite/knowledge-repository.js +120 -6
- package/dist/src/storage/sqlite/knowledge-repository.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/037_connected_knowledge_pipeline.sql +47 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/user-context/projection.d.ts +13 -4
- package/dist/src/user-context/projection.js +19 -7
- package/dist/src/user-context/projection.js.map +1 -1
- package/dist/src/voice/language-profile.d.ts +20 -0
- package/dist/src/voice/language-profile.js +156 -0
- package/dist/src/voice/language-profile.js.map +1 -0
- package/dist/src/voice/local/model-files.d.ts +14 -0
- package/dist/src/voice/local/model-files.js +94 -0
- package/dist/src/voice/local/model-files.js.map +1 -0
- package/dist/src/voice/local/model-manager.d.ts +18 -0
- package/dist/src/voice/local/model-manager.js +135 -0
- package/dist/src/voice/local/model-manager.js.map +1 -0
- package/dist/src/voice/local/models.d.ts +28 -0
- package/dist/src/voice/local/models.js +101 -0
- package/dist/src/voice/local/models.js.map +1 -0
- package/dist/src/voice/local/runtime-client.d.ts +26 -0
- package/dist/src/voice/local/runtime-client.js +166 -0
- package/dist/src/voice/local/runtime-client.js.map +1 -0
- package/dist/src/voice/local/runtime-dispatcher.d.ts +9 -0
- package/dist/src/voice/local/runtime-dispatcher.js +22 -0
- package/dist/src/voice/local/runtime-dispatcher.js.map +1 -0
- package/dist/src/voice/local/runtime-worker.d.ts +1 -0
- package/dist/src/voice/local/runtime-worker.js +249 -0
- package/dist/src/voice/local/runtime-worker.js.map +1 -0
- package/dist/src/voice/local/wav.d.ts +6 -0
- package/dist/src/voice/local/wav.js +69 -0
- package/dist/src/voice/local/wav.js.map +1 -0
- package/dist/src/voice/metadata/builtin.js +46 -0
- package/dist/src/voice/metadata/builtin.js.map +1 -1
- package/dist/src/voice/stt/factory.js +8 -2
- package/dist/src/voice/stt/factory.js.map +1 -1
- package/dist/src/voice/stt/providers/alibaba-transcription.js +14 -8
- package/dist/src/voice/stt/providers/alibaba-transcription.js.map +1 -1
- package/dist/src/voice/stt/providers/index.d.ts +1 -0
- package/dist/src/voice/stt/providers/index.js +2 -1
- package/dist/src/voice/stt/providers/local-transcription.d.ts +2 -0
- package/dist/src/voice/stt/providers/local-transcription.js +51 -0
- package/dist/src/voice/stt/providers/local-transcription.js.map +1 -0
- package/dist/src/voice/stt/providers/openai-transcription.js +5 -3
- package/dist/src/voice/stt/providers/openai-transcription.js.map +1 -1
- package/dist/src/voice/stt/transcribe-core.js +5 -4
- package/dist/src/voice/stt/transcribe-core.js.map +1 -1
- package/dist/src/voice/stt/types.d.ts +4 -0
- package/dist/src/voice/stt/types.js +5 -4
- package/dist/src/voice/stt/types.js.map +1 -1
- package/dist/src/voice/tts/providers/edge-speech.js +2 -2
- package/dist/src/voice/tts/providers/edge-speech.js.map +1 -1
- package/dist/src/voice/tts/types.js +3 -3
- package/dist/src/voice/tts/types.js.map +1 -1
- package/package.json +4 -2
- package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +0 -41
- package/dist/gateway/static/root/assets/agents-DzBXhZXO.js +0 -2
- package/dist/gateway/static/root/assets/app-management-settings-panel-Cuo7SbUP.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-CKuKK5AU.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-D-FF_WKV.js +0 -4
- package/dist/gateway/static/root/assets/browser-settings-page-BvKLdEQt.js +0 -8
- package/dist/gateway/static/root/assets/capability-presets-api-BU-pVZiZ.js +0 -1
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-DgyeEwzh.js +0 -2
- package/dist/gateway/static/root/assets/channel-recipient-api-DuulnhIj.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-BbVsdD_I.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-B5lVrUsN.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-B8QqVy3I.js +0 -2
- package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +0 -1
- package/dist/gateway/static/root/assets/dreaming-settings-BgT5KZVe.js +0 -3
- package/dist/gateway/static/root/assets/extension-settings-page-JUtwIhbF.js +0 -1
- package/dist/gateway/static/root/assets/fetch-8QNN25Sh.js +0 -1
- package/dist/gateway/static/root/assets/gateway-config-api-CxicF50_.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-Bz2I_EO5.js +0 -1
- package/dist/gateway/static/root/assets/gateway-startup-retry-5YBnS9Fj.js +0 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-VcoyuZGN.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-Cilkap2G.js +0 -4
- package/dist/gateway/static/root/assets/goals-config-api-CwcHRdC0.js +0 -1
- package/dist/gateway/static/root/assets/goals-page-C6eoni4z.js +0 -2
- package/dist/gateway/static/root/assets/goals-settings-o4i-qAOS.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-DQQkETX8.js +0 -1
- package/dist/gateway/static/root/assets/index-BKRqeRHm.js +0 -257
- package/dist/gateway/static/root/assets/index-CphxClbs.css +0 -1
- package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-LT6qB-ua.js +0 -1
- package/dist/gateway/static/root/assets/locale-store-Ba72bFmP.js +0 -1
- package/dist/gateway/static/root/assets/logs-page-Dli2e3fS.js +0 -7
- package/dist/gateway/static/root/assets/markdown-editor-jD1LPiP-.js +0 -2
- package/dist/gateway/static/root/assets/messages-CzKxM6I4.js +0 -5
- package/dist/gateway/static/root/assets/models-hub-panel-DVn2Q1Ik.js +0 -2
- package/dist/gateway/static/root/assets/notes-workbench-BxZRHLWx.js +0 -2
- package/dist/gateway/static/root/assets/project-detail-page-Doz53xLn.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-DoMtcRLg.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-BeHVEgrm.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-Bk4bQsBe.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DdFyaRMm.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-DJ1_MLgX.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-BQBV7YrV.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-0VuFLBeU.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-DO87Ckeo.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-D3VdtxAn.js +0 -2
- package/dist/gateway/static/root/assets/use-channel-catalog-COXIfXcl.js +0 -1
- package/dist/gateway/static/root/assets/user-context-page-m9JVQ73F.js +0 -1
- package/dist/gateway/static/root/assets/user-profile-fields-editor-DtQiuY7E.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-LoT3wgZ9.js +0 -1
- package/dist/gateway/static/root/assets/work-item-detail-page-DSTMz_mO.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-B35lJzvm.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-BfSm9oR8.js +0 -11
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createLogger } from "../../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../../utils/logger.js";
|
|
3
3
|
import { ProviderHttpError } from "../../../media-shared/http/provider-http-errors.js";
|
|
4
|
-
import { fetchWithTimeoutGuarded, postJsonRequest } from "../../../media-shared/http/provider-http.js";
|
|
4
|
+
import { fetchWithTimeoutGuarded, postJsonRequest, waitProviderOperationPollInterval } from "../../../media-shared/http/provider-http.js";
|
|
5
5
|
import "../../../media-shared/http/index.js";
|
|
6
6
|
import { registerMediaUnderstandingProvider } from "../../../media-understanding/registry.js";
|
|
7
7
|
//#region src/voice/stt/providers/alibaba-transcription.ts
|
|
@@ -43,7 +43,8 @@ async function submitTask(params) {
|
|
|
43
43
|
model: params.model,
|
|
44
44
|
input: { file_urls: [params.audioDataUrl] },
|
|
45
45
|
...params.language ? { parameters: { language_hint: params.language } } : {}
|
|
46
|
-
}
|
|
46
|
+
},
|
|
47
|
+
signal: params.signal
|
|
47
48
|
})).json();
|
|
48
49
|
if (data.code) throw new Error(`Alibaba STT API error: ${data.code} - ${data.message}`);
|
|
49
50
|
if (!data.output?.task_id) {
|
|
@@ -60,6 +61,7 @@ async function pollTask(params) {
|
|
|
60
61
|
const response = await fetchWithTimeoutGuarded(url, {
|
|
61
62
|
timeoutMs: params.timeoutMs,
|
|
62
63
|
label: "Alibaba STT poll",
|
|
64
|
+
signal: params.signal,
|
|
63
65
|
init: {
|
|
64
66
|
method: "GET",
|
|
65
67
|
headers: { Authorization: `Bearer ${params.apiKey}` }
|
|
@@ -78,17 +80,18 @@ async function pollTask(params) {
|
|
|
78
80
|
if (status === "SUCCEEDED") {
|
|
79
81
|
const result = data.output?.results?.[0];
|
|
80
82
|
if (!result) throw new Error("Alibaba STT task succeeded but no results found");
|
|
81
|
-
return { text: (await fetchTranscription(result.transcription_url, params.timeoutMs)).transcripts.map((t) => t.text).join("\n") };
|
|
83
|
+
return { text: (await fetchTranscription(result.transcription_url, params.timeoutMs, params.signal)).transcripts.map((t) => t.text).join("\n") };
|
|
82
84
|
}
|
|
83
85
|
if (status === "FAILED") throw new Error("Alibaba STT task failed");
|
|
84
|
-
await
|
|
86
|
+
await waitProviderOperationPollInterval(POLL_INTERVAL_MS, params.signal);
|
|
85
87
|
}
|
|
86
88
|
throw new Error(`Alibaba STT task did not complete within ${MAX_POLL_MS}ms`);
|
|
87
89
|
}
|
|
88
|
-
async function fetchTranscription(url, timeoutMs) {
|
|
90
|
+
async function fetchTranscription(url, timeoutMs, signal) {
|
|
89
91
|
const response = await fetchWithTimeoutGuarded(url, {
|
|
90
92
|
timeoutMs,
|
|
91
|
-
label: "Alibaba STT transcription fetch"
|
|
93
|
+
label: "Alibaba STT transcription fetch",
|
|
94
|
+
signal
|
|
92
95
|
});
|
|
93
96
|
if (!response.ok) throw new ProviderHttpError({
|
|
94
97
|
label: "Alibaba STT transcription fetch",
|
|
@@ -116,12 +119,14 @@ async function transcribeAudio(req) {
|
|
|
116
119
|
model,
|
|
117
120
|
audioDataUrl,
|
|
118
121
|
...req.language ? { language: req.language } : {},
|
|
119
|
-
timeoutMs: req.timeoutMs
|
|
122
|
+
timeoutMs: req.timeoutMs,
|
|
123
|
+
signal: req.signal
|
|
120
124
|
});
|
|
121
125
|
const result = await pollTask({
|
|
122
126
|
apiKey: req.apiKey,
|
|
123
127
|
taskId,
|
|
124
|
-
timeoutMs: req.timeoutMs
|
|
128
|
+
timeoutMs: req.timeoutMs,
|
|
129
|
+
signal: req.signal
|
|
125
130
|
});
|
|
126
131
|
const durationSeconds = (Date.now() - startTime) / 1e3;
|
|
127
132
|
log.info({
|
|
@@ -136,6 +141,7 @@ async function transcribeAudio(req) {
|
|
|
136
141
|
durationSeconds
|
|
137
142
|
};
|
|
138
143
|
} catch (error) {
|
|
144
|
+
if (req.signal?.aborted) throw error;
|
|
139
145
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
140
146
|
log.error({
|
|
141
147
|
err: error,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alibaba-transcription.js","names":[],"sources":["../../../../../src/voice/stt/providers/alibaba-transcription.ts"],"sourcesContent":["/**\n * Alibaba DashScope Paraformer STT — implements MediaUnderstandingProvider.transcribeAudio.\n *\n * DashScope's async API is a 4-step dance:\n * 1. POST /api/v1/services/audio/asr/transcription with `X-DashScope-Async: enable`\n * and `input.file_urls = [data:audio/ogg;base64,...]` → returns task_id\n * 2. Poll GET /api/v1/tasks/{task_id} until task_status === 'SUCCEEDED'\n * 3. The success result contains `transcription_url` pointing at a JSON document\n * 4. Fetch that JSON to extract the actual `transcripts[].text`\n *\n * All HTTP calls go through `fetchWithTimeoutGuarded` so the SSRF guard catches\n * malicious config injection. The transcription_url is a public OSS URL\n * (CDN-hosted); we keep `allowPrivateNetwork: false` so a malicious tenant\n * cannot redirect to an internal address.\n *\n * Polling caps at 60s by default (60 * 1s). The runner's `timeoutMs` controls\n * HTTP timeouts on each individual submit/poll/fetch call, not the overall\n * poll loop ceiling.\n */\n\nimport {\n ProviderHttpError,\n fetchWithTimeoutGuarded,\n postJsonRequest,\n} from '../../../media-shared/http/index.js';\nimport { registerMediaUnderstandingProvider } from '../../../media-understanding/registry.js';\nimport type {\n AudioTranscriptionRequest,\n AudioTranscriptionResult,\n MediaUnderstandingProvider,\n} from '../../../media-understanding/types.js';\nimport { createLogger } from '../../../utils/logger.js';\n\nconst log = createLogger('STT:Alibaba');\n\nconst DEFAULT_MODEL = 'paraformer-v2';\nconst DEFAULT_BASE_URL =\n 'https://dashscope.aliyuncs.com/api/v1/services/audio/asr/transcription';\nconst TASKS_BASE_URL = 'https://dashscope.aliyuncs.com/api/v1/tasks';\nconst POLL_INTERVAL_MS = 1000;\nconst MAX_POLL_MS = 60_000;\n\ninterface TaskResponse {\n status_code?: number;\n request_id?: string;\n code?: string;\n message?: string;\n output?: {\n task_id: string;\n task_status: 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED';\n };\n}\n\ninterface TranscriptionResultEntry {\n file_url: string;\n transcription_url: string;\n subtask_status: string;\n}\n\ninterface FetchResponse {\n status_code?: number;\n request_id?: string;\n output?: {\n task_id: string;\n task_status: 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED';\n results?: TranscriptionResultEntry[];\n };\n usage?: { duration?: number };\n}\n\ninterface TranscriptionDetail {\n file_url: string;\n properties: {\n audio_format: string;\n channels: number[];\n original_sampling_rate: number;\n original_duration_in_milliseconds: number;\n };\n transcripts: Array<{\n channel_id: number;\n content_duration_in_milliseconds: number;\n text: string;\n }>;\n}\n\nasync function submitTask(params: {\n apiKey: string;\n baseUrl: string;\n model: string;\n audioDataUrl: string;\n language?: string;\n timeoutMs: number;\n}): Promise<string> {\n const response = await postJsonRequest(params.baseUrl, {\n timeoutMs: params.timeoutMs,\n label: 'Alibaba STT submit',\n headers: {\n Authorization: `Bearer ${params.apiKey}`,\n 'X-DashScope-Async': 'enable',\n },\n body: {\n model: params.model,\n input: { file_urls: [params.audioDataUrl] },\n ...(params.language ? { parameters: { language_hint: params.language } } : {}),\n },\n });\n const data = (await response.json()) as TaskResponse;\n if (data.code) {\n throw new Error(`Alibaba STT API error: ${data.code} - ${data.message}`);\n }\n if (!data.output?.task_id) {\n log.error({ response: data }, 'Alibaba STT API response missing task_id');\n throw new Error(`No task_id returned from Alibaba STT API: ${JSON.stringify(data)}`);\n }\n log.debug({ taskId: data.output.task_id }, 'Task submitted');\n return data.output.task_id;\n}\n\nasync function pollTask(params: {\n apiKey: string;\n taskId: string;\n timeoutMs: number;\n}): Promise<{ text: string }> {\n const startTime = Date.now();\n const url = `${TASKS_BASE_URL}/${params.taskId}`;\n while (Date.now() - startTime < MAX_POLL_MS) {\n const response = await fetchWithTimeoutGuarded(url, {\n timeoutMs: params.timeoutMs,\n label: 'Alibaba STT poll',\n init: {\n method: 'GET',\n headers: { Authorization: `Bearer ${params.apiKey}` },\n },\n });\n if (!response.ok) {\n const errorText = await response.text().catch(() => '');\n throw new ProviderHttpError({\n label: 'Alibaba STT poll',\n status: response.status,\n detail: errorText.slice(0, 220) || response.statusText,\n });\n }\n const data = (await response.json()) as FetchResponse;\n const status = data.output?.task_status;\n if (status === 'SUCCEEDED') {\n const result = data.output?.results?.[0];\n if (!result) {\n throw new Error('Alibaba STT task succeeded but no results found');\n }\n const transcription = await fetchTranscription(result.transcription_url, params.timeoutMs);\n const fullText = transcription.transcripts.map((t) => t.text).join('\\n');\n return { text: fullText };\n }\n if (status === 'FAILED') {\n throw new Error('Alibaba STT task failed');\n }\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n }\n throw new Error(`Alibaba STT task did not complete within ${MAX_POLL_MS}ms`);\n}\n\nasync function fetchTranscription(url: string, timeoutMs: number): Promise<TranscriptionDetail> {\n const response = await fetchWithTimeoutGuarded(url, {\n timeoutMs,\n label: 'Alibaba STT transcription fetch',\n });\n if (!response.ok) {\n throw new ProviderHttpError({\n label: 'Alibaba STT transcription fetch',\n status: response.status,\n detail: response.statusText,\n });\n }\n return (await response.json()) as TranscriptionDetail;\n}\n\nasync function transcribeAudio(req: AudioTranscriptionRequest): Promise<AudioTranscriptionResult> {\n const startTime = Date.now();\n const model = req.model ?? DEFAULT_MODEL;\n const baseUrl = req.baseUrl ?? DEFAULT_BASE_URL;\n\n // Paraformer accepts `data:` URLs directly so we don't need to upload\n // the audio first. This avoids the OSS pre-signing dance.\n const base64Audio = req.buffer.toString('base64');\n const audioDataUrl = `data:${req.mime ?? 'audio/ogg'};base64,${base64Audio}`;\n\n log.debug(\n { model, bufferSize: req.buffer.length, language: req.language, fileName: req.fileName },\n 'Sending to Alibaba Paraformer',\n );\n\n try {\n const taskId = await submitTask({\n apiKey: req.apiKey,\n baseUrl,\n model,\n audioDataUrl,\n ...(req.language ? { language: req.language } : {}),\n timeoutMs: req.timeoutMs,\n });\n const result = await pollTask({\n apiKey: req.apiKey,\n taskId,\n timeoutMs: req.timeoutMs,\n });\n const durationSeconds = (Date.now() - startTime) / 1000;\n log.info(\n { provider: 'alibaba', durationSeconds, textLength: result.text.length },\n 'Transcription completed',\n );\n return {\n text: result.text,\n model,\n ...(req.language ? { language: req.language } : {}),\n durationSeconds,\n };\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n log.error(\n { err: error, bufferSize: req.buffer.length, model },\n `Alibaba transcription failed: ${errorMsg}`,\n );\n throw new Error(`Alibaba STT failed: ${errorMsg}`);\n }\n}\n\nexport const alibabaTranscriptionProvider: MediaUnderstandingProvider = {\n id: 'alibaba',\n aliases: ['dashscope', 'paraformer'],\n capabilities: ['audio'],\n envKey: 'DASHSCOPE_API_KEY',\n defaultModels: { audio: DEFAULT_MODEL },\n // Higher priority than openai for audio because Paraformer-v2 has better\n // Chinese accuracy and is the project's typical primary STT.\n autoPriority: { audio: 10 },\n transcribeAudio,\n};\n\nregisterMediaUnderstandingProvider(alibabaTranscriptionProvider);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"alibaba-transcription.js","names":[],"sources":["../../../../../src/voice/stt/providers/alibaba-transcription.ts"],"sourcesContent":["/**\n * Alibaba DashScope Paraformer STT — implements MediaUnderstandingProvider.transcribeAudio.\n *\n * DashScope's async API is a 4-step dance:\n * 1. POST /api/v1/services/audio/asr/transcription with `X-DashScope-Async: enable`\n * and `input.file_urls = [data:audio/ogg;base64,...]` → returns task_id\n * 2. Poll GET /api/v1/tasks/{task_id} until task_status === 'SUCCEEDED'\n * 3. The success result contains `transcription_url` pointing at a JSON document\n * 4. Fetch that JSON to extract the actual `transcripts[].text`\n *\n * All HTTP calls go through `fetchWithTimeoutGuarded` so the SSRF guard catches\n * malicious config injection. The transcription_url is a public OSS URL\n * (CDN-hosted); we keep `allowPrivateNetwork: false` so a malicious tenant\n * cannot redirect to an internal address.\n *\n * Polling caps at 60s by default (60 * 1s). The runner's `timeoutMs` controls\n * HTTP timeouts on each individual submit/poll/fetch call, not the overall\n * poll loop ceiling.\n */\n\nimport {\n ProviderHttpError,\n fetchWithTimeoutGuarded,\n postJsonRequest,\n waitProviderOperationPollInterval,\n} from '../../../media-shared/http/index.js';\nimport { registerMediaUnderstandingProvider } from '../../../media-understanding/registry.js';\nimport type {\n AudioTranscriptionRequest,\n AudioTranscriptionResult,\n MediaUnderstandingProvider,\n} from '../../../media-understanding/types.js';\nimport { createLogger } from '../../../utils/logger.js';\n\nconst log = createLogger('STT:Alibaba');\n\nconst DEFAULT_MODEL = 'paraformer-v2';\nconst DEFAULT_BASE_URL =\n 'https://dashscope.aliyuncs.com/api/v1/services/audio/asr/transcription';\nconst TASKS_BASE_URL = 'https://dashscope.aliyuncs.com/api/v1/tasks';\nconst POLL_INTERVAL_MS = 1000;\nconst MAX_POLL_MS = 60_000;\n\ninterface TaskResponse {\n status_code?: number;\n request_id?: string;\n code?: string;\n message?: string;\n output?: {\n task_id: string;\n task_status: 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED';\n };\n}\n\ninterface TranscriptionResultEntry {\n file_url: string;\n transcription_url: string;\n subtask_status: string;\n}\n\ninterface FetchResponse {\n status_code?: number;\n request_id?: string;\n output?: {\n task_id: string;\n task_status: 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED';\n results?: TranscriptionResultEntry[];\n };\n usage?: { duration?: number };\n}\n\ninterface TranscriptionDetail {\n file_url: string;\n properties: {\n audio_format: string;\n channels: number[];\n original_sampling_rate: number;\n original_duration_in_milliseconds: number;\n };\n transcripts: Array<{\n channel_id: number;\n content_duration_in_milliseconds: number;\n text: string;\n }>;\n}\n\nasync function submitTask(params: {\n apiKey: string;\n baseUrl: string;\n model: string;\n audioDataUrl: string;\n language?: string;\n timeoutMs: number;\n signal?: AbortSignal;\n}): Promise<string> {\n const response = await postJsonRequest(params.baseUrl, {\n timeoutMs: params.timeoutMs,\n label: 'Alibaba STT submit',\n headers: {\n Authorization: `Bearer ${params.apiKey}`,\n 'X-DashScope-Async': 'enable',\n },\n body: {\n model: params.model,\n input: { file_urls: [params.audioDataUrl] },\n ...(params.language ? { parameters: { language_hint: params.language } } : {}),\n },\n signal: params.signal,\n });\n const data = (await response.json()) as TaskResponse;\n if (data.code) {\n throw new Error(`Alibaba STT API error: ${data.code} - ${data.message}`);\n }\n if (!data.output?.task_id) {\n log.error({ response: data }, 'Alibaba STT API response missing task_id');\n throw new Error(`No task_id returned from Alibaba STT API: ${JSON.stringify(data)}`);\n }\n log.debug({ taskId: data.output.task_id }, 'Task submitted');\n return data.output.task_id;\n}\n\nasync function pollTask(params: {\n apiKey: string;\n taskId: string;\n timeoutMs: number;\n signal?: AbortSignal;\n}): Promise<{ text: string }> {\n const startTime = Date.now();\n const url = `${TASKS_BASE_URL}/${params.taskId}`;\n while (Date.now() - startTime < MAX_POLL_MS) {\n const response = await fetchWithTimeoutGuarded(url, {\n timeoutMs: params.timeoutMs,\n label: 'Alibaba STT poll',\n signal: params.signal,\n init: {\n method: 'GET',\n headers: { Authorization: `Bearer ${params.apiKey}` },\n },\n });\n if (!response.ok) {\n const errorText = await response.text().catch(() => '');\n throw new ProviderHttpError({\n label: 'Alibaba STT poll',\n status: response.status,\n detail: errorText.slice(0, 220) || response.statusText,\n });\n }\n const data = (await response.json()) as FetchResponse;\n const status = data.output?.task_status;\n if (status === 'SUCCEEDED') {\n const result = data.output?.results?.[0];\n if (!result) {\n throw new Error('Alibaba STT task succeeded but no results found');\n }\n const transcription = await fetchTranscription(\n result.transcription_url,\n params.timeoutMs,\n params.signal,\n );\n const fullText = transcription.transcripts.map((t) => t.text).join('\\n');\n return { text: fullText };\n }\n if (status === 'FAILED') {\n throw new Error('Alibaba STT task failed');\n }\n await waitProviderOperationPollInterval(POLL_INTERVAL_MS, params.signal);\n }\n throw new Error(`Alibaba STT task did not complete within ${MAX_POLL_MS}ms`);\n}\n\nasync function fetchTranscription(\n url: string,\n timeoutMs: number,\n signal?: AbortSignal,\n): Promise<TranscriptionDetail> {\n const response = await fetchWithTimeoutGuarded(url, {\n timeoutMs,\n label: 'Alibaba STT transcription fetch',\n signal,\n });\n if (!response.ok) {\n throw new ProviderHttpError({\n label: 'Alibaba STT transcription fetch',\n status: response.status,\n detail: response.statusText,\n });\n }\n return (await response.json()) as TranscriptionDetail;\n}\n\nasync function transcribeAudio(req: AudioTranscriptionRequest): Promise<AudioTranscriptionResult> {\n const startTime = Date.now();\n const model = req.model ?? DEFAULT_MODEL;\n const baseUrl = req.baseUrl ?? DEFAULT_BASE_URL;\n\n // Paraformer accepts `data:` URLs directly so we don't need to upload\n // the audio first. This avoids the OSS pre-signing dance.\n const base64Audio = req.buffer.toString('base64');\n const audioDataUrl = `data:${req.mime ?? 'audio/ogg'};base64,${base64Audio}`;\n\n log.debug(\n { model, bufferSize: req.buffer.length, language: req.language, fileName: req.fileName },\n 'Sending to Alibaba Paraformer',\n );\n\n try {\n const taskId = await submitTask({\n apiKey: req.apiKey,\n baseUrl,\n model,\n audioDataUrl,\n ...(req.language ? { language: req.language } : {}),\n timeoutMs: req.timeoutMs,\n signal: req.signal,\n });\n const result = await pollTask({\n apiKey: req.apiKey,\n taskId,\n timeoutMs: req.timeoutMs,\n signal: req.signal,\n });\n const durationSeconds = (Date.now() - startTime) / 1000;\n log.info(\n { provider: 'alibaba', durationSeconds, textLength: result.text.length },\n 'Transcription completed',\n );\n return {\n text: result.text,\n model,\n ...(req.language ? { language: req.language } : {}),\n durationSeconds,\n };\n } catch (error) {\n if (req.signal?.aborted) {\n throw error;\n }\n const errorMsg = error instanceof Error ? error.message : String(error);\n log.error(\n { err: error, bufferSize: req.buffer.length, model },\n `Alibaba transcription failed: ${errorMsg}`,\n );\n throw new Error(`Alibaba STT failed: ${errorMsg}`);\n }\n}\n\nexport const alibabaTranscriptionProvider: MediaUnderstandingProvider = {\n id: 'alibaba',\n aliases: ['dashscope', 'paraformer'],\n capabilities: ['audio'],\n envKey: 'DASHSCOPE_API_KEY',\n defaultModels: { audio: DEFAULT_MODEL },\n // Higher priority than openai for audio because Paraformer-v2 has better\n // Chinese accuracy and is the project's typical primary STT.\n autoPriority: { audio: 10 },\n transcribeAudio,\n};\n\nregisterMediaUnderstandingProvider(alibabaTranscriptionProvider);\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { openAiTranscriptionProvider } from "./openai-transcription.js";
|
|
2
2
|
import { alibabaTranscriptionProvider } from "./alibaba-transcription.js";
|
|
3
|
-
|
|
3
|
+
import { localTranscriptionProvider } from "./local-transcription.js";
|
|
4
|
+
export { alibabaTranscriptionProvider, localTranscriptionProvider, openAiTranscriptionProvider };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createLogger } from "../../../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../../../utils/logger.js";
|
|
3
|
+
import { registerMediaUnderstandingProvider } from "../../../media-understanding/registry.js";
|
|
4
|
+
import { DEFAULT_LOCAL_VOICE_MODEL_ID, getLocalVoiceModel, hasInstalledLocalVoiceModel, isLocalVoiceModelInstalled } from "../../local/models.js";
|
|
5
|
+
import { getLocalVoiceRuntimeClient } from "../../local/runtime-client.js";
|
|
6
|
+
import { decodeWavToMonoFloat32 } from "../../local/wav.js";
|
|
7
|
+
//#region src/voice/stt/providers/local-transcription.ts
|
|
8
|
+
init_logger();
|
|
9
|
+
const log = createLogger("STT:Local");
|
|
10
|
+
async function transcribeAudio(req) {
|
|
11
|
+
const model = getLocalVoiceModel(req.model);
|
|
12
|
+
if (!isLocalVoiceModelInstalled(model.id)) throw new Error(`Local voice model "${model.id}" is not installed`);
|
|
13
|
+
const decoded = decodeWavToMonoFloat32(req.buffer);
|
|
14
|
+
const audioBytes = Buffer.from(decoded.samples.buffer, decoded.samples.byteOffset, decoded.samples.byteLength);
|
|
15
|
+
const startedAt = Date.now();
|
|
16
|
+
const result = await getLocalVoiceRuntimeClient().request("transcribe", {
|
|
17
|
+
modelId: model.id,
|
|
18
|
+
audioBase64: audioBytes.toString("base64"),
|
|
19
|
+
...req.language ? { language: req.language } : {}
|
|
20
|
+
}, {
|
|
21
|
+
signal: req.signal,
|
|
22
|
+
timeoutMs: req.timeoutMs
|
|
23
|
+
});
|
|
24
|
+
log.info({
|
|
25
|
+
model: model.id,
|
|
26
|
+
audioDurationSeconds: decoded.durationSeconds,
|
|
27
|
+
latencyMs: Date.now() - startedAt,
|
|
28
|
+
textLength: result.text.length
|
|
29
|
+
}, "Local transcription completed");
|
|
30
|
+
return {
|
|
31
|
+
text: result.text,
|
|
32
|
+
model: model.id,
|
|
33
|
+
...result.language || req.language ? { language: result.language ?? req.language } : {},
|
|
34
|
+
durationSeconds: decoded.durationSeconds
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const localTranscriptionProvider = {
|
|
38
|
+
id: "xopc-local",
|
|
39
|
+
aliases: ["local"],
|
|
40
|
+
capabilities: ["audio"],
|
|
41
|
+
requiresApiKey: false,
|
|
42
|
+
defaultModels: { audio: DEFAULT_LOCAL_VOICE_MODEL_ID },
|
|
43
|
+
autoPriority: { audio: 0 },
|
|
44
|
+
isConfigured: (options) => options?.model ? isLocalVoiceModelInstalled(options.model) : hasInstalledLocalVoiceModel(),
|
|
45
|
+
transcribeAudio
|
|
46
|
+
};
|
|
47
|
+
registerMediaUnderstandingProvider(localTranscriptionProvider);
|
|
48
|
+
//#endregion
|
|
49
|
+
export { localTranscriptionProvider };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=local-transcription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-transcription.js","names":[],"sources":["../../../../../src/voice/stt/providers/local-transcription.ts"],"sourcesContent":["import { registerMediaUnderstandingProvider } from '../../../media-understanding/registry.js';\nimport type {\n AudioTranscriptionRequest,\n AudioTranscriptionResult,\n MediaUnderstandingProvider,\n} from '../../../media-understanding/types.js';\nimport { createLogger } from '../../../utils/logger.js';\nimport { getLocalVoiceRuntimeClient } from '../../local/runtime-client.js';\nimport { decodeWavToMonoFloat32 } from '../../local/wav.js';\nimport {\n DEFAULT_LOCAL_VOICE_MODEL_ID,\n getLocalVoiceModel,\n hasInstalledLocalVoiceModel,\n isLocalVoiceModelInstalled,\n} from '../../local/models.js';\n\nconst log = createLogger('STT:Local');\n\nasync function transcribeAudio(req: AudioTranscriptionRequest): Promise<AudioTranscriptionResult> {\n const model = getLocalVoiceModel(req.model);\n if (!isLocalVoiceModelInstalled(model.id)) {\n throw new Error(`Local voice model \"${model.id}\" is not installed`);\n }\n const decoded = decodeWavToMonoFloat32(req.buffer);\n const audioBytes = Buffer.from(\n decoded.samples.buffer,\n decoded.samples.byteOffset,\n decoded.samples.byteLength,\n );\n const startedAt = Date.now();\n const result = await getLocalVoiceRuntimeClient().request<{\n text: string;\n modelId: string;\n language?: string;\n emotion?: string;\n event?: string;\n }>(\n 'transcribe',\n {\n modelId: model.id,\n audioBase64: audioBytes.toString('base64'),\n ...(req.language ? { language: req.language } : {}),\n },\n { signal: req.signal, timeoutMs: req.timeoutMs },\n );\n log.info(\n {\n model: model.id,\n audioDurationSeconds: decoded.durationSeconds,\n latencyMs: Date.now() - startedAt,\n textLength: result.text.length,\n },\n 'Local transcription completed',\n );\n return {\n text: result.text,\n model: model.id,\n ...(result.language || req.language ? { language: result.language ?? req.language } : {}),\n durationSeconds: decoded.durationSeconds,\n };\n}\n\nexport const localTranscriptionProvider: MediaUnderstandingProvider = {\n id: 'xopc-local',\n aliases: ['local'],\n capabilities: ['audio'],\n requiresApiKey: false,\n defaultModels: { audio: DEFAULT_LOCAL_VOICE_MODEL_ID },\n autoPriority: { audio: 0 },\n isConfigured: (options) => options?.model\n ? isLocalVoiceModelInstalled(options.model)\n : hasInstalledLocalVoiceModel(),\n transcribeAudio,\n};\n\nregisterMediaUnderstandingProvider(localTranscriptionProvider);\n"],"mappings":";;;;;;;aAMwD;AAUxD,MAAM,MAAM,aAAa,YAAY;AAErC,eAAe,gBAAgB,KAAmE;CAChG,MAAM,QAAQ,mBAAmB,IAAI,MAAM;AAC3C,KAAI,CAAC,2BAA2B,MAAM,GAAG,CACvC,OAAM,IAAI,MAAM,sBAAsB,MAAM,GAAG,oBAAoB;CAErE,MAAM,UAAU,uBAAuB,IAAI,OAAO;CAClD,MAAM,aAAa,OAAO,KACxB,QAAQ,QAAQ,QAChB,QAAQ,QAAQ,YAChB,QAAQ,QAAQ,WACjB;CACD,MAAM,YAAY,KAAK,KAAK;CAC5B,MAAM,SAAS,MAAM,4BAA4B,CAAC,QAOhD,cACA;EACE,SAAS,MAAM;EACf,aAAa,WAAW,SAAS,SAAS;EAC1C,GAAI,IAAI,WAAW,EAAE,UAAU,IAAI,UAAU,GAAG,EAAE;EACnD,EACD;EAAE,QAAQ,IAAI;EAAQ,WAAW,IAAI;EAAW,CACjD;AACD,KAAI,KACF;EACE,OAAO,MAAM;EACb,sBAAsB,QAAQ;EAC9B,WAAW,KAAK,KAAK,GAAG;EACxB,YAAY,OAAO,KAAK;EACzB,EACD,gCACD;AACD,QAAO;EACL,MAAM,OAAO;EACb,OAAO,MAAM;EACb,GAAI,OAAO,YAAY,IAAI,WAAW,EAAE,UAAU,OAAO,YAAY,IAAI,UAAU,GAAG,EAAE;EACxF,iBAAiB,QAAQ;EAC1B;;AAGH,MAAa,6BAAyD;CACpE,IAAI;CACJ,SAAS,CAAC,QAAQ;CAClB,cAAc,CAAC,QAAQ;CACvB,gBAAgB;CAChB,eAAe,EAAE,OAAO,8BAA8B;CACtD,cAAc,EAAE,OAAO,GAAG;CAC1B,eAAe,YAAY,SAAS,QAChC,2BAA2B,QAAQ,MAAM,GACzC,6BAA6B;CACjC;CACD;AAED,mCAAmC,2BAA2B"}
|
|
@@ -25,11 +25,12 @@ async function transcribeAudio(req) {
|
|
|
25
25
|
const model = req.model ?? DEFAULT_MODEL;
|
|
26
26
|
const client = new OpenAI({
|
|
27
27
|
apiKey: req.apiKey,
|
|
28
|
+
timeout: req.timeoutMs,
|
|
28
29
|
...req.baseUrl ? { baseURL: req.baseUrl } : {},
|
|
29
30
|
...req.headers ? { defaultHeaders: req.headers } : {}
|
|
30
31
|
});
|
|
31
32
|
const uint8Array = new Uint8Array(req.buffer.buffer, req.buffer.byteOffset, req.buffer.byteLength);
|
|
32
|
-
const
|
|
33
|
+
const file = new File([uint8Array], req.fileName, { type: req.mime ?? "audio/ogg" });
|
|
33
34
|
log.debug({
|
|
34
35
|
model,
|
|
35
36
|
bufferSize: req.buffer.length,
|
|
@@ -38,12 +39,12 @@ async function transcribeAudio(req) {
|
|
|
38
39
|
}, "Sending to OpenAI Whisper");
|
|
39
40
|
try {
|
|
40
41
|
const result = await client.audio.transcriptions.create({
|
|
41
|
-
file
|
|
42
|
+
file,
|
|
42
43
|
model,
|
|
43
44
|
...req.language ? { language: req.language } : {},
|
|
44
45
|
...req.prompt ? { prompt: req.prompt } : {},
|
|
45
46
|
response_format: "json"
|
|
46
|
-
});
|
|
47
|
+
}, req.signal ? { signal: req.signal } : void 0);
|
|
47
48
|
const durationSeconds = (Date.now() - startTime) / 1e3;
|
|
48
49
|
log.info({
|
|
49
50
|
provider: "openai",
|
|
@@ -57,6 +58,7 @@ async function transcribeAudio(req) {
|
|
|
57
58
|
durationSeconds
|
|
58
59
|
};
|
|
59
60
|
} catch (error) {
|
|
61
|
+
if (req.signal?.aborted) throw error;
|
|
60
62
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
61
63
|
log.error({
|
|
62
64
|
err: error,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-transcription.js","names":[],"sources":["../../../../../src/voice/stt/providers/openai-transcription.ts"],"sourcesContent":["/**\n * OpenAI Whisper STT — implements MediaUnderstandingProvider.transcribeAudio.\n *\n * Uses the official `openai` SDK rather than the raw HTTP path because the SDK\n * already handles multipart/form-data encoding (`file` field), streaming\n * response normalization, and per-call apiKey override via constructor\n * injection. The media-shared HTTP layer is intentionally not used here so we\n * keep parity with the SDK's own fetch (proxy support, retries, etc.); the\n * only egress is api.openai.com which is HTTPS-only and public.\n *\n * Streaming transcription is not implemented (Whisper returns a final text\n * blob). Self-registers with `capabilities: ['audio']` only — the registry's\n * `listProvidersForCapability` filter skips this provider for image/video.\n */\n\nimport OpenAI from 'openai';\n\nimport { registerMediaUnderstandingProvider } from '../../../media-understanding/registry.js';\nimport type {\n AudioTranscriptionRequest,\n AudioTranscriptionResult,\n MediaUnderstandingProvider,\n} from '../../../media-understanding/types.js';\nimport { createLogger } from '../../../utils/logger.js';\n\nconst log = createLogger('STT:OpenAI');\n\nconst DEFAULT_MODEL = 'whisper-1';\n\nasync function transcribeAudio(req: AudioTranscriptionRequest): Promise<AudioTranscriptionResult> {\n const startTime = Date.now();\n const model = req.model ?? DEFAULT_MODEL;\n\n // openai SDK supports baseURL override per-instance.\n const client = new OpenAI({\n apiKey: req.apiKey,\n ...(req.baseUrl ? { baseURL: req.baseUrl } : {}),\n ...(req.headers ? { defaultHeaders: req.headers } : {}),\n });\n\n const uint8Array = new Uint8Array(\n req.buffer.buffer as ArrayBuffer,\n req.buffer.byteOffset,\n req.buffer.byteLength,\n );\n //
|
|
1
|
+
{"version":3,"file":"openai-transcription.js","names":[],"sources":["../../../../../src/voice/stt/providers/openai-transcription.ts"],"sourcesContent":["/**\n * OpenAI Whisper STT — implements MediaUnderstandingProvider.transcribeAudio.\n *\n * Uses the official `openai` SDK rather than the raw HTTP path because the SDK\n * already handles multipart/form-data encoding (`file` field), streaming\n * response normalization, and per-call apiKey override via constructor\n * injection. The media-shared HTTP layer is intentionally not used here so we\n * keep parity with the SDK's own fetch (proxy support, retries, etc.); the\n * only egress is api.openai.com which is HTTPS-only and public.\n *\n * Streaming transcription is not implemented (Whisper returns a final text\n * blob). Self-registers with `capabilities: ['audio']` only — the registry's\n * `listProvidersForCapability` filter skips this provider for image/video.\n */\n\nimport OpenAI from 'openai';\n\nimport { registerMediaUnderstandingProvider } from '../../../media-understanding/registry.js';\nimport type {\n AudioTranscriptionRequest,\n AudioTranscriptionResult,\n MediaUnderstandingProvider,\n} from '../../../media-understanding/types.js';\nimport { createLogger } from '../../../utils/logger.js';\n\nconst log = createLogger('STT:OpenAI');\n\nconst DEFAULT_MODEL = 'whisper-1';\n\nasync function transcribeAudio(req: AudioTranscriptionRequest): Promise<AudioTranscriptionResult> {\n const startTime = Date.now();\n const model = req.model ?? DEFAULT_MODEL;\n\n // openai SDK supports baseURL override per-instance.\n const client = new OpenAI({\n apiKey: req.apiKey,\n timeout: req.timeoutMs,\n ...(req.baseUrl ? { baseURL: req.baseUrl } : {}),\n ...(req.headers ? { defaultHeaders: req.headers } : {}),\n });\n\n const uint8Array = new Uint8Array(\n req.buffer.buffer as ArrayBuffer,\n req.buffer.byteOffset,\n req.buffer.byteLength,\n );\n // Preserve the original name and MIME so provider-side format detection and\n // diagnostics reflect the real upload instead of a synthetic OGG file.\n const file = new File([uint8Array], req.fileName, { type: req.mime ?? 'audio/ogg' });\n\n log.debug(\n { model, bufferSize: req.buffer.length, language: req.language, fileName: req.fileName },\n 'Sending to OpenAI Whisper',\n );\n\n try {\n const result = await client.audio.transcriptions.create(\n {\n file,\n model,\n ...(req.language ? { language: req.language } : {}),\n ...(req.prompt ? { prompt: req.prompt } : {}),\n response_format: 'json',\n },\n req.signal ? { signal: req.signal } : undefined,\n );\n const durationSeconds = (Date.now() - startTime) / 1000;\n log.info(\n { provider: 'openai', durationSeconds, textLength: result.text?.length ?? 0 },\n 'Transcription completed',\n );\n return {\n text: result.text || '',\n model,\n ...(req.language ? { language: req.language } : {}),\n durationSeconds,\n };\n } catch (error) {\n if (req.signal?.aborted) {\n throw error;\n }\n const errorMsg = error instanceof Error ? error.message : String(error);\n log.error(\n { err: error, bufferSize: req.buffer.length, model },\n `OpenAI transcription failed: ${errorMsg}`,\n );\n throw new Error(`OpenAI STT failed: ${errorMsg}`);\n }\n}\n\nexport const openAiTranscriptionProvider: MediaUnderstandingProvider = {\n id: 'openai',\n capabilities: ['audio'],\n envKey: 'OPENAI_API_KEY',\n defaultModels: { audio: DEFAULT_MODEL },\n autoPriority: { audio: 20 }, // alibaba is preferred when both configured (Chinese-first)\n transcribeAudio,\n};\n\nregisterMediaUnderstandingProvider(openAiTranscriptionProvider);\n"],"mappings":";;;;;;;;;;;;;;;;;;;aAuBwD;AAExD,MAAM,MAAM,aAAa,aAAa;AAEtC,MAAM,gBAAgB;AAEtB,eAAe,gBAAgB,KAAmE;CAChG,MAAM,YAAY,KAAK,KAAK;CAC5B,MAAM,QAAQ,IAAI,SAAS;CAG3B,MAAM,SAAS,IAAI,OAAO;EACxB,QAAQ,IAAI;EACZ,SAAS,IAAI;EACb,GAAI,IAAI,UAAU,EAAE,SAAS,IAAI,SAAS,GAAG,EAAE;EAC/C,GAAI,IAAI,UAAU,EAAE,gBAAgB,IAAI,SAAS,GAAG,EAAE;EACvD,CAAC;CAEF,MAAM,aAAa,IAAI,WACrB,IAAI,OAAO,QACX,IAAI,OAAO,YACX,IAAI,OAAO,WACZ;CAGD,MAAM,OAAO,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,UAAU,EAAE,MAAM,IAAI,QAAQ,aAAa,CAAC;AAEpF,KAAI,MACF;EAAE;EAAO,YAAY,IAAI,OAAO;EAAQ,UAAU,IAAI;EAAU,UAAU,IAAI;EAAU,EACxF,4BACD;AAED,KAAI;EACF,MAAM,SAAS,MAAM,OAAO,MAAM,eAAe,OAC/C;GACE;GACA;GACA,GAAI,IAAI,WAAW,EAAE,UAAU,IAAI,UAAU,GAAG,EAAE;GAClD,GAAI,IAAI,SAAS,EAAE,QAAQ,IAAI,QAAQ,GAAG,EAAE;GAC5C,iBAAiB;GAClB,EACD,IAAI,SAAS,EAAE,QAAQ,IAAI,QAAQ,GAAG,KAAA,EACvC;EACD,MAAM,mBAAmB,KAAK,KAAK,GAAG,aAAa;AACnD,MAAI,KACF;GAAE,UAAU;GAAU;GAAiB,YAAY,OAAO,MAAM,UAAU;GAAG,EAC7E,0BACD;AACD,SAAO;GACL,MAAM,OAAO,QAAQ;GACrB;GACA,GAAI,IAAI,WAAW,EAAE,UAAU,IAAI,UAAU,GAAG,EAAE;GAClD;GACD;UACM,OAAO;AACd,MAAI,IAAI,QAAQ,QACd,OAAM;EAER,MAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACvE,MAAI,MACF;GAAE,KAAK;GAAO,YAAY,IAAI,OAAO;GAAQ;GAAO,EACpD,gCAAgC,WACjC;AACD,QAAM,IAAI,MAAM,sBAAsB,WAAW;;;AAIrD,MAAa,8BAA0D;CACrE,IAAI;CACJ,cAAc,CAAC,QAAQ;CACvB,QAAQ;CACR,eAAe,EAAE,OAAO,eAAe;CACvC,cAAc,EAAE,OAAO,IAAI;CAC3B;CACD;AAED,mCAAmC,4BAA4B"}
|
|
@@ -18,7 +18,7 @@ function toAttempt(attempt) {
|
|
|
18
18
|
provider: attempt.provider ?? "unknown",
|
|
19
19
|
outcome: attempt.outcome,
|
|
20
20
|
reasonCode: attempt.outcome === "success" ? "success" : classifyFailureReason(attempt.reason),
|
|
21
|
-
latencyMs: 0,
|
|
21
|
+
latencyMs: attempt.latencyMs ?? 0,
|
|
22
22
|
...attempt.reason ? { error: attempt.reason } : {}
|
|
23
23
|
};
|
|
24
24
|
}
|
|
@@ -35,10 +35,11 @@ async function transcribe(audioBuffer, config, options) {
|
|
|
35
35
|
attachments: [{
|
|
36
36
|
attachmentIndex: 0,
|
|
37
37
|
buffer: audioBuffer,
|
|
38
|
-
fileName: `audio-${Date.now()}.ogg`,
|
|
39
|
-
mime: "audio/ogg"
|
|
38
|
+
fileName: options?.fileName ?? `audio-${Date.now()}.ogg`,
|
|
39
|
+
mime: options?.mime ?? "audio/ogg"
|
|
40
40
|
}],
|
|
41
|
-
timeoutMs: config.timeoutMs ?? 6e4
|
|
41
|
+
timeoutMs: config.timeoutMs ?? 6e4,
|
|
42
|
+
signal: options?.signal
|
|
42
43
|
});
|
|
43
44
|
const attachmentDecision = result.decision.attachments[0];
|
|
44
45
|
const attempts = (attachmentDecision?.attempts ?? []).map(toAttempt);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcribe-core.js","names":[],"sources":["../../../../src/voice/stt/transcribe-core.ts"],"sourcesContent":["/**\n * STT orchestrator — `transcribe()` is the single entry point used by channel\n * adapters (telegram, webchat) and the preflight router.\n *\n * Internally delegates to `runAudioTranscription` from the media-understanding\n * runner. The runner returns a `MediaUnderstandingDecision`; we translate each\n * `decision.attachments[0].attempts[]` entry into the `STTProviderAttempt`\n * shape consumed by downstream telemetry.\n *\n * One-attachment fast path: `transcribe()` is 1-buffer-in / 1-text-out, so we\n * wrap the input as a single CapabilityAttachmentInput and unpack the first\n * output. Multi-attachment cases go through `runAudioTranscription` directly.\n */\n\nimport { createLogger } from '../../utils/logger.js';\n\nimport { runAudioTranscription } from '../../media-understanding/audio-transcription-runner.js';\nimport type {\n MediaUnderstandingAttachmentDecision,\n MediaUnderstandingModelDecision,\n} from '../../media-understanding/types.js';\nimport { resolveSTTProviderChain } from './factory.js';\nimport type {\n STTConfig,\n STTOptions,\n STTProviderAttempt,\n STTProviderFailureReason,\n STTResultWithTracking,\n} from './types.js';\n\nconst log = createLogger('STT');\n\nfunction classifyFailureReason(reason: string | undefined): STTProviderFailureReason {\n if (!reason) return 'unknown';\n const lower = reason.toLowerCase();\n if (lower.includes('not configured') || lower.includes('api key') || lower.includes('missing')) {\n return 'not_configured';\n }\n if (lower.includes('timeout') || lower.includes('timed out') || lower.includes('aborted')) {\n return 'timeout';\n }\n if (lower.includes('unsupported') || lower.includes('format')) {\n return 'unsupported_format';\n }\n return 'provider_error';\n}\n\nfunction toAttempt(attempt: MediaUnderstandingModelDecision): STTProviderAttempt {\n return {\n provider: attempt.provider ?? 'unknown',\n outcome: attempt.outcome,\n reasonCode: attempt.outcome === 'success' ? 'success' : classifyFailureReason(attempt.reason),\n
|
|
1
|
+
{"version":3,"file":"transcribe-core.js","names":[],"sources":["../../../../src/voice/stt/transcribe-core.ts"],"sourcesContent":["/**\n * STT orchestrator — `transcribe()` is the single entry point used by channel\n * adapters (telegram, webchat) and the preflight router.\n *\n * Internally delegates to `runAudioTranscription` from the media-understanding\n * runner. The runner returns a `MediaUnderstandingDecision`; we translate each\n * `decision.attachments[0].attempts[]` entry into the `STTProviderAttempt`\n * shape consumed by downstream telemetry.\n *\n * One-attachment fast path: `transcribe()` is 1-buffer-in / 1-text-out, so we\n * wrap the input as a single CapabilityAttachmentInput and unpack the first\n * output. Multi-attachment cases go through `runAudioTranscription` directly.\n */\n\nimport { createLogger } from '../../utils/logger.js';\n\nimport { runAudioTranscription } from '../../media-understanding/audio-transcription-runner.js';\nimport type {\n MediaUnderstandingAttachmentDecision,\n MediaUnderstandingModelDecision,\n} from '../../media-understanding/types.js';\nimport { resolveSTTProviderChain } from './factory.js';\nimport type {\n STTConfig,\n STTOptions,\n STTProviderAttempt,\n STTProviderFailureReason,\n STTResultWithTracking,\n} from './types.js';\n\nconst log = createLogger('STT');\n\nfunction classifyFailureReason(reason: string | undefined): STTProviderFailureReason {\n if (!reason) return 'unknown';\n const lower = reason.toLowerCase();\n if (lower.includes('not configured') || lower.includes('api key') || lower.includes('missing')) {\n return 'not_configured';\n }\n if (lower.includes('timeout') || lower.includes('timed out') || lower.includes('aborted')) {\n return 'timeout';\n }\n if (lower.includes('unsupported') || lower.includes('format')) {\n return 'unsupported_format';\n }\n return 'provider_error';\n}\n\nfunction toAttempt(attempt: MediaUnderstandingModelDecision): STTProviderAttempt {\n return {\n provider: attempt.provider ?? 'unknown',\n outcome: attempt.outcome,\n reasonCode: attempt.outcome === 'success' ? 'success' : classifyFailureReason(attempt.reason),\n latencyMs: attempt.latencyMs ?? 0,\n ...(attempt.reason ? { error: attempt.reason } : {}),\n };\n}\n\nexport async function transcribe(\n audioBuffer: Buffer,\n config: STTConfig,\n options?: STTOptions,\n): Promise<STTResultWithTracking> {\n if (!config.enabled) {\n throw new Error('STT is not enabled');\n }\n const providers = resolveSTTProviderChain(config);\n if (providers.length === 0) {\n throw new Error('No STT providers configured');\n }\n\n // Apply caller-supplied language hint to each provider's resolved config.\n // (We don't bake it into resolveSTTProviderChain because it is per-call.)\n const providersWithLanguage = options?.language\n ? providers.map((p) => ({ ...p, language: options.language }))\n : providers;\n\n const result = await runAudioTranscription({\n providers: providersWithLanguage,\n attachments: [\n {\n attachmentIndex: 0,\n buffer: audioBuffer,\n fileName: options?.fileName ?? `audio-${Date.now()}.ogg`,\n mime: options?.mime ?? 'audio/ogg',\n },\n ],\n timeoutMs: config.timeoutMs ?? 60_000,\n signal: options?.signal,\n });\n\n const attachmentDecision: MediaUnderstandingAttachmentDecision | undefined =\n result.decision.attachments[0];\n const attempts = (attachmentDecision?.attempts ?? []).map(toAttempt);\n const attemptedProviders = attempts.map((a) => a.provider);\n\n if (!attachmentDecision?.chosen) {\n log.error({ attempts, attemptedProviders }, 'All STT providers failed');\n const lastError = attempts.find((a) => a.outcome === 'failed')?.error;\n throw new Error(lastError ?? 'All STT providers failed');\n }\n\n const chosen = attachmentDecision.chosen;\n const output = result.outputs[0];\n if (!output) {\n throw new Error('STT runner returned chosen attempt but no output text');\n }\n const primaryProvider = providersWithLanguage[0]!.id;\n const fallbackFrom =\n chosen.provider && chosen.provider !== primaryProvider ? primaryProvider : undefined;\n\n return {\n text: output.text,\n provider: chosen.provider ?? 'unknown',\n ...(chosen.model ? { language: options?.language } : {}),\n attempts,\n ...(fallbackFrom ? { fallbackFrom } : {}),\n attemptedProviders,\n };\n}\n"],"mappings":";;;;;aAcqD;AAgBrD,MAAM,MAAM,aAAa,MAAM;AAE/B,SAAS,sBAAsB,QAAsD;AACnF,KAAI,CAAC,OAAQ,QAAO;CACpB,MAAM,QAAQ,OAAO,aAAa;AAClC,KAAI,MAAM,SAAS,iBAAiB,IAAI,MAAM,SAAS,UAAU,IAAI,MAAM,SAAS,UAAU,CAC5F,QAAO;AAET,KAAI,MAAM,SAAS,UAAU,IAAI,MAAM,SAAS,YAAY,IAAI,MAAM,SAAS,UAAU,CACvF,QAAO;AAET,KAAI,MAAM,SAAS,cAAc,IAAI,MAAM,SAAS,SAAS,CAC3D,QAAO;AAET,QAAO;;AAGT,SAAS,UAAU,SAA8D;AAC/E,QAAO;EACL,UAAU,QAAQ,YAAY;EAC9B,SAAS,QAAQ;EACjB,YAAY,QAAQ,YAAY,YAAY,YAAY,sBAAsB,QAAQ,OAAO;EAC7F,WAAW,QAAQ,aAAa;EAChC,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,QAAQ,GAAG,EAAE;EACpD;;AAGH,eAAsB,WACpB,aACA,QACA,SACgC;AAChC,KAAI,CAAC,OAAO,QACV,OAAM,IAAI,MAAM,qBAAqB;CAEvC,MAAM,YAAY,wBAAwB,OAAO;AACjD,KAAI,UAAU,WAAW,EACvB,OAAM,IAAI,MAAM,8BAA8B;CAKhD,MAAM,wBAAwB,SAAS,WACnC,UAAU,KAAK,OAAO;EAAE,GAAG;EAAG,UAAU,QAAQ;EAAU,EAAE,GAC5D;CAEJ,MAAM,SAAS,MAAM,sBAAsB;EACzC,WAAW;EACX,aAAa,CACX;GACE,iBAAiB;GACjB,QAAQ;GACR,UAAU,SAAS,YAAY,SAAS,KAAK,KAAK,CAAC;GACnD,MAAM,SAAS,QAAQ;GACxB,CACF;EACD,WAAW,OAAO,aAAa;EAC/B,QAAQ,SAAS;EAClB,CAAC;CAEF,MAAM,qBACJ,OAAO,SAAS,YAAY;CAC9B,MAAM,YAAY,oBAAoB,YAAY,EAAE,EAAE,IAAI,UAAU;CACpE,MAAM,qBAAqB,SAAS,KAAK,MAAM,EAAE,SAAS;AAE1D,KAAI,CAAC,oBAAoB,QAAQ;AAC/B,MAAI,MAAM;GAAE;GAAU;GAAoB,EAAE,2BAA2B;EACvE,MAAM,YAAY,SAAS,MAAM,MAAM,EAAE,YAAY,SAAS,EAAE;AAChE,QAAM,IAAI,MAAM,aAAa,2BAA2B;;CAG1D,MAAM,SAAS,mBAAmB;CAClC,MAAM,SAAS,OAAO,QAAQ;AAC9B,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,wDAAwD;CAE1E,MAAM,kBAAkB,sBAAsB,GAAI;CAClD,MAAM,eACJ,OAAO,YAAY,OAAO,aAAa,kBAAkB,kBAAkB,KAAA;AAE7E,QAAO;EACL,MAAM,OAAO;EACb,UAAU,OAAO,YAAY;EAC7B,GAAI,OAAO,QAAQ,EAAE,UAAU,SAAS,UAAU,GAAG,EAAE;EACvD;EACA,GAAI,eAAe,EAAE,cAAc,GAAG,EAAE;EACxC;EACD"}
|
|
@@ -20,6 +20,10 @@ export interface STTOptions {
|
|
|
20
20
|
language?: string;
|
|
21
21
|
/** Model id (provider-specific). */
|
|
22
22
|
model?: string;
|
|
23
|
+
/** Original upload metadata propagated to provider multipart/data URLs. */
|
|
24
|
+
mime?: string;
|
|
25
|
+
fileName?: string;
|
|
26
|
+
signal?: AbortSignal;
|
|
23
27
|
}
|
|
24
28
|
export type { MediaUnderstandingModelEntry } from '../../media-understanding/types.js';
|
|
25
29
|
import type { MediaUnderstandingModelEntry } from '../../media-understanding/types.js';
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
//#region src/voice/stt/types.ts
|
|
2
2
|
const DEFAULT_STT_CONFIG = {
|
|
3
|
-
enabled:
|
|
4
|
-
provider: "
|
|
3
|
+
enabled: true,
|
|
4
|
+
provider: "xopc-local",
|
|
5
5
|
providers: {
|
|
6
|
+
"xopc-local": { model: "sensevoice-small" },
|
|
6
7
|
alibaba: { model: "paraformer-v2" },
|
|
7
8
|
openai: { model: "whisper-1" }
|
|
8
9
|
},
|
|
9
10
|
fallback: {
|
|
10
|
-
enabled:
|
|
11
|
-
order: ["
|
|
11
|
+
enabled: false,
|
|
12
|
+
order: ["xopc-local"]
|
|
12
13
|
},
|
|
13
14
|
timeoutMs: 6e4
|
|
14
15
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/voice/stt/types.ts"],"sourcesContent":["/**\n * STT public types — data shapes consumed by downstream code (channels,\n * schema validation, gateway payloads).\n *\n * Provider implementations live behind `MediaUnderstandingProvider.transcribeAudio`\n * (see src/media-understanding/types.ts).\n */\n\nexport interface STTResult {\n /** Transcribed text. */\n text: string;\n /** Provider that performed the transcription (e.g. \"alibaba\", \"openai\"). */\n provider: string;\n /** Audio duration in seconds (if reported). */\n duration?: number;\n /** Detected language (if reported). */\n language?: string;\n}\n\nexport interface STTOptions {\n /** Language hint (e.g. 'zh', 'en'). */\n language?: string;\n /** Model id (provider-specific). */\n model?: string;\n}\n\nexport type { MediaUnderstandingModelEntry } from '../../media-understanding/types.js';\nimport type { MediaUnderstandingModelEntry } from '../../media-understanding/types.js';\n\n/** STTConfig consumed by the schema and downstream wiring. */\nexport interface STTConfig {\n enabled: boolean;\n provider: string;\n /** Capability-local model chain (`tools.media.audio.models`). */\n models?: MediaUnderstandingModelEntry[];\n /** Shared model entries merged from `tools.media.models` at runtime. */\n sharedModels?: MediaUnderstandingModelEntry[];\n /** Provider settings map (`tools.media.audio.providers.<id>`). */\n providers?: Record<string, Record<string, unknown>>;\n fallback?: {\n enabled: boolean;\n order: string[];\n };\n /** Hard timeout per provider call (ms). Default 60s. */\n timeoutMs?: number;\n}\n\nexport type STTProviderAttemptOutcome = 'success' | 'skipped' | 'failed';\n\nexport type STTProviderFailureReason =\n | 'success'\n | 'not_configured'\n | 'timeout'\n | 'provider_error'\n | 'unsupported_format'\n | 'unknown';\n\nexport interface STTProviderAttempt {\n provider: string;\n outcome: STTProviderAttemptOutcome;\n reasonCode: STTProviderFailureReason;\n latencyMs: number;\n error?: string;\n}\n\nexport interface STTResultWithTracking extends STTResult {\n attempts: STTProviderAttempt[];\n fallbackFrom?: string;\n attemptedProviders: string[];\n}\n\nexport const DEFAULT_STT_CONFIG: STTConfig = {\n enabled:
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/voice/stt/types.ts"],"sourcesContent":["/**\n * STT public types — data shapes consumed by downstream code (channels,\n * schema validation, gateway payloads).\n *\n * Provider implementations live behind `MediaUnderstandingProvider.transcribeAudio`\n * (see src/media-understanding/types.ts).\n */\n\nexport interface STTResult {\n /** Transcribed text. */\n text: string;\n /** Provider that performed the transcription (e.g. \"alibaba\", \"openai\"). */\n provider: string;\n /** Audio duration in seconds (if reported). */\n duration?: number;\n /** Detected language (if reported). */\n language?: string;\n}\n\nexport interface STTOptions {\n /** Language hint (e.g. 'zh', 'en'). */\n language?: string;\n /** Model id (provider-specific). */\n model?: string;\n /** Original upload metadata propagated to provider multipart/data URLs. */\n mime?: string;\n fileName?: string;\n signal?: AbortSignal;\n}\n\nexport type { MediaUnderstandingModelEntry } from '../../media-understanding/types.js';\nimport type { MediaUnderstandingModelEntry } from '../../media-understanding/types.js';\n\n/** STTConfig consumed by the schema and downstream wiring. */\nexport interface STTConfig {\n enabled: boolean;\n provider: string;\n /** Capability-local model chain (`tools.media.audio.models`). */\n models?: MediaUnderstandingModelEntry[];\n /** Shared model entries merged from `tools.media.models` at runtime. */\n sharedModels?: MediaUnderstandingModelEntry[];\n /** Provider settings map (`tools.media.audio.providers.<id>`). */\n providers?: Record<string, Record<string, unknown>>;\n fallback?: {\n enabled: boolean;\n order: string[];\n };\n /** Hard timeout per provider call (ms). Default 60s. */\n timeoutMs?: number;\n}\n\nexport type STTProviderAttemptOutcome = 'success' | 'skipped' | 'failed';\n\nexport type STTProviderFailureReason =\n | 'success'\n | 'not_configured'\n | 'timeout'\n | 'provider_error'\n | 'unsupported_format'\n | 'unknown';\n\nexport interface STTProviderAttempt {\n provider: string;\n outcome: STTProviderAttemptOutcome;\n reasonCode: STTProviderFailureReason;\n latencyMs: number;\n error?: string;\n}\n\nexport interface STTResultWithTracking extends STTResult {\n attempts: STTProviderAttempt[];\n fallbackFrom?: string;\n attemptedProviders: string[];\n}\n\nexport const DEFAULT_STT_CONFIG: STTConfig = {\n enabled: true,\n provider: 'xopc-local',\n providers: {\n 'xopc-local': { model: 'sensevoice-small' },\n alibaba: { model: 'paraformer-v2' },\n openai: { model: 'whisper-1' },\n },\n fallback: {\n enabled: false,\n order: ['xopc-local'],\n },\n timeoutMs: 60_000,\n};\n"],"mappings":";AA2EA,MAAa,qBAAgC;CAC3C,SAAS;CACT,UAAU;CACV,WAAW;EACT,cAAc,EAAE,OAAO,oBAAoB;EAC3C,SAAS,EAAE,OAAO,iBAAiB;EACnC,QAAQ,EAAE,OAAO,aAAa;EAC/B;CACD,UAAU;EACR,SAAS;EACT,OAAO,CAAC,aAAa;EACtB;CACD,WAAW;CACZ"}
|
|
@@ -20,8 +20,8 @@ import { mkdtempSync, readFileSync, rmSync, statSync } from "node:fs";
|
|
|
20
20
|
*/
|
|
21
21
|
init_logger();
|
|
22
22
|
const log = createLogger("SpeechProvider:Edge");
|
|
23
|
-
const DEFAULT_VOICE = "
|
|
24
|
-
const DEFAULT_LANG = "
|
|
23
|
+
const DEFAULT_VOICE = "zh-CN-XiaoxiaoNeural";
|
|
24
|
+
const DEFAULT_LANG = "zh-CN";
|
|
25
25
|
const DEFAULT_OUTPUT_FORMAT = "audio-24khz-48kbitrate-mono-mp3";
|
|
26
26
|
function trimToUndefined(value) {
|
|
27
27
|
if (typeof value !== "string") return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edge-speech.js","names":[],"sources":["../../../../../src/voice/tts/providers/edge-speech.ts"],"sourcesContent":["/**\n * Edge TTS provider — wraps the `node-edge-tts` package. Requires no API key\n * (Microsoft's Edge browser endpoint is unauthenticated).\n *\n * Implementation notes:\n * - Temp-file dance (mkdtemp → ttsPromise → readFileSync → rm) is required\n * because `node-edge-tts` does not expose a stream-to-buffer API.\n * - `synthesizeStream` is implemented as \"synthesize + wrap as single-chunk\n * stream\" so callers see uniform behavior. This is not a true network\n * stream; it's buffered-then-wrapped.\n * - `inferEdgeExtension` derives the file extension from Edge's outputFormat\n * string.\n */\n\nimport { mkdtempSync, readFileSync, rmSync, statSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\n\nimport { createLogger } from '../../../utils/logger.js';\nimport { registerSpeechProvider } from '../speech-registry.js';\nimport type {\n SpeechDirectiveTokenParseContext,\n SpeechDirectiveTokenParseResult,\n SpeechProviderConfig,\n SpeechProviderConfiguredContext,\n SpeechProviderPlugin,\n SpeechProviderResolveConfigContext,\n SpeechSynthesisRequest,\n SpeechSynthesisResult,\n SpeechSynthesisStreamRequest,\n SpeechSynthesisStreamResult,\n} from '../speech-provider-types.js';\n\nconst log = createLogger('SpeechProvider:Edge');\n\nconst DEFAULT_VOICE = 'en-US-MichelleNeural';\nconst DEFAULT_LANG = 'en-US';\nconst DEFAULT_OUTPUT_FORMAT = 'audio-24khz-48kbitrate-mono-mp3';\n\ninterface EdgeTtsConfig extends Record<string, unknown> {\n voice: string;\n lang: string;\n outputFormat: string;\n pitch?: string;\n rate?: string;\n volume?: string;\n proxy?: string;\n enabled: boolean;\n}\n\nfunction trimToUndefined(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction inferEdgeExtension(outputFormat: string): { ext: string; format: string } {\n const normalized = outputFormat.toLowerCase();\n if (normalized.includes('webm')) return { ext: 'webm', format: 'webm' };\n if (normalized.includes('opus')) return { ext: 'opus', format: 'opus' };\n if (normalized.includes('ogg')) return { ext: 'ogg', format: 'ogg' };\n if (normalized.includes('wav') || normalized.includes('riff') || normalized.includes('pcm')) {\n return { ext: 'wav', format: 'wav' };\n }\n return { ext: 'mp3', format: 'mp3' };\n}\n\nfunction normalizeConfig(rawConfig: Record<string, unknown>): EdgeTtsConfig {\n const raw = asObject(rawConfig.edge) ?? rawConfig;\n return {\n voice: trimToUndefined(raw.voice ?? raw.voiceId) ?? DEFAULT_VOICE,\n lang: trimToUndefined(raw.lang) ?? DEFAULT_LANG,\n outputFormat: trimToUndefined(raw.outputFormat) ?? DEFAULT_OUTPUT_FORMAT,\n pitch: trimToUndefined(raw.pitch),\n rate: trimToUndefined(raw.rate),\n volume: trimToUndefined(raw.volume),\n proxy: trimToUndefined(raw.proxy),\n enabled: raw.enabled !== false, // default-on\n };\n}\n\nfunction readProviderConfig(config: SpeechProviderConfig): EdgeTtsConfig {\n return {\n voice: trimToUndefined(config.voice ?? config.voiceId) ?? DEFAULT_VOICE,\n lang: trimToUndefined(config.lang) ?? DEFAULT_LANG,\n outputFormat: trimToUndefined(config.outputFormat) ?? DEFAULT_OUTPUT_FORMAT,\n pitch: trimToUndefined(config.pitch),\n rate: trimToUndefined(config.rate),\n volume: trimToUndefined(config.volume),\n proxy: trimToUndefined(config.proxy),\n enabled: config.enabled !== false,\n };\n}\n\nfunction parseDirectiveTokenInternal(\n ctx: SpeechDirectiveTokenParseContext,\n): SpeechDirectiveTokenParseResult {\n switch (ctx.key) {\n case 'voice':\n case 'voice_id':\n case 'voiceid':\n case 'edge_voice':\n case 'edgevoice':\n if (!ctx.policy.allowVoice) {\n return { handled: true };\n }\n return { handled: true, overrides: { voice: ctx.value } };\n default:\n return { handled: false };\n }\n}\n\nasync function waitForNonEmptyFile(filePath: string, timeoutMs: number): Promise<void> {\n const deadline = Date.now() + Math.min(Math.max(timeoutMs, 1000), 5000);\n while (Date.now() < deadline) {\n try {\n if (statSync(filePath).size > 0) return;\n } catch {\n // The Edge package may resolve before the file is visible on slower filesystems.\n }\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n}\n\nasync function synthesizeToBuffer(\n text: string,\n config: EdgeTtsConfig,\n voiceOverride: string | undefined,\n timeoutMs: number,\n): Promise<{ buffer: Buffer; outputFormat: string; ext: string }> {\n // Lazy import — node-edge-tts loads ws transitively; skip cost for users that\n // never enable the edge provider.\n const { EdgeTTS } = await import('node-edge-tts');\n\n const voice = voiceOverride ?? config.voice;\n const tempDir = mkdtempSync(path.join(tmpdir(), 'tts-edge-'));\n const { ext, format } = inferEdgeExtension(config.outputFormat);\n const outputPath = path.join(tempDir, `speech-${Date.now()}.${ext}`);\n\n try {\n log.debug(\n { voice, outputFormat: config.outputFormat, textLength: text.length },\n 'Calling Edge TTS',\n );\n const tts = new EdgeTTS({\n voice,\n lang: config.lang,\n outputFormat: config.outputFormat,\n proxy: config.proxy,\n rate: config.rate,\n pitch: config.pitch,\n volume: config.volume,\n timeout: timeoutMs,\n });\n await tts.ttsPromise(text, outputPath);\n await waitForNonEmptyFile(outputPath, timeoutMs);\n const buffer = readFileSync(outputPath);\n if (buffer.length === 0) {\n throw new Error(`Edge TTS produced an empty ${format} file for voice ${voice}`);\n }\n return { buffer, outputFormat: format, ext };\n } finally {\n try {\n rmSync(tempDir, { recursive: true, force: true });\n } catch (cleanupError) {\n log.warn({ err: cleanupError, tempDir }, 'Failed to cleanup Edge TTS temp directory');\n }\n }\n}\n\nexport const edgeSpeechProvider: SpeechProviderPlugin = {\n id: 'edge',\n autoSelectOrder: 100,\n\n resolveConfig: (ctx: SpeechProviderResolveConfigContext) => normalizeConfig(ctx.rawConfig),\n\n isConfigured: (ctx: SpeechProviderConfiguredContext) => readProviderConfig(ctx.providerConfig).enabled,\n\n parseDirectiveToken: parseDirectiveTokenInternal,\n\n // Edge has hundreds of voices; we don't enumerate them statically. UI should\n // call upstream Microsoft endpoint for the full catalog.\n listVoices: async () => [{ id: DEFAULT_VOICE, name: DEFAULT_VOICE, locale: DEFAULT_LANG }],\n\n synthesize: async (req: SpeechSynthesisRequest): Promise<SpeechSynthesisResult> => {\n const config = readProviderConfig(req.providerConfig);\n if (!config.enabled) {\n throw new Error('Edge TTS is disabled in config (messages.tts.providers.edge.enabled = false)');\n }\n const overrides = req.providerOverrides ?? {};\n const voiceOverride = trimToUndefined(overrides.voice ?? overrides.voiceId);\n const { buffer, outputFormat, ext } = await synthesizeToBuffer(\n req.text,\n config,\n voiceOverride,\n req.timeoutMs,\n );\n return {\n audioBuffer: buffer,\n outputFormat,\n fileExtension: ext,\n voiceCompatible: outputFormat === 'opus' || outputFormat === 'ogg',\n };\n },\n\n // True streaming would require a node-edge-tts upstream change. We expose a\n // \"fake stream\" (single chunk) so callers don't fall through to speak-core's\n // wrapBufferAsStream branch and can treat all providers uniformly.\n synthesizeStream: async (req: SpeechSynthesisStreamRequest): Promise<SpeechSynthesisStreamResult> => {\n const config = readProviderConfig(req.providerConfig);\n if (!config.enabled) {\n throw new Error('Edge TTS is disabled in config (messages.tts.providers.edge.enabled = false)');\n }\n const overrides = req.providerOverrides ?? {};\n const voiceOverride = trimToUndefined(overrides.voice ?? overrides.voiceId);\n const { buffer, outputFormat, ext } = await synthesizeToBuffer(\n req.text,\n config,\n voiceOverride,\n req.timeoutMs,\n );\n const audioStream = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(new Uint8Array(buffer));\n controller.close();\n },\n });\n return {\n audioStream,\n outputFormat,\n fileExtension: ext,\n voiceCompatible: outputFormat === 'opus' || outputFormat === 'ogg',\n };\n },\n};\n\nregisterSpeechProvider(edgeSpeechProvider);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"edge-speech.js","names":[],"sources":["../../../../../src/voice/tts/providers/edge-speech.ts"],"sourcesContent":["/**\n * Edge TTS provider — wraps the `node-edge-tts` package. Requires no API key\n * (Microsoft's Edge browser endpoint is unauthenticated).\n *\n * Implementation notes:\n * - Temp-file dance (mkdtemp → ttsPromise → readFileSync → rm) is required\n * because `node-edge-tts` does not expose a stream-to-buffer API.\n * - `synthesizeStream` is implemented as \"synthesize + wrap as single-chunk\n * stream\" so callers see uniform behavior. This is not a true network\n * stream; it's buffered-then-wrapped.\n * - `inferEdgeExtension` derives the file extension from Edge's outputFormat\n * string.\n */\n\nimport { mkdtempSync, readFileSync, rmSync, statSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\n\nimport { createLogger } from '../../../utils/logger.js';\nimport { registerSpeechProvider } from '../speech-registry.js';\nimport type {\n SpeechDirectiveTokenParseContext,\n SpeechDirectiveTokenParseResult,\n SpeechProviderConfig,\n SpeechProviderConfiguredContext,\n SpeechProviderPlugin,\n SpeechProviderResolveConfigContext,\n SpeechSynthesisRequest,\n SpeechSynthesisResult,\n SpeechSynthesisStreamRequest,\n SpeechSynthesisStreamResult,\n} from '../speech-provider-types.js';\n\nconst log = createLogger('SpeechProvider:Edge');\n\nconst DEFAULT_VOICE = 'zh-CN-XiaoxiaoNeural';\nconst DEFAULT_LANG = 'zh-CN';\nconst DEFAULT_OUTPUT_FORMAT = 'audio-24khz-48kbitrate-mono-mp3';\n\ninterface EdgeTtsConfig extends Record<string, unknown> {\n voice: string;\n lang: string;\n outputFormat: string;\n pitch?: string;\n rate?: string;\n volume?: string;\n proxy?: string;\n enabled: boolean;\n}\n\nfunction trimToUndefined(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction inferEdgeExtension(outputFormat: string): { ext: string; format: string } {\n const normalized = outputFormat.toLowerCase();\n if (normalized.includes('webm')) return { ext: 'webm', format: 'webm' };\n if (normalized.includes('opus')) return { ext: 'opus', format: 'opus' };\n if (normalized.includes('ogg')) return { ext: 'ogg', format: 'ogg' };\n if (normalized.includes('wav') || normalized.includes('riff') || normalized.includes('pcm')) {\n return { ext: 'wav', format: 'wav' };\n }\n return { ext: 'mp3', format: 'mp3' };\n}\n\nfunction normalizeConfig(rawConfig: Record<string, unknown>): EdgeTtsConfig {\n const raw = asObject(rawConfig.edge) ?? rawConfig;\n return {\n voice: trimToUndefined(raw.voice ?? raw.voiceId) ?? DEFAULT_VOICE,\n lang: trimToUndefined(raw.lang) ?? DEFAULT_LANG,\n outputFormat: trimToUndefined(raw.outputFormat) ?? DEFAULT_OUTPUT_FORMAT,\n pitch: trimToUndefined(raw.pitch),\n rate: trimToUndefined(raw.rate),\n volume: trimToUndefined(raw.volume),\n proxy: trimToUndefined(raw.proxy),\n enabled: raw.enabled !== false, // default-on\n };\n}\n\nfunction readProviderConfig(config: SpeechProviderConfig): EdgeTtsConfig {\n return {\n voice: trimToUndefined(config.voice ?? config.voiceId) ?? DEFAULT_VOICE,\n lang: trimToUndefined(config.lang) ?? DEFAULT_LANG,\n outputFormat: trimToUndefined(config.outputFormat) ?? DEFAULT_OUTPUT_FORMAT,\n pitch: trimToUndefined(config.pitch),\n rate: trimToUndefined(config.rate),\n volume: trimToUndefined(config.volume),\n proxy: trimToUndefined(config.proxy),\n enabled: config.enabled !== false,\n };\n}\n\nfunction parseDirectiveTokenInternal(\n ctx: SpeechDirectiveTokenParseContext,\n): SpeechDirectiveTokenParseResult {\n switch (ctx.key) {\n case 'voice':\n case 'voice_id':\n case 'voiceid':\n case 'edge_voice':\n case 'edgevoice':\n if (!ctx.policy.allowVoice) {\n return { handled: true };\n }\n return { handled: true, overrides: { voice: ctx.value } };\n default:\n return { handled: false };\n }\n}\n\nasync function waitForNonEmptyFile(filePath: string, timeoutMs: number): Promise<void> {\n const deadline = Date.now() + Math.min(Math.max(timeoutMs, 1000), 5000);\n while (Date.now() < deadline) {\n try {\n if (statSync(filePath).size > 0) return;\n } catch {\n // The Edge package may resolve before the file is visible on slower filesystems.\n }\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n}\n\nasync function synthesizeToBuffer(\n text: string,\n config: EdgeTtsConfig,\n voiceOverride: string | undefined,\n timeoutMs: number,\n): Promise<{ buffer: Buffer; outputFormat: string; ext: string }> {\n // Lazy import — node-edge-tts loads ws transitively; skip cost for users that\n // never enable the edge provider.\n const { EdgeTTS } = await import('node-edge-tts');\n\n const voice = voiceOverride ?? config.voice;\n const tempDir = mkdtempSync(path.join(tmpdir(), 'tts-edge-'));\n const { ext, format } = inferEdgeExtension(config.outputFormat);\n const outputPath = path.join(tempDir, `speech-${Date.now()}.${ext}`);\n\n try {\n log.debug(\n { voice, outputFormat: config.outputFormat, textLength: text.length },\n 'Calling Edge TTS',\n );\n const tts = new EdgeTTS({\n voice,\n lang: config.lang,\n outputFormat: config.outputFormat,\n proxy: config.proxy,\n rate: config.rate,\n pitch: config.pitch,\n volume: config.volume,\n timeout: timeoutMs,\n });\n await tts.ttsPromise(text, outputPath);\n await waitForNonEmptyFile(outputPath, timeoutMs);\n const buffer = readFileSync(outputPath);\n if (buffer.length === 0) {\n throw new Error(`Edge TTS produced an empty ${format} file for voice ${voice}`);\n }\n return { buffer, outputFormat: format, ext };\n } finally {\n try {\n rmSync(tempDir, { recursive: true, force: true });\n } catch (cleanupError) {\n log.warn({ err: cleanupError, tempDir }, 'Failed to cleanup Edge TTS temp directory');\n }\n }\n}\n\nexport const edgeSpeechProvider: SpeechProviderPlugin = {\n id: 'edge',\n autoSelectOrder: 100,\n\n resolveConfig: (ctx: SpeechProviderResolveConfigContext) => normalizeConfig(ctx.rawConfig),\n\n isConfigured: (ctx: SpeechProviderConfiguredContext) => readProviderConfig(ctx.providerConfig).enabled,\n\n parseDirectiveToken: parseDirectiveTokenInternal,\n\n // Edge has hundreds of voices; we don't enumerate them statically. UI should\n // call upstream Microsoft endpoint for the full catalog.\n listVoices: async () => [{ id: DEFAULT_VOICE, name: DEFAULT_VOICE, locale: DEFAULT_LANG }],\n\n synthesize: async (req: SpeechSynthesisRequest): Promise<SpeechSynthesisResult> => {\n const config = readProviderConfig(req.providerConfig);\n if (!config.enabled) {\n throw new Error('Edge TTS is disabled in config (messages.tts.providers.edge.enabled = false)');\n }\n const overrides = req.providerOverrides ?? {};\n const voiceOverride = trimToUndefined(overrides.voice ?? overrides.voiceId);\n const { buffer, outputFormat, ext } = await synthesizeToBuffer(\n req.text,\n config,\n voiceOverride,\n req.timeoutMs,\n );\n return {\n audioBuffer: buffer,\n outputFormat,\n fileExtension: ext,\n voiceCompatible: outputFormat === 'opus' || outputFormat === 'ogg',\n };\n },\n\n // True streaming would require a node-edge-tts upstream change. We expose a\n // \"fake stream\" (single chunk) so callers don't fall through to speak-core's\n // wrapBufferAsStream branch and can treat all providers uniformly.\n synthesizeStream: async (req: SpeechSynthesisStreamRequest): Promise<SpeechSynthesisStreamResult> => {\n const config = readProviderConfig(req.providerConfig);\n if (!config.enabled) {\n throw new Error('Edge TTS is disabled in config (messages.tts.providers.edge.enabled = false)');\n }\n const overrides = req.providerOverrides ?? {};\n const voiceOverride = trimToUndefined(overrides.voice ?? overrides.voiceId);\n const { buffer, outputFormat, ext } = await synthesizeToBuffer(\n req.text,\n config,\n voiceOverride,\n req.timeoutMs,\n );\n const audioStream = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(new Uint8Array(buffer));\n controller.close();\n },\n });\n return {\n audioStream,\n outputFormat,\n fileExtension: ext,\n voiceCompatible: outputFormat === 'opus' || outputFormat === 'ogg',\n };\n },\n};\n\nregisterSpeechProvider(edgeSpeechProvider);\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
//#region src/voice/tts/types.ts
|
|
2
2
|
const DEFAULT_TTS_CONFIG = {
|
|
3
|
-
enabled:
|
|
3
|
+
enabled: true,
|
|
4
4
|
provider: "edge",
|
|
5
|
-
trigger: "
|
|
5
|
+
trigger: "inbound",
|
|
6
6
|
fallback: {
|
|
7
7
|
enabled: true,
|
|
8
8
|
order: [
|
|
@@ -13,7 +13,7 @@ const DEFAULT_TTS_CONFIG = {
|
|
|
13
13
|
]
|
|
14
14
|
},
|
|
15
15
|
maxTextLength: 512,
|
|
16
|
-
timeoutMs:
|
|
16
|
+
timeoutMs: 6e4,
|
|
17
17
|
summarization: { enabled: true },
|
|
18
18
|
modelOverrides: {
|
|
19
19
|
enabled: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/voice/tts/types.ts"],"sourcesContent":["/**\n * TTS (Text-to-Speech) Types\n */\n\n/** Built-in provider ids shipped in core. Extensions add more at runtime. */\nexport type BuiltinTTSProvider = 'openai' | 'alibaba' | 'edge' | 'minimax' | 'tts-local-cli';\n\n/** Any registered SpeechProviderPlugin id (built-in or extension). */\nexport type TTSProvider = string;\n\nexport type TTSAutoMode = 'off' | 'always' | 'inbound' | 'tagged';\n\nexport interface TTSOptions {\n /** Voice ID */\n voice?: string;\n /** Speed (0.25 - 4.0) */\n speed?: number;\n /** Output format */\n format?: 'opus' | 'mp3' | 'wav';\n /** Model ID (for provider-specific model selection) */\n model?: string;\n /** Request timeout in milliseconds */\n timeoutMs?: number;\n}\n\nexport interface TTSResult {\n /** Audio buffer */\n audio: Buffer;\n /** Format */\n format: string;\n /** Duration in seconds (if available) */\n duration?: number;\n /** Provider that generated the speech */\n provider: string;\n}\n\nexport interface TTSProviderInterface {\n /** Provider name */\n name: string;\n /** Convert text to speech */\n speak(text: string, options?: TTSOptions): Promise<TTSResult>;\n /** Check if provider is configured */\n isConfigured(): boolean;\n}\n\nexport interface TTSModelOverrideConfig {\n /** Enable model-provided overrides for TTS */\n enabled?: boolean;\n /** Allow model-provided TTS text blocks */\n allowText?: boolean;\n /** Allow model-provided provider override (default: false) */\n allowProvider?: boolean;\n /** Allow model-provided voice/voiceId override */\n allowVoice?: boolean;\n /** Allow model-provided modelId override */\n allowModelId?: boolean;\n /** Allow model-provided voice settings override */\n allowVoiceSettings?: boolean;\n /** Allow model-provided normalization or language overrides */\n allowNormalization?: boolean;\n /** Allow model-provided seed override */\n allowSeed?: boolean;\n}\n\nexport interface TTSConfig {\n enabled: boolean;\n provider: TTSProvider;\n /** Trigger mode: auto = reply with voice when user sends voice */\n trigger: TTSAutoMode;\n /** Fallback configuration */\n fallback?: {\n enabled: boolean;\n order: TTSProvider[];\n };\n /** Maximum text length for TTS */\n maxTextLength?: number;\n /** API request timeout (ms) */\n timeoutMs?: number;\n /** Long-text summarization before TTS */\n summarization?: TTSSummarizationConfig;\n /** Allow model to override TTS parameters */\n modelOverrides?: TTSModelOverrideConfig;\n /** Provider settings map (`messages.tts.providers.<id>`). */\n providers?: Record<string, Record<string, unknown>>;\n}\n\nexport interface TTSSummarizationConfig {\n /** When true (default), long text is summarized via LLM before TTS */\n enabled?: boolean;\n targetLength?: number;\n threshold?: number;\n model?: string;\n}\n\nexport const DEFAULT_TTS_CONFIG: TTSConfig = {\n enabled:
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/voice/tts/types.ts"],"sourcesContent":["/**\n * TTS (Text-to-Speech) Types\n */\n\n/** Built-in provider ids shipped in core. Extensions add more at runtime. */\nexport type BuiltinTTSProvider = 'openai' | 'alibaba' | 'edge' | 'minimax' | 'tts-local-cli';\n\n/** Any registered SpeechProviderPlugin id (built-in or extension). */\nexport type TTSProvider = string;\n\nexport type TTSAutoMode = 'off' | 'always' | 'inbound' | 'tagged';\n\nexport interface TTSOptions {\n /** Voice ID */\n voice?: string;\n /** Speed (0.25 - 4.0) */\n speed?: number;\n /** Output format */\n format?: 'opus' | 'mp3' | 'wav';\n /** Model ID (for provider-specific model selection) */\n model?: string;\n /** Request timeout in milliseconds */\n timeoutMs?: number;\n}\n\nexport interface TTSResult {\n /** Audio buffer */\n audio: Buffer;\n /** Format */\n format: string;\n /** Duration in seconds (if available) */\n duration?: number;\n /** Provider that generated the speech */\n provider: string;\n}\n\nexport interface TTSProviderInterface {\n /** Provider name */\n name: string;\n /** Convert text to speech */\n speak(text: string, options?: TTSOptions): Promise<TTSResult>;\n /** Check if provider is configured */\n isConfigured(): boolean;\n}\n\nexport interface TTSModelOverrideConfig {\n /** Enable model-provided overrides for TTS */\n enabled?: boolean;\n /** Allow model-provided TTS text blocks */\n allowText?: boolean;\n /** Allow model-provided provider override (default: false) */\n allowProvider?: boolean;\n /** Allow model-provided voice/voiceId override */\n allowVoice?: boolean;\n /** Allow model-provided modelId override */\n allowModelId?: boolean;\n /** Allow model-provided voice settings override */\n allowVoiceSettings?: boolean;\n /** Allow model-provided normalization or language overrides */\n allowNormalization?: boolean;\n /** Allow model-provided seed override */\n allowSeed?: boolean;\n}\n\nexport interface TTSConfig {\n enabled: boolean;\n provider: TTSProvider;\n /** Trigger mode: auto = reply with voice when user sends voice */\n trigger: TTSAutoMode;\n /** Fallback configuration */\n fallback?: {\n enabled: boolean;\n order: TTSProvider[];\n };\n /** Maximum text length for TTS */\n maxTextLength?: number;\n /** API request timeout (ms) */\n timeoutMs?: number;\n /** Long-text summarization before TTS */\n summarization?: TTSSummarizationConfig;\n /** Allow model to override TTS parameters */\n modelOverrides?: TTSModelOverrideConfig;\n /** Provider settings map (`messages.tts.providers.<id>`). */\n providers?: Record<string, Record<string, unknown>>;\n}\n\nexport interface TTSSummarizationConfig {\n /** When true (default), long text is summarized via LLM before TTS */\n enabled?: boolean;\n targetLength?: number;\n threshold?: number;\n model?: string;\n}\n\nexport const DEFAULT_TTS_CONFIG: TTSConfig = {\n enabled: true,\n provider: 'edge',\n trigger: 'inbound',\n fallback: {\n enabled: true,\n order: ['openai', 'alibaba', 'minimax', 'edge'],\n },\n maxTextLength: 512, // Conservative default to accommodate all providers (Alibaba limit is 512)\n timeoutMs: 60000,\n summarization: {\n enabled: true,\n },\n modelOverrides: {\n enabled: true,\n allowText: true,\n allowProvider: false,\n allowVoice: true,\n allowModelId: true,\n allowVoiceSettings: false,\n allowNormalization: false,\n allowSeed: false,\n },\n providers: {\n alibaba: { model: 'qwen-tts', voice: 'Cherry' },\n openai: { model: 'tts-1', voice: 'alloy' },\n edge: {\n enabled: true,\n voice: 'en-US-MichelleNeural',\n lang: 'en-US',\n outputFormat: 'audio-24khz-48kbitrate-mono-mp3',\n },\n minimax: { model: 'speech-2.8-hd', voice: 'male-qn-qingse' },\n },\n};\n\n/** TTS directive parse result */\nexport interface TtsDirectiveParseResult {\n cleanedText: string;\n ttsText?: string;\n hasDirective: boolean;\n overrides: TtsDirectiveOverrides;\n warnings: string[];\n}\n\n/** TTS directive overrides */\nexport interface TtsDirectiveOverrides {\n ttsText?: string;\n provider?: TTSProvider;\n openai?: {\n voice?: string;\n model?: string;\n };\n alibaba?: {\n voice?: string;\n model?: string;\n };\n edge?: {\n voice?: string;\n };\n minimax?: {\n voice?: string;\n model?: string;\n };\n}\n\n/** Provider attempt result (TTS fallback chain) */\nexport type ProviderAttemptOutcome = 'success' | 'skipped' | 'failed';\n\n/** Provider failure reason classification */\nexport type ProviderFailureReason =\n | 'success'\n | 'not_configured'\n | 'timeout'\n | 'provider_error'\n | 'text_too_long'\n | 'unknown';\n\n/** Single provider attempt in a TTS fallback chain */\nexport interface ProviderAttempt {\n provider: string;\n outcome: ProviderAttemptOutcome;\n reasonCode: ProviderFailureReason;\n latencyMs: number;\n error?: string;\n}\n\n/** TTS result including fallback / preprocessing metadata */\nexport interface TTSResultWithTracking extends TTSResult {\n attempts: ProviderAttempt[];\n fallbackFrom?: string;\n attemptedProviders: string[];\n wasPreprocessed?: boolean;\n ttsText?: string;\n wasSummarized?: boolean;\n}\n\n/** Last TTS call diagnostics (memory-only) */\nexport interface TtsStatusEntry {\n timestamp: number;\n success: boolean;\n provider?: string;\n latencyMs?: number;\n error?: string;\n textLength?: number;\n audioSize?: number;\n audioFormat?: string;\n usedFallback?: boolean;\n wasSummarized?: boolean;\n}\n\nexport interface TtsRuntimeStatus {\n lastAttempt?: TtsStatusEntry;\n recentSuccessRate?: number;\n totalCalls: number;\n totalSuccesses: number;\n totalFailures: number;\n}\n"],"mappings":";AA8FA,MAAa,qBAAgC;CAC3C,SAAS;CACT,UAAU;CACV,SAAS;CACT,UAAU;EACR,SAAS;EACT,OAAO;GAAC;GAAU;GAAW;GAAW;GAAO;EAChD;CACD,eAAe;CACf,WAAW;CACX,eAAe,EACb,SAAS,MACV;CACD,gBAAgB;EACd,SAAS;EACT,WAAW;EACX,eAAe;EACf,YAAY;EACZ,cAAc;EACd,oBAAoB;EACpB,oBAAoB;EACpB,WAAW;EACZ;CACD,WAAW;EACT,SAAS;GAAE,OAAO;GAAY,OAAO;GAAU;EAC/C,QAAQ;GAAE,OAAO;GAAS,OAAO;GAAS;EAC1C,MAAM;GACJ,SAAS;GACT,OAAO;GACP,MAAM;GACN,cAAc;GACf;EACD,SAAS;GAAE,OAAO;GAAiB,OAAO;GAAkB;EAC7D;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xopcai/xopc",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.145",
|
|
4
4
|
"description": "Local-first AI system that remembers context, coordinates AI, and sustains long-term progress.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"@earendil-works/pi-tui": "0.80.10",
|
|
74
74
|
"@grammyjs/runner": "^2.0.3",
|
|
75
75
|
"@hono/node-server": "^2.0.4",
|
|
76
|
+
"@huggingface/transformers": "3.8.1",
|
|
76
77
|
"@inquirer/prompts": "^8.5.0",
|
|
77
78
|
"@larksuiteoapi/node-sdk": "^1.66.0",
|
|
78
79
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
"@sinclair/typebox": "^0.34.49",
|
|
81
82
|
"@vscode/ripgrep": "^1.18.0",
|
|
82
83
|
"abort-controller": "^3.0.0",
|
|
83
|
-
"adm-zip": "^0.
|
|
84
|
+
"adm-zip": "^0.6.0",
|
|
84
85
|
"ajv": "^8.20.0",
|
|
85
86
|
"chalk": "^5.6.2",
|
|
86
87
|
"chokidar": "^5.0.0",
|
|
@@ -103,6 +104,7 @@
|
|
|
103
104
|
"playwright-core": "^1.60.0",
|
|
104
105
|
"proper-lockfile": "^4.1.2",
|
|
105
106
|
"semver": "^7.8.1",
|
|
107
|
+
"sherpa-onnx-node": "1.13.4",
|
|
106
108
|
"silk-wasm": "^3.7.1",
|
|
107
109
|
"thread-stream": "^4.2.0",
|
|
108
110
|
"undici": "^8.5.0",
|