@xopcai/xopc 0.0.144 → 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/feishu/src/workflow-progress.js +1 -1
- package/dist/extensions/telegram/src/adapters/streaming-adapter.js +1 -1
- package/dist/extensions/telegram/src/inbound-processor.js +1 -1
- package/dist/extensions/telegram/src/plugin.js +1 -1
- package/dist/extensions/telegram/src/routing-integration.js +2 -2
- package/dist/extensions/telegram/src/workflow-progress.js +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/api/api.js +1 -1
- package/dist/extensions/weixin/src/plugin.js +1 -1
- package/dist/extensions/weixin/src/workflow-progress.js +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-EAVnrCAs.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-DSkm9bp_.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-C99N0vV6.js → archive-plugin-B6vTkH2A.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-CHTDqME5.js → attachment-load-BjZXzaNv.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-CBt6U3-S.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-BRtxOLlM.js → binary-plugins-DYZfLvPz.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-CUlXfgXd.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-C-Drhqyk.js → desktop-pet-_jUmoXd3.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-CRGiolqF.js → desktop-pet-settings-DwGOy4AQ.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-Dw1L3M84.js → directory-picker-path-field-akvF0F-u.js} +1 -1
- package/dist/gateway/static/root/assets/{dist-SwiI0Xu3.js → dist-C99RGfAH.js} +1 -1
- package/dist/gateway/static/root/assets/dreaming-settings-BLetQiYY.js +3 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-Dcl8LJG_.js → extension-debug-page-Cof2ncxE.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-i_BXmO_x.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-BTRFTK0w.js → markdown-split-5rJx5flj.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-BABfIZPU.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-88QBHW9a.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-je95DZOf.js → page-tabs-6Al9_sGz.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-BJbwi7Rc.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-qWMQ9mod.js → save-bar-controls-BYtfzu_F.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-kxd270Gs.js → schedule-field-CvIRc6yK.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-DLnD-MuD.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-DQprvu5J.js → settings-collapsible-section-CF3HHM-t.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-Cqq7Y98y.js → settings-form-section-CwWjoRN6.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-b6rXULL5.js → settings-loading-skeleton-aUFmJa13.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-BtBVz3cX.js → settings-page-B2UUU9TF.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-layout-BNJbtklV.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-Cq9J7i7v.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-CdSwi1gT.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-B8hlamVB.js → user-profile-settings-panel-DW-Up6vt.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-Dx01V8qF.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/activity/emitter.js +1 -1
- package/dist/src/agent/agent-manager.js +4 -4
- package/dist/src/agent/code-intelligence/binary.js +1 -1
- package/dist/src/agent/context/project-context.js +2 -2
- package/dist/src/agent/context/workspace-seed.js +2 -2
- package/dist/src/agent/embedded/run-for-session.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/goals/post-turn.js +2 -2
- package/dist/src/agent/ipc/bus.js +1 -1
- package/dist/src/agent/mcp/bundle-mcp-materialize.js +1 -1
- package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
- package/dist/src/agent/mcp/mcp-transport-config.js +1 -1
- package/dist/src/agent/mcp/mcp-transport.js +1 -1
- package/dist/src/agent/memory/dreaming/config.js +1 -1
- package/dist/src/agent/memory/plugin-discovery.js +1 -1
- package/dist/src/agent/models/manager.js +1 -1
- package/dist/src/agent/orchestration/agent-orchestrator.js +1 -1
- package/dist/src/agent/service.js +3 -3
- package/dist/src/agent/session/session-inspector.js +2 -2
- package/dist/src/agent/skills/hub-pull.js +1 -1
- package/dist/src/agent/tools/composio-tool.js +1 -1
- package/dist/src/agent/tools/goal-tool.js +2 -2
- package/dist/src/agent/tools/workflow-tool.js +1 -1
- package/dist/src/agent/tools/xopc-use-tool.js +1 -1
- package/dist/src/agent/workspace-runtime/registry.js +2 -2
- package/dist/src/agent-runtime/index.js +1 -1
- package/dist/src/agent-runtime/runtime-profile.js +1 -1
- package/dist/src/automations/api/routes.js +1 -1
- package/dist/src/automations/service/action-executor.js +2 -2
- package/dist/src/browser/cdp-local-launcher.js +1 -1
- package/dist/src/browser/providers/browser-ext-install.js +3 -3
- package/dist/src/browser/providers/cloakbrowser.js +2 -2
- package/dist/src/browser/providers/playwright-doctor.js +1 -1
- package/dist/src/browser/stealth.js +1 -1
- package/dist/src/capabilities/store-connector.js +1 -1
- package/dist/src/chat-commands/agent-edit.js +1 -1
- package/dist/src/chat-commands/builtins/config.js +1 -1
- package/dist/src/chat-commands/builtins/goal.js +2 -2
- package/dist/src/chat-commands/context.js +1 -1
- package/dist/src/cli/commands/agent.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +2 -2
- package/dist/src/cli/commands/doctor/checks/session-integrity.js +1 -1
- package/dist/src/cli/commands/extension-marketplace.js +1 -1
- package/dist/src/cli/commands/extension-pack.js +1 -1
- package/dist/src/cli/commands/goal.js +2 -2
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/commands/project.js +3 -3
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/config/gateway-bind.js +1 -1
- package/dist/src/config/index.js +3 -3
- package/dist/src/config/loader.js +1 -1
- package/dist/src/config/models-json.js +1 -1
- 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/composio-sessions.js +1 -1
- package/dist/src/connectors/composio.js +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/daemon/launchd.js +1 -1
- package/dist/src/daemon/systemd.js +1 -1
- package/dist/src/extensions/discover-extensions.js +1 -1
- package/dist/src/extensions/loader.js +2 -2
- 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/lib/static-ui.js +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/home.js +2 -2
- package/dist/src/gateway/hono/routes/memory.js +13 -3
- package/dist/src/gateway/hono/routes/memory.js.map +1 -1
- package/dist/src/gateway/hono/routes/projects.js +5 -5
- 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/workflows.js +1 -1
- package/dist/src/gateway/hono/routes/you.js +11 -7
- package/dist/src/gateway/hono/routes/you.js.map +1 -1
- package/dist/src/gateway/lock.js +1 -1
- package/dist/src/gateway/ports.js +1 -1
- package/dist/src/gateway/service.d.ts +8 -0
- package/dist/src/gateway/service.js +47 -17
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/goals/goal-workflow-judge.js +1 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/infra/ports.js +1 -1
- package/dist/src/infra/ssh-tunnel.js +1 -1
- package/dist/src/infra/update-check.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/migrations/runner.js +1 -1
- package/dist/src/notes/service.js +3 -3
- package/dist/src/notes/store.js +1 -1
- package/dist/src/projects/project-service.js +1 -1
- package/dist/src/projects/workspace.js +1 -1
- package/dist/src/routing/agent-session-key.js +1 -1
- package/dist/src/routing/resolve-route.js +1 -1
- package/dist/src/routing/resolve-tui-session-key.js +1 -1
- package/dist/src/session/init-session-turn.js +1 -1
- package/dist/src/session/resolve-session.js +1 -1
- package/dist/src/session/session-title.js +2 -2
- package/dist/src/session/store.js +1 -1
- package/dist/src/storage/sqlite/connection.js +1 -1
- 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/row-mappers.js +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/tui/backends/embedded-backend.js +4 -4
- package/dist/src/tui/clipboard-image.js +1 -1
- package/dist/src/tui/tui-command-formatters.js +2 -2
- package/dist/src/tui/tui-commands.js +3 -3
- package/dist/src/tui/tui.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/utils/index.js +1 -1
- package/dist/src/utils/logger/index.js +1 -1
- package/dist/src/utils/logger.js +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/dist/src/work-items/work-item-service.js +1 -1
- package/dist/src/workflows/draft/workflow-draft-service.js +1 -1
- package/dist/src/workflows/service/workflow-session-bridge.js +3 -3
- package/package.json +3 -1
- package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +0 -41
- package/dist/gateway/static/root/assets/agents-5rTuJOtT.js +0 -2
- package/dist/gateway/static/root/assets/app-management-settings-panel-Bu568NpC.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-DWetalKp.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-ICUfxeOz.js +0 -4
- package/dist/gateway/static/root/assets/browser-settings-page-D7Bj4MKi.js +0 -8
- package/dist/gateway/static/root/assets/capability-presets-api-Bg1ntFSD.js +0 -1
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-C-8I-Ktn.js +0 -2
- package/dist/gateway/static/root/assets/channel-recipient-api-DuulnhIj.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-Dm5EqQyK.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-B5lVrUsN.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-BOzjh-OK.js +0 -2
- package/dist/gateway/static/root/assets/dreaming-settings-CF21cbjo.js +0 -3
- package/dist/gateway/static/root/assets/extension-settings-page-CWIi7xsn.js +0 -1
- package/dist/gateway/static/root/assets/fetch-8QNN25Sh.js +0 -1
- package/dist/gateway/static/root/assets/gateway-config-api-2sLP2kkA.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-DMHiZc-2.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-CAODVDiA.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-_d4DjmeX.js +0 -4
- package/dist/gateway/static/root/assets/goals-config-api-B4qeLKQS.js +0 -1
- package/dist/gateway/static/root/assets/goals-page-lgsop664.js +0 -2
- package/dist/gateway/static/root/assets/goals-settings-Dx_1ZNgb.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-DeTseEk_.js +0 -1
- package/dist/gateway/static/root/assets/index-CphxClbs.css +0 -1
- package/dist/gateway/static/root/assets/index-VWvztcst.js +0 -257
- package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-BpwpMmlk.js +0 -1
- package/dist/gateway/static/root/assets/locale-store-Ba72bFmP.js +0 -1
- package/dist/gateway/static/root/assets/logs-page-9_hbTg3j.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-B1CUvZxh.js +0 -2
- package/dist/gateway/static/root/assets/notes-workbench-CerZOeLW.js +0 -2
- package/dist/gateway/static/root/assets/project-detail-page-BmrApldl.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-tTn-k76N.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-DeA2-pOi.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-D4kvvcL1.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-CI1OG4gq.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-cmclUJzS.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page--zO-A1W1.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-DNJvqA67.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-DWr_0Xgn.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-DEGsTbJx.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-C-V73ZaJ.js +0 -1
- package/dist/gateway/static/root/assets/user-profile-fields-editor-B1cxoGkh.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-DGpIUelZ.js +0 -1
- package/dist/gateway/static/root/assets/work-item-detail-page-fzWTxF26.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-Dt_x8LGg.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-Bg02zEUh.js +0 -11
|
@@ -30,6 +30,28 @@ const alibabaSttModels = [
|
|
|
30
30
|
name: "Paraformer MTL v1 (Multilingual)"
|
|
31
31
|
}
|
|
32
32
|
];
|
|
33
|
+
const localSttModels = [
|
|
34
|
+
{
|
|
35
|
+
id: "sensevoice-small",
|
|
36
|
+
name: "SenseVoice Small 中文(推荐)",
|
|
37
|
+
description: "约 230 MB · 普通话、粤语、中英混说"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "tiny",
|
|
41
|
+
name: "Whisper Tiny (Fastest)",
|
|
42
|
+
description: "About 45 MB"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "base",
|
|
46
|
+
name: "Whisper Base (Multilingual)",
|
|
47
|
+
description: "About 80 MB"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "small",
|
|
51
|
+
name: "Whisper Small (Higher quality)",
|
|
52
|
+
description: "About 245 MB"
|
|
53
|
+
}
|
|
54
|
+
];
|
|
33
55
|
const alibabaTtsModels = [
|
|
34
56
|
{
|
|
35
57
|
id: "qwen-tts",
|
|
@@ -133,6 +155,30 @@ const edgeVoices = [
|
|
|
133
155
|
}
|
|
134
156
|
];
|
|
135
157
|
const builtinVoiceProviderMetadata = [
|
|
158
|
+
{
|
|
159
|
+
id: "xopc-local",
|
|
160
|
+
capability: "stt",
|
|
161
|
+
displayName: "xopc Local Voice",
|
|
162
|
+
description: "Private on-device Chinese and multilingual transcription. Audio never leaves this device.",
|
|
163
|
+
aliases: ["local"],
|
|
164
|
+
models: localSttModels,
|
|
165
|
+
fields: [{
|
|
166
|
+
key: "model",
|
|
167
|
+
label: "Model",
|
|
168
|
+
type: "select",
|
|
169
|
+
options: localSttModels,
|
|
170
|
+
defaultValue: "sensevoice-small"
|
|
171
|
+
}, {
|
|
172
|
+
key: "language",
|
|
173
|
+
label: "Language hint",
|
|
174
|
+
type: "string",
|
|
175
|
+
placeholder: "zh"
|
|
176
|
+
}],
|
|
177
|
+
diagnostics: {
|
|
178
|
+
requiresApiKey: false,
|
|
179
|
+
configPath: "tools.media.audio.providers.xopc-local"
|
|
180
|
+
}
|
|
181
|
+
},
|
|
136
182
|
{
|
|
137
183
|
id: "alibaba",
|
|
138
184
|
capability: "stt",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtin.js","names":[],"sources":["../../../../src/voice/metadata/builtin.ts"],"sourcesContent":["import { registerVoiceProviderMetadata } from './registry.js';\nimport type { VoiceOptionMetadata, VoiceProviderMetadata } from './types.js';\nimport { MINIMAX_TTS_MODELS, MINIMAX_TTS_VOICES, OPENAI_TTS_MODELS, OPENAI_TTS_VOICES } from '../tts/providers/index.js';\n\nfunction titleCase(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n\nfunction options(values: readonly string[], names?: Record<string, string>): VoiceOptionMetadata[] {\n return values.map((id) => ({ id, name: names?.[id] ?? titleCase(id) }));\n}\n\nconst alibabaSttModels = [\n { id: 'paraformer-v2', name: 'Paraformer v2 (Recommended)' },\n { id: 'paraformer-v1', name: 'Paraformer v1' },\n { id: 'paraformer-8k-v1', name: 'Paraformer 8k v1 (Phone)' },\n { id: 'paraformer-mtl-v1', name: 'Paraformer MTL v1 (Multilingual)' },\n];\n\nconst alibabaTtsModels = [\n { id: 'qwen-tts', name: 'Qwen TTS (Recommended)' },\n { id: 'qwen-tts-realtime', name: 'Qwen TTS Realtime' },\n { id: 'qwen3-tts-flash', name: 'Qwen3 TTS Flash' },\n { id: 'qwen3-tts-instruct-flash', name: 'Qwen3 TTS Instruct Flash' },\n];\n\nconst alibabaTtsVoices = [\n { id: 'Cherry', name: 'Cherry' },\n { id: 'Ethan', name: 'Ethan' },\n { id: 'Serena', name: 'Serena' },\n { id: 'Chelsie', name: 'Chelsie' },\n { id: 'longxiaochun', name: 'Long Xiao Chun (龙小春)' },\n { id: 'longxiaobai', name: 'Long Xiao Bai (龙小白)' },\n { id: 'longwan', name: 'Long Wan (龙婉)' },\n { id: 'longcheng', name: 'Long Cheng (龙呈)' },\n];\n\nconst edgeVoices = [\n { id: 'en-US-MichelleNeural', name: 'Michelle (US English, Female)' },\n { id: 'en-US-JennyNeural', name: 'Jenny (US English, Female)' },\n { id: 'en-US-GuyNeural', name: 'Guy (US English, Male)' },\n { id: 'en-GB-SoniaNeural', name: 'Sonia (UK English, Female)' },\n { id: 'en-GB-RyanNeural', name: 'Ryan (UK English, Male)' },\n { id: 'zh-CN-XiaoxiaoNeural', name: '晓晓 (中文, 女声)' },\n { id: 'zh-CN-YunyangNeural', name: '云扬 (中文, 男声)' },\n { id: 'zh-CN-XiaoyiNeural', name: '晓伊 (中文, 女声)' },\n { id: 'ja-JP-NanamiNeural', name: '七海 (日本語, 女性)' },\n { id: 'de-DE-KatjaNeural', name: 'Katja (Deutsch, Weiblich)' },\n { id: 'fr-FR-DeniseNeural', name: 'Denise (Français, Féminin)' },\n { id: 'es-ES-ElviraNeural', name: 'Elvira (Español, Femenino)' },\n];\n\nexport const builtinVoiceProviderMetadata: VoiceProviderMetadata[] = [\n {\n id: 'alibaba',\n capability: 'stt',\n displayName: 'Alibaba DashScope',\n description: 'DashScope Paraformer speech-to-text, strong Chinese recognition.',\n aliases: ['dashscope', 'paraformer'],\n models: alibabaSttModels,\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'DASHSCOPE_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: alibabaSttModels, defaultValue: 'paraformer-v2' },\n { key: 'language', label: 'Language hint', type: 'string', placeholder: 'zh' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['DASHSCOPE_API_KEY'], configPath: 'tools.media.audio.providers.alibaba' },\n },\n {\n id: 'openai',\n capability: 'stt',\n displayName: 'OpenAI Whisper',\n description: 'OpenAI Whisper transcription endpoint.',\n models: [{ id: 'whisper-1', name: 'Whisper-1' }],\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'OPENAI_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: [{ id: 'whisper-1', name: 'Whisper-1' }], defaultValue: 'whisper-1' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.openai.com/v1' },\n { key: 'language', label: 'Language hint', type: 'string', placeholder: 'en' },\n { key: 'prompt', label: 'Prompt', type: 'textarea' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['OPENAI_API_KEY'], configPath: 'tools.media.audio.providers.openai' },\n },\n {\n id: 'openai',\n capability: 'tts',\n displayName: 'OpenAI',\n description: 'OpenAI speech synthesis with multiple voices.',\n models: options(OPENAI_TTS_MODELS, {\n 'gpt-4o-mini-tts': 'GPT-4o Mini TTS (Latest)',\n 'tts-1': 'TTS-1 (Fast)',\n 'tts-1-hd': 'TTS-1 HD (High Quality)',\n }),\n voices: options(OPENAI_TTS_VOICES),\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'OPENAI_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: options(OPENAI_TTS_MODELS), defaultValue: 'tts-1' },\n { key: 'voice', label: 'Voice', type: 'select', options: options(OPENAI_TTS_VOICES), defaultValue: 'alloy' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.openai.com/v1' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['OPENAI_API_KEY'], configPath: 'messages.tts.providers.openai' },\n },\n {\n id: 'alibaba',\n capability: 'tts',\n displayName: 'Alibaba DashScope',\n description: 'DashScope Qwen TTS, strong Chinese voice quality.',\n models: alibabaTtsModels,\n voices: alibabaTtsVoices,\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'DASHSCOPE_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: alibabaTtsModels, defaultValue: 'qwen-tts' },\n { key: 'voice', label: 'Voice', type: 'select', options: alibabaTtsVoices, defaultValue: 'longxiaochun' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['DASHSCOPE_API_KEY'], configPath: 'messages.tts.providers.alibaba' },\n },\n {\n id: 'minimax',\n capability: 'tts',\n displayName: 'MiniMax',\n description: 'MiniMax high-quality async text-to-speech.',\n models: options(MINIMAX_TTS_MODELS),\n voices: options(MINIMAX_TTS_VOICES),\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'eyJ...', description: 'MINIMAX_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: options(MINIMAX_TTS_MODELS), defaultValue: 'speech-2.8-hd' },\n { key: 'voice', label: 'Voice', type: 'select', options: options(MINIMAX_TTS_VOICES), defaultValue: 'male-qn-qingse' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.minimaxi.com/v1' },\n { key: 'groupId', label: 'Group ID', type: 'string' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['MINIMAX_API_KEY'], configPath: 'messages.tts.providers.minimax' },\n },\n {\n id: 'edge',\n capability: 'tts',\n displayName: 'Microsoft Edge',\n description: 'Free Edge TTS endpoint, no API key required.',\n models: [{ id: 'edge-default', name: 'Edge TTS (Free)' }],\n voices: edgeVoices,\n fields: [\n { key: 'voice', label: 'Voice', type: 'select', options: edgeVoices, defaultValue: 'en-US-MichelleNeural' },\n { key: 'lang', label: 'Language', type: 'string', placeholder: 'en-US' },\n { key: 'outputFormat', label: 'Output format', type: 'string', placeholder: 'audio-24khz-48kbitrate-mono-mp3' },\n { key: 'rate', label: 'Rate', type: 'string', placeholder: '+0%' },\n { key: 'pitch', label: 'Pitch', type: 'string', placeholder: '+0Hz' },\n { key: 'volume', label: 'Volume', type: 'string', placeholder: '+0%' },\n { key: 'proxy', label: 'Proxy', type: 'string' },\n ],\n diagnostics: { requiresApiKey: false, configPath: 'messages.tts.providers.edge' },\n },\n];\n\nfor (const metadata of builtinVoiceProviderMetadata) {\n registerVoiceProviderMetadata(metadata);\n}\n"],"mappings":";;;;;AAIA,SAAS,UAAU,OAAuB;AACxC,QAAO,MAAM,OAAO,EAAE,CAAC,aAAa,GAAG,MAAM,MAAM,EAAE;;AAGvD,SAAS,QAAQ,QAA2B,OAAuD;AACjG,QAAO,OAAO,KAAK,QAAQ;EAAE;EAAI,MAAM,QAAQ,OAAO,UAAU,GAAG;EAAE,EAAE;;AAGzE,MAAM,mBAAmB;CACvB;EAAE,IAAI;EAAiB,MAAM;EAA+B;CAC5D;EAAE,IAAI;EAAiB,MAAM;EAAiB;CAC9C;EAAE,IAAI;EAAoB,MAAM;EAA4B;CAC5D;EAAE,IAAI;EAAqB,MAAM;EAAoC;CACtE;AAED,MAAM,mBAAmB;CACvB;EAAE,IAAI;EAAY,MAAM;EAA0B;CAClD;EAAE,IAAI;EAAqB,MAAM;EAAqB;CACtD;EAAE,IAAI;EAAmB,MAAM;EAAmB;CAClD;EAAE,IAAI;EAA4B,MAAM;EAA4B;CACrE;AAED,MAAM,mBAAmB;CACvB;EAAE,IAAI;EAAU,MAAM;EAAU;CAChC;EAAE,IAAI;EAAS,MAAM;EAAS;CAC9B;EAAE,IAAI;EAAU,MAAM;EAAU;CAChC;EAAE,IAAI;EAAW,MAAM;EAAW;CAClC;EAAE,IAAI;EAAgB,MAAM;EAAwB;CACpD;EAAE,IAAI;EAAe,MAAM;EAAuB;CAClD;EAAE,IAAI;EAAW,MAAM;EAAiB;CACxC;EAAE,IAAI;EAAa,MAAM;EAAmB;CAC7C;AAED,MAAM,aAAa;CACjB;EAAE,IAAI;EAAwB,MAAM;EAAiC;CACrE;EAAE,IAAI;EAAqB,MAAM;EAA8B;CAC/D;EAAE,IAAI;EAAmB,MAAM;EAA0B;CACzD;EAAE,IAAI;EAAqB,MAAM;EAA8B;CAC/D;EAAE,IAAI;EAAoB,MAAM;EAA2B;CAC3D;EAAE,IAAI;EAAwB,MAAM;EAAe;CACnD;EAAE,IAAI;EAAuB,MAAM;EAAe;CAClD;EAAE,IAAI;EAAsB,MAAM;EAAe;CACjD;EAAE,IAAI;EAAsB,MAAM;EAAgB;CAClD;EAAE,IAAI;EAAqB,MAAM;EAA6B;CAC9D;EAAE,IAAI;EAAsB,MAAM;EAA8B;CAChE;EAAE,IAAI;EAAsB,MAAM;EAA8B;CACjE;AAED,MAAa,+BAAwD;CACnE;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,SAAS,CAAC,aAAa,aAAa;EACpC,QAAQ;EACR,QAAQ;GACN;IAAE,KAAK;IAAU,OAAO;IAAW,MAAM;IAAY,QAAQ;IAAM,aAAa;IAAU,aAAa;IAAqB;GAC5H;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS;IAAkB,cAAc;IAAiB;GAC1G;IAAE,KAAK;IAAY,OAAO;IAAiB,MAAM;IAAU,aAAa;IAAM;GAC/E;EACD,aAAa;GAAE,gBAAgB;GAAM,SAAS,CAAC,oBAAoB;GAAE,YAAY;GAAuC;EACzH;CACD;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,QAAQ,CAAC;GAAE,IAAI;GAAa,MAAM;GAAa,CAAC;EAChD,QAAQ;GACN;IAAE,KAAK;IAAU,OAAO;IAAW,MAAM;IAAY,QAAQ;IAAM,aAAa;IAAU,aAAa;IAAkB;GACzH;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS,CAAC;KAAE,IAAI;KAAa,MAAM;KAAa,CAAC;IAAE,cAAc;IAAa;GAC9H;IAAE,KAAK;IAAW,OAAO;IAAY,MAAM;IAAU,aAAa;IAA6B;GAC/F;IAAE,KAAK;IAAY,OAAO;IAAiB,MAAM;IAAU,aAAa;IAAM;GAC9E;IAAE,KAAK;IAAU,OAAO;IAAU,MAAM;IAAY;GACrD;EACD,aAAa;GAAE,gBAAgB;GAAM,SAAS,CAAC,iBAAiB;GAAE,YAAY;GAAsC;EACrH;CACD;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,QAAQ,QAAQ,mBAAmB;GACjC,mBAAmB;GACnB,SAAS;GACT,YAAY;GACb,CAAC;EACF,QAAQ,QAAQ,kBAAkB;EAClC,QAAQ;GACN;IAAE,KAAK;IAAU,OAAO;IAAW,MAAM;IAAY,QAAQ;IAAM,aAAa;IAAU,aAAa;IAAkB;GACzH;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS,QAAQ,kBAAkB;IAAE,cAAc;IAAS;GAC5G;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS,QAAQ,kBAAkB;IAAE,cAAc;IAAS;GAC5G;IAAE,KAAK;IAAW,OAAO;IAAY,MAAM;IAAU,aAAa;IAA6B;GAChG;EACD,aAAa;GAAE,gBAAgB;GAAM,SAAS,CAAC,iBAAiB;GAAE,YAAY;GAAiC;EAChH;CACD;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,QAAQ;EACR,QAAQ;EACR,QAAQ;GACN;IAAE,KAAK;IAAU,OAAO;IAAW,MAAM;IAAY,QAAQ;IAAM,aAAa;IAAU,aAAa;IAAqB;GAC5H;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS;IAAkB,cAAc;IAAY;GACrG;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS;IAAkB,cAAc;IAAgB;GAC1G;EACD,aAAa;GAAE,gBAAgB;GAAM,SAAS,CAAC,oBAAoB;GAAE,YAAY;GAAkC;EACpH;CACD;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,QAAQ,QAAQ,mBAAmB;EACnC,QAAQ,QAAQ,mBAAmB;EACnC,QAAQ;GACN;IAAE,KAAK;IAAU,OAAO;IAAW,MAAM;IAAY,QAAQ;IAAM,aAAa;IAAU,aAAa;IAAmB;GAC1H;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS,QAAQ,mBAAmB;IAAE,cAAc;IAAiB;GACrH;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS,QAAQ,mBAAmB;IAAE,cAAc;IAAkB;GACtH;IAAE,KAAK;IAAW,OAAO;IAAY,MAAM;IAAU,aAAa;IAA+B;GACjG;IAAE,KAAK;IAAW,OAAO;IAAY,MAAM;IAAU;GACtD;EACD,aAAa;GAAE,gBAAgB;GAAM,SAAS,CAAC,kBAAkB;GAAE,YAAY;GAAkC;EAClH;CACD;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,QAAQ,CAAC;GAAE,IAAI;GAAgB,MAAM;GAAmB,CAAC;EACzD,QAAQ;EACR,QAAQ;GACN;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS;IAAY,cAAc;IAAwB;GAC3G;IAAE,KAAK;IAAQ,OAAO;IAAY,MAAM;IAAU,aAAa;IAAS;GACxE;IAAE,KAAK;IAAgB,OAAO;IAAiB,MAAM;IAAU,aAAa;IAAmC;GAC/G;IAAE,KAAK;IAAQ,OAAO;IAAQ,MAAM;IAAU,aAAa;IAAO;GAClE;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,aAAa;IAAQ;GACrE;IAAE,KAAK;IAAU,OAAO;IAAU,MAAM;IAAU,aAAa;IAAO;GACtE;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU;GACjD;EACD,aAAa;GAAE,gBAAgB;GAAO,YAAY;GAA+B;EAClF;CACF;AAED,KAAK,MAAM,YAAY,6BACrB,+BAA8B,SAAS"}
|
|
1
|
+
{"version":3,"file":"builtin.js","names":[],"sources":["../../../../src/voice/metadata/builtin.ts"],"sourcesContent":["import { registerVoiceProviderMetadata } from './registry.js';\nimport type { VoiceOptionMetadata, VoiceProviderMetadata } from './types.js';\nimport { MINIMAX_TTS_MODELS, MINIMAX_TTS_VOICES, OPENAI_TTS_MODELS, OPENAI_TTS_VOICES } from '../tts/providers/index.js';\n\nfunction titleCase(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n\nfunction options(values: readonly string[], names?: Record<string, string>): VoiceOptionMetadata[] {\n return values.map((id) => ({ id, name: names?.[id] ?? titleCase(id) }));\n}\n\nconst alibabaSttModels = [\n { id: 'paraformer-v2', name: 'Paraformer v2 (Recommended)' },\n { id: 'paraformer-v1', name: 'Paraformer v1' },\n { id: 'paraformer-8k-v1', name: 'Paraformer 8k v1 (Phone)' },\n { id: 'paraformer-mtl-v1', name: 'Paraformer MTL v1 (Multilingual)' },\n];\n\nconst localSttModels = [\n {\n id: 'sensevoice-small',\n name: 'SenseVoice Small 中文(推荐)',\n description: '约 230 MB · 普通话、粤语、中英混说',\n },\n { id: 'tiny', name: 'Whisper Tiny (Fastest)', description: 'About 45 MB' },\n { id: 'base', name: 'Whisper Base (Multilingual)', description: 'About 80 MB' },\n { id: 'small', name: 'Whisper Small (Higher quality)', description: 'About 245 MB' },\n];\n\nconst alibabaTtsModels = [\n { id: 'qwen-tts', name: 'Qwen TTS (Recommended)' },\n { id: 'qwen-tts-realtime', name: 'Qwen TTS Realtime' },\n { id: 'qwen3-tts-flash', name: 'Qwen3 TTS Flash' },\n { id: 'qwen3-tts-instruct-flash', name: 'Qwen3 TTS Instruct Flash' },\n];\n\nconst alibabaTtsVoices = [\n { id: 'Cherry', name: 'Cherry' },\n { id: 'Ethan', name: 'Ethan' },\n { id: 'Serena', name: 'Serena' },\n { id: 'Chelsie', name: 'Chelsie' },\n { id: 'longxiaochun', name: 'Long Xiao Chun (龙小春)' },\n { id: 'longxiaobai', name: 'Long Xiao Bai (龙小白)' },\n { id: 'longwan', name: 'Long Wan (龙婉)' },\n { id: 'longcheng', name: 'Long Cheng (龙呈)' },\n];\n\nconst edgeVoices = [\n { id: 'en-US-MichelleNeural', name: 'Michelle (US English, Female)' },\n { id: 'en-US-JennyNeural', name: 'Jenny (US English, Female)' },\n { id: 'en-US-GuyNeural', name: 'Guy (US English, Male)' },\n { id: 'en-GB-SoniaNeural', name: 'Sonia (UK English, Female)' },\n { id: 'en-GB-RyanNeural', name: 'Ryan (UK English, Male)' },\n { id: 'zh-CN-XiaoxiaoNeural', name: '晓晓 (中文, 女声)' },\n { id: 'zh-CN-YunyangNeural', name: '云扬 (中文, 男声)' },\n { id: 'zh-CN-XiaoyiNeural', name: '晓伊 (中文, 女声)' },\n { id: 'ja-JP-NanamiNeural', name: '七海 (日本語, 女性)' },\n { id: 'de-DE-KatjaNeural', name: 'Katja (Deutsch, Weiblich)' },\n { id: 'fr-FR-DeniseNeural', name: 'Denise (Français, Féminin)' },\n { id: 'es-ES-ElviraNeural', name: 'Elvira (Español, Femenino)' },\n];\n\nexport const builtinVoiceProviderMetadata: VoiceProviderMetadata[] = [\n {\n id: 'xopc-local',\n capability: 'stt',\n displayName: 'xopc Local Voice',\n description: 'Private on-device Chinese and multilingual transcription. Audio never leaves this device.',\n aliases: ['local'],\n models: localSttModels,\n fields: [\n { key: 'model', label: 'Model', type: 'select', options: localSttModels, defaultValue: 'sensevoice-small' },\n { key: 'language', label: 'Language hint', type: 'string', placeholder: 'zh' },\n ],\n diagnostics: { requiresApiKey: false, configPath: 'tools.media.audio.providers.xopc-local' },\n },\n {\n id: 'alibaba',\n capability: 'stt',\n displayName: 'Alibaba DashScope',\n description: 'DashScope Paraformer speech-to-text, strong Chinese recognition.',\n aliases: ['dashscope', 'paraformer'],\n models: alibabaSttModels,\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'DASHSCOPE_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: alibabaSttModels, defaultValue: 'paraformer-v2' },\n { key: 'language', label: 'Language hint', type: 'string', placeholder: 'zh' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['DASHSCOPE_API_KEY'], configPath: 'tools.media.audio.providers.alibaba' },\n },\n {\n id: 'openai',\n capability: 'stt',\n displayName: 'OpenAI Whisper',\n description: 'OpenAI Whisper transcription endpoint.',\n models: [{ id: 'whisper-1', name: 'Whisper-1' }],\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'OPENAI_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: [{ id: 'whisper-1', name: 'Whisper-1' }], defaultValue: 'whisper-1' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.openai.com/v1' },\n { key: 'language', label: 'Language hint', type: 'string', placeholder: 'en' },\n { key: 'prompt', label: 'Prompt', type: 'textarea' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['OPENAI_API_KEY'], configPath: 'tools.media.audio.providers.openai' },\n },\n {\n id: 'openai',\n capability: 'tts',\n displayName: 'OpenAI',\n description: 'OpenAI speech synthesis with multiple voices.',\n models: options(OPENAI_TTS_MODELS, {\n 'gpt-4o-mini-tts': 'GPT-4o Mini TTS (Latest)',\n 'tts-1': 'TTS-1 (Fast)',\n 'tts-1-hd': 'TTS-1 HD (High Quality)',\n }),\n voices: options(OPENAI_TTS_VOICES),\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'OPENAI_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: options(OPENAI_TTS_MODELS), defaultValue: 'tts-1' },\n { key: 'voice', label: 'Voice', type: 'select', options: options(OPENAI_TTS_VOICES), defaultValue: 'alloy' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.openai.com/v1' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['OPENAI_API_KEY'], configPath: 'messages.tts.providers.openai' },\n },\n {\n id: 'alibaba',\n capability: 'tts',\n displayName: 'Alibaba DashScope',\n description: 'DashScope Qwen TTS, strong Chinese voice quality.',\n models: alibabaTtsModels,\n voices: alibabaTtsVoices,\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'sk-...', description: 'DASHSCOPE_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: alibabaTtsModels, defaultValue: 'qwen-tts' },\n { key: 'voice', label: 'Voice', type: 'select', options: alibabaTtsVoices, defaultValue: 'longxiaochun' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['DASHSCOPE_API_KEY'], configPath: 'messages.tts.providers.alibaba' },\n },\n {\n id: 'minimax',\n capability: 'tts',\n displayName: 'MiniMax',\n description: 'MiniMax high-quality async text-to-speech.',\n models: options(MINIMAX_TTS_MODELS),\n voices: options(MINIMAX_TTS_VOICES),\n fields: [\n { key: 'apiKey', label: 'API Key', type: 'password', secret: true, placeholder: 'eyJ...', description: 'MINIMAX_API_KEY' },\n { key: 'model', label: 'Model', type: 'select', options: options(MINIMAX_TTS_MODELS), defaultValue: 'speech-2.8-hd' },\n { key: 'voice', label: 'Voice', type: 'select', options: options(MINIMAX_TTS_VOICES), defaultValue: 'male-qn-qingse' },\n { key: 'baseUrl', label: 'Base URL', type: 'string', placeholder: 'https://api.minimaxi.com/v1' },\n { key: 'groupId', label: 'Group ID', type: 'string' },\n ],\n diagnostics: { requiresApiKey: true, envKeys: ['MINIMAX_API_KEY'], configPath: 'messages.tts.providers.minimax' },\n },\n {\n id: 'edge',\n capability: 'tts',\n displayName: 'Microsoft Edge',\n description: 'Free Edge TTS endpoint, no API key required.',\n models: [{ id: 'edge-default', name: 'Edge TTS (Free)' }],\n voices: edgeVoices,\n fields: [\n { key: 'voice', label: 'Voice', type: 'select', options: edgeVoices, defaultValue: 'en-US-MichelleNeural' },\n { key: 'lang', label: 'Language', type: 'string', placeholder: 'en-US' },\n { key: 'outputFormat', label: 'Output format', type: 'string', placeholder: 'audio-24khz-48kbitrate-mono-mp3' },\n { key: 'rate', label: 'Rate', type: 'string', placeholder: '+0%' },\n { key: 'pitch', label: 'Pitch', type: 'string', placeholder: '+0Hz' },\n { key: 'volume', label: 'Volume', type: 'string', placeholder: '+0%' },\n { key: 'proxy', label: 'Proxy', type: 'string' },\n ],\n diagnostics: { requiresApiKey: false, configPath: 'messages.tts.providers.edge' },\n },\n];\n\nfor (const metadata of builtinVoiceProviderMetadata) {\n registerVoiceProviderMetadata(metadata);\n}\n"],"mappings":";;;;;AAIA,SAAS,UAAU,OAAuB;AACxC,QAAO,MAAM,OAAO,EAAE,CAAC,aAAa,GAAG,MAAM,MAAM,EAAE;;AAGvD,SAAS,QAAQ,QAA2B,OAAuD;AACjG,QAAO,OAAO,KAAK,QAAQ;EAAE;EAAI,MAAM,QAAQ,OAAO,UAAU,GAAG;EAAE,EAAE;;AAGzE,MAAM,mBAAmB;CACvB;EAAE,IAAI;EAAiB,MAAM;EAA+B;CAC5D;EAAE,IAAI;EAAiB,MAAM;EAAiB;CAC9C;EAAE,IAAI;EAAoB,MAAM;EAA4B;CAC5D;EAAE,IAAI;EAAqB,MAAM;EAAoC;CACtE;AAED,MAAM,iBAAiB;CACrB;EACE,IAAI;EACJ,MAAM;EACN,aAAa;EACd;CACD;EAAE,IAAI;EAAQ,MAAM;EAA0B,aAAa;EAAe;CAC1E;EAAE,IAAI;EAAQ,MAAM;EAA+B,aAAa;EAAe;CAC/E;EAAE,IAAI;EAAS,MAAM;EAAkC,aAAa;EAAgB;CACrF;AAED,MAAM,mBAAmB;CACvB;EAAE,IAAI;EAAY,MAAM;EAA0B;CAClD;EAAE,IAAI;EAAqB,MAAM;EAAqB;CACtD;EAAE,IAAI;EAAmB,MAAM;EAAmB;CAClD;EAAE,IAAI;EAA4B,MAAM;EAA4B;CACrE;AAED,MAAM,mBAAmB;CACvB;EAAE,IAAI;EAAU,MAAM;EAAU;CAChC;EAAE,IAAI;EAAS,MAAM;EAAS;CAC9B;EAAE,IAAI;EAAU,MAAM;EAAU;CAChC;EAAE,IAAI;EAAW,MAAM;EAAW;CAClC;EAAE,IAAI;EAAgB,MAAM;EAAwB;CACpD;EAAE,IAAI;EAAe,MAAM;EAAuB;CAClD;EAAE,IAAI;EAAW,MAAM;EAAiB;CACxC;EAAE,IAAI;EAAa,MAAM;EAAmB;CAC7C;AAED,MAAM,aAAa;CACjB;EAAE,IAAI;EAAwB,MAAM;EAAiC;CACrE;EAAE,IAAI;EAAqB,MAAM;EAA8B;CAC/D;EAAE,IAAI;EAAmB,MAAM;EAA0B;CACzD;EAAE,IAAI;EAAqB,MAAM;EAA8B;CAC/D;EAAE,IAAI;EAAoB,MAAM;EAA2B;CAC3D;EAAE,IAAI;EAAwB,MAAM;EAAe;CACnD;EAAE,IAAI;EAAuB,MAAM;EAAe;CAClD;EAAE,IAAI;EAAsB,MAAM;EAAe;CACjD;EAAE,IAAI;EAAsB,MAAM;EAAgB;CAClD;EAAE,IAAI;EAAqB,MAAM;EAA6B;CAC9D;EAAE,IAAI;EAAsB,MAAM;EAA8B;CAChE;EAAE,IAAI;EAAsB,MAAM;EAA8B;CACjE;AAED,MAAa,+BAAwD;CACnE;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,SAAS,CAAC,QAAQ;EAClB,QAAQ;EACR,QAAQ,CACN;GAAE,KAAK;GAAS,OAAO;GAAS,MAAM;GAAU,SAAS;GAAgB,cAAc;GAAoB,EAC3G;GAAE,KAAK;GAAY,OAAO;GAAiB,MAAM;GAAU,aAAa;GAAM,CAC/E;EACD,aAAa;GAAE,gBAAgB;GAAO,YAAY;GAA0C;EAC7F;CACD;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,SAAS,CAAC,aAAa,aAAa;EACpC,QAAQ;EACR,QAAQ;GACN;IAAE,KAAK;IAAU,OAAO;IAAW,MAAM;IAAY,QAAQ;IAAM,aAAa;IAAU,aAAa;IAAqB;GAC5H;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS;IAAkB,cAAc;IAAiB;GAC1G;IAAE,KAAK;IAAY,OAAO;IAAiB,MAAM;IAAU,aAAa;IAAM;GAC/E;EACD,aAAa;GAAE,gBAAgB;GAAM,SAAS,CAAC,oBAAoB;GAAE,YAAY;GAAuC;EACzH;CACD;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,QAAQ,CAAC;GAAE,IAAI;GAAa,MAAM;GAAa,CAAC;EAChD,QAAQ;GACN;IAAE,KAAK;IAAU,OAAO;IAAW,MAAM;IAAY,QAAQ;IAAM,aAAa;IAAU,aAAa;IAAkB;GACzH;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS,CAAC;KAAE,IAAI;KAAa,MAAM;KAAa,CAAC;IAAE,cAAc;IAAa;GAC9H;IAAE,KAAK;IAAW,OAAO;IAAY,MAAM;IAAU,aAAa;IAA6B;GAC/F;IAAE,KAAK;IAAY,OAAO;IAAiB,MAAM;IAAU,aAAa;IAAM;GAC9E;IAAE,KAAK;IAAU,OAAO;IAAU,MAAM;IAAY;GACrD;EACD,aAAa;GAAE,gBAAgB;GAAM,SAAS,CAAC,iBAAiB;GAAE,YAAY;GAAsC;EACrH;CACD;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,QAAQ,QAAQ,mBAAmB;GACjC,mBAAmB;GACnB,SAAS;GACT,YAAY;GACb,CAAC;EACF,QAAQ,QAAQ,kBAAkB;EAClC,QAAQ;GACN;IAAE,KAAK;IAAU,OAAO;IAAW,MAAM;IAAY,QAAQ;IAAM,aAAa;IAAU,aAAa;IAAkB;GACzH;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS,QAAQ,kBAAkB;IAAE,cAAc;IAAS;GAC5G;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS,QAAQ,kBAAkB;IAAE,cAAc;IAAS;GAC5G;IAAE,KAAK;IAAW,OAAO;IAAY,MAAM;IAAU,aAAa;IAA6B;GAChG;EACD,aAAa;GAAE,gBAAgB;GAAM,SAAS,CAAC,iBAAiB;GAAE,YAAY;GAAiC;EAChH;CACD;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,QAAQ;EACR,QAAQ;EACR,QAAQ;GACN;IAAE,KAAK;IAAU,OAAO;IAAW,MAAM;IAAY,QAAQ;IAAM,aAAa;IAAU,aAAa;IAAqB;GAC5H;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS;IAAkB,cAAc;IAAY;GACrG;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS;IAAkB,cAAc;IAAgB;GAC1G;EACD,aAAa;GAAE,gBAAgB;GAAM,SAAS,CAAC,oBAAoB;GAAE,YAAY;GAAkC;EACpH;CACD;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,QAAQ,QAAQ,mBAAmB;EACnC,QAAQ,QAAQ,mBAAmB;EACnC,QAAQ;GACN;IAAE,KAAK;IAAU,OAAO;IAAW,MAAM;IAAY,QAAQ;IAAM,aAAa;IAAU,aAAa;IAAmB;GAC1H;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS,QAAQ,mBAAmB;IAAE,cAAc;IAAiB;GACrH;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS,QAAQ,mBAAmB;IAAE,cAAc;IAAkB;GACtH;IAAE,KAAK;IAAW,OAAO;IAAY,MAAM;IAAU,aAAa;IAA+B;GACjG;IAAE,KAAK;IAAW,OAAO;IAAY,MAAM;IAAU;GACtD;EACD,aAAa;GAAE,gBAAgB;GAAM,SAAS,CAAC,kBAAkB;GAAE,YAAY;GAAkC;EAClH;CACD;EACE,IAAI;EACJ,YAAY;EACZ,aAAa;EACb,aAAa;EACb,QAAQ,CAAC;GAAE,IAAI;GAAgB,MAAM;GAAmB,CAAC;EACzD,QAAQ;EACR,QAAQ;GACN;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,SAAS;IAAY,cAAc;IAAwB;GAC3G;IAAE,KAAK;IAAQ,OAAO;IAAY,MAAM;IAAU,aAAa;IAAS;GACxE;IAAE,KAAK;IAAgB,OAAO;IAAiB,MAAM;IAAU,aAAa;IAAmC;GAC/G;IAAE,KAAK;IAAQ,OAAO;IAAQ,MAAM;IAAU,aAAa;IAAO;GAClE;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU,aAAa;IAAQ;GACrE;IAAE,KAAK;IAAU,OAAO;IAAU,MAAM;IAAU,aAAa;IAAO;GACtE;IAAE,KAAK;IAAS,OAAO;IAAS,MAAM;IAAU;GACjD;EACD,aAAa;GAAE,gBAAgB;GAAO,YAAY;GAA+B;EAClF;CACF;AAED,KAAK,MAAM,YAAY,6BACrB,+BAA8B,SAAS"}
|
|
@@ -19,7 +19,13 @@ function resolveSTTProviderConfig(providerId, config, entry) {
|
|
|
19
19
|
}
|
|
20
20
|
const fields = readSttProviderFields(resolveSttProviderConfigSlice(providerId, config), entry);
|
|
21
21
|
const apiKey = resolveApiKey(providerId, fields, plugin.envKey);
|
|
22
|
-
|
|
22
|
+
const requiresApiKey = plugin.requiresApiKey !== false;
|
|
23
|
+
const model = fields.model ?? entry?.model ?? plugin.defaultModels?.audio;
|
|
24
|
+
if (plugin.isConfigured && !plugin.isConfigured({ model })) {
|
|
25
|
+
log.debug({ providerId }, `STT provider "${providerId}" runtime is not ready; skipping`);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (requiresApiKey && !apiKey) {
|
|
23
29
|
log.debug({ providerId }, `STT provider "${providerId}" missing API key; skipping`);
|
|
24
30
|
return null;
|
|
25
31
|
}
|
|
@@ -28,7 +34,7 @@ function resolveSTTProviderConfig(providerId, config, entry) {
|
|
|
28
34
|
...apiKey ? { apiKey } : {},
|
|
29
35
|
...fields.baseUrl ? { baseUrl: fields.baseUrl } : {},
|
|
30
36
|
...fields.headers ? { headers: fields.headers } : {},
|
|
31
|
-
...
|
|
37
|
+
...model ? { model } : {},
|
|
32
38
|
...fields.language ? { language: fields.language } : {},
|
|
33
39
|
...fields.prompt ? { prompt: fields.prompt } : {}
|
|
34
40
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.js","names":[],"sources":["../../../../src/voice/stt/factory.ts"],"sourcesContent":["/**\n * STT factory — registry-driven provider chain construction.\n *\n * Resolves STTConfig slices into `AudioProviderResolvedConfig` arrays that are\n * consumed by `runAudioTranscription`. Side-effect import of `./providers/index.js`\n * ensures all built-in STT providers self-register with the media-understanding\n * registry before the first lookup.\n */\n\nimport { createLogger } from '../../utils/logger.js';\n\nimport './providers/index.js'; // side-effect: register built-in STT providers\nimport { resolveModelEntries } from '../../media-understanding/resolve-entries.js';\nimport {\n getMediaUnderstandingProvider,\n listMediaUnderstandingProviders,\n} from '../../media-understanding/registry.js';\nimport type { AudioProviderResolvedConfig } from '../../media-understanding/audio-transcription-runner.js';\nimport {\n STT_LEGACY_ENV_KEYS,\n readSttProviderFields,\n resolveSttProviderConfigSlice,\n} from './config-slice.js';\nimport type { MediaUnderstandingModelEntry, STTConfig } from './types.js';\n\nconst log = createLogger('STT:Factory');\n\nfunction resolveApiKey(\n providerId: string,\n fields: ReturnType<typeof readSttProviderFields>,\n envKey?: string,\n): string | undefined {\n return (\n fields.apiKey ??\n (envKey ? process.env[envKey] : undefined) ??\n process.env[STT_LEGACY_ENV_KEYS[providerId] ?? '']\n );\n}\n\n/** Resolve a single STT provider config slice → runner-shaped resolved config, or null when unavailable. */\nexport function resolveSTTProviderConfig(\n providerId: string,\n config: STTConfig,\n entry?: MediaUnderstandingModelEntry,\n): AudioProviderResolvedConfig | null {\n const plugin = getMediaUnderstandingProvider(providerId);\n if (!plugin || typeof plugin.transcribeAudio !== 'function') {\n log.warn(\n { providerId },\n `STT provider \"${providerId}\" is not registered or does not implement transcribeAudio`,\n );\n return null;\n }\n\n const slice = resolveSttProviderConfigSlice(providerId, config);\n const fields = readSttProviderFields(slice, entry);\n const apiKey = resolveApiKey(providerId, fields, plugin.envKey);\n const requiresApiKey = plugin.requiresApiKey !== false;\n if (requiresApiKey && !apiKey) {\n log.debug({ providerId }, `STT provider \"${providerId}\" missing API key; skipping`);\n return null;\n }\n\n return {\n id: providerId,\n ...(apiKey ? { apiKey } : {}),\n ...(fields.baseUrl ? { baseUrl: fields.baseUrl } : {}),\n ...(fields.headers ? { headers: fields.headers } : {}),\n ...(
|
|
1
|
+
{"version":3,"file":"factory.js","names":[],"sources":["../../../../src/voice/stt/factory.ts"],"sourcesContent":["/**\n * STT factory — registry-driven provider chain construction.\n *\n * Resolves STTConfig slices into `AudioProviderResolvedConfig` arrays that are\n * consumed by `runAudioTranscription`. Side-effect import of `./providers/index.js`\n * ensures all built-in STT providers self-register with the media-understanding\n * registry before the first lookup.\n */\n\nimport { createLogger } from '../../utils/logger.js';\n\nimport './providers/index.js'; // side-effect: register built-in STT providers\nimport { resolveModelEntries } from '../../media-understanding/resolve-entries.js';\nimport {\n getMediaUnderstandingProvider,\n listMediaUnderstandingProviders,\n} from '../../media-understanding/registry.js';\nimport type { AudioProviderResolvedConfig } from '../../media-understanding/audio-transcription-runner.js';\nimport {\n STT_LEGACY_ENV_KEYS,\n readSttProviderFields,\n resolveSttProviderConfigSlice,\n} from './config-slice.js';\nimport type { MediaUnderstandingModelEntry, STTConfig } from './types.js';\n\nconst log = createLogger('STT:Factory');\n\nfunction resolveApiKey(\n providerId: string,\n fields: ReturnType<typeof readSttProviderFields>,\n envKey?: string,\n): string | undefined {\n return (\n fields.apiKey ??\n (envKey ? process.env[envKey] : undefined) ??\n process.env[STT_LEGACY_ENV_KEYS[providerId] ?? '']\n );\n}\n\n/** Resolve a single STT provider config slice → runner-shaped resolved config, or null when unavailable. */\nexport function resolveSTTProviderConfig(\n providerId: string,\n config: STTConfig,\n entry?: MediaUnderstandingModelEntry,\n): AudioProviderResolvedConfig | null {\n const plugin = getMediaUnderstandingProvider(providerId);\n if (!plugin || typeof plugin.transcribeAudio !== 'function') {\n log.warn(\n { providerId },\n `STT provider \"${providerId}\" is not registered or does not implement transcribeAudio`,\n );\n return null;\n }\n\n const slice = resolveSttProviderConfigSlice(providerId, config);\n const fields = readSttProviderFields(slice, entry);\n const apiKey = resolveApiKey(providerId, fields, plugin.envKey);\n const requiresApiKey = plugin.requiresApiKey !== false;\n const model = fields.model ?? entry?.model ?? plugin.defaultModels?.audio;\n if (plugin.isConfigured && !plugin.isConfigured({ model })) {\n log.debug({ providerId }, `STT provider \"${providerId}\" runtime is not ready; skipping`);\n return null;\n }\n if (requiresApiKey && !apiKey) {\n log.debug({ providerId }, `STT provider \"${providerId}\" missing API key; skipping`);\n return null;\n }\n\n return {\n id: providerId,\n ...(apiKey ? { apiKey } : {}),\n ...(fields.baseUrl ? { baseUrl: fields.baseUrl } : {}),\n ...(fields.headers ? { headers: fields.headers } : {}),\n ...(model ? { model } : {}),\n ...(fields.language ? { language: fields.language } : {}),\n ...(fields.prompt ? { prompt: fields.prompt } : {}),\n };\n}\n\nfunction sortAudioProvidersByAutoPriority(\n plugins: ReturnType<typeof listMediaUnderstandingProviders>,\n): string[] {\n return [...plugins]\n .filter(\n (plugin) =>\n plugin.capabilities?.includes('audio') && typeof plugin.transcribeAudio === 'function',\n )\n .sort((left, right) => {\n const leftOrder = left.autoPriority?.audio ?? Number.MAX_SAFE_INTEGER;\n const rightOrder = right.autoPriority?.audio ?? Number.MAX_SAFE_INTEGER;\n if (leftOrder !== rightOrder) {\n return leftOrder - rightOrder;\n }\n return left.id.localeCompare(right.id);\n })\n .map((plugin) => plugin.id);\n}\n\n/** Resolve provider order: explicit fallback when enabled, otherwise auto-select by registry order. */\nexport function resolveSTTProviderOrder(\n primary: string,\n fallback: STTConfig['fallback'] | undefined,\n config: STTConfig,\n): string[] {\n if (fallback?.enabled && fallback.order.length > 0) {\n const order: string[] = [primary];\n for (const providerId of fallback.order) {\n if (providerId !== primary && !order.includes(providerId)) {\n order.push(providerId);\n }\n }\n return order;\n }\n\n const configured = sortAudioProvidersByAutoPriority(listMediaUnderstandingProviders()).filter(\n (providerId) => resolveSTTProviderConfig(providerId, config) !== null,\n );\n\n const order: string[] = [primary];\n for (const providerId of configured) {\n if (providerId !== primary && !order.includes(providerId)) {\n order.push(providerId);\n }\n }\n return order;\n}\n\nfunction resolveModelEntryChain(config: STTConfig): AudioProviderResolvedConfig[] {\n const entries = resolveModelEntries({\n capability: 'audio',\n capabilityModels: config.models,\n sharedModels: config.sharedModels,\n });\n const chain: AudioProviderResolvedConfig[] = [];\n for (const entry of entries) {\n const providerId = entry.provider?.trim();\n if (!providerId) {\n continue;\n }\n const resolved = resolveSTTProviderConfig(providerId, config, entry);\n if (resolved) {\n chain.push(resolved);\n }\n }\n return chain;\n}\n\n/** Build the full chain of resolved provider configs in priority order. */\nexport function resolveSTTProviderChain(config: STTConfig): AudioProviderResolvedConfig[] {\n if (!config.enabled) {\n return [];\n }\n\n const modelChain = resolveModelEntryChain(config);\n if (modelChain.length > 0) {\n return modelChain;\n }\n\n const order = resolveSTTProviderOrder(config.provider, config.fallback, config);\n const chain: AudioProviderResolvedConfig[] = [];\n for (const providerId of order) {\n const resolved = resolveSTTProviderConfig(providerId, config);\n if (resolved) {\n chain.push(resolved);\n }\n }\n return chain;\n}\n"],"mappings":";;;;;;;aASqD;AAgBrD,MAAM,MAAM,aAAa,cAAc;AAEvC,SAAS,cACP,YACA,QACA,QACoB;AACpB,QACE,OAAO,WACN,SAAS,QAAQ,IAAI,UAAU,KAAA,MAChC,QAAQ,IAAI,oBAAoB,eAAe;;;AAKnD,SAAgB,yBACd,YACA,QACA,OACoC;CACpC,MAAM,SAAS,8BAA8B,WAAW;AACxD,KAAI,CAAC,UAAU,OAAO,OAAO,oBAAoB,YAAY;AAC3D,MAAI,KACF,EAAE,YAAY,EACd,iBAAiB,WAAW,2DAC7B;AACD,SAAO;;CAIT,MAAM,SAAS,sBADD,8BAA8B,YAAY,OACd,EAAE,MAAM;CAClD,MAAM,SAAS,cAAc,YAAY,QAAQ,OAAO,OAAO;CAC/D,MAAM,iBAAiB,OAAO,mBAAmB;CACjD,MAAM,QAAQ,OAAO,SAAS,OAAO,SAAS,OAAO,eAAe;AACpE,KAAI,OAAO,gBAAgB,CAAC,OAAO,aAAa,EAAE,OAAO,CAAC,EAAE;AAC1D,MAAI,MAAM,EAAE,YAAY,EAAE,iBAAiB,WAAW,kCAAkC;AACxF,SAAO;;AAET,KAAI,kBAAkB,CAAC,QAAQ;AAC7B,MAAI,MAAM,EAAE,YAAY,EAAE,iBAAiB,WAAW,6BAA6B;AACnF,SAAO;;AAGT,QAAO;EACL,IAAI;EACJ,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;EAC5B,GAAI,OAAO,UAAU,EAAE,SAAS,OAAO,SAAS,GAAG,EAAE;EACrD,GAAI,OAAO,UAAU,EAAE,SAAS,OAAO,SAAS,GAAG,EAAE;EACrD,GAAI,QAAQ,EAAE,OAAO,GAAG,EAAE;EAC1B,GAAI,OAAO,WAAW,EAAE,UAAU,OAAO,UAAU,GAAG,EAAE;EACxD,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,QAAQ,GAAG,EAAE;EACnD;;AAGH,SAAS,iCACP,SACU;AACV,QAAO,CAAC,GAAG,QAAQ,CAChB,QACE,WACC,OAAO,cAAc,SAAS,QAAQ,IAAI,OAAO,OAAO,oBAAoB,WAC/E,CACA,MAAM,MAAM,UAAU;EACrB,MAAM,YAAY,KAAK,cAAc,SAAS,OAAO;EACrD,MAAM,aAAa,MAAM,cAAc,SAAS,OAAO;AACvD,MAAI,cAAc,WAChB,QAAO,YAAY;AAErB,SAAO,KAAK,GAAG,cAAc,MAAM,GAAG;GACtC,CACD,KAAK,WAAW,OAAO,GAAG;;;AAI/B,SAAgB,wBACd,SACA,UACA,QACU;AACV,KAAI,UAAU,WAAW,SAAS,MAAM,SAAS,GAAG;EAClD,MAAM,QAAkB,CAAC,QAAQ;AACjC,OAAK,MAAM,cAAc,SAAS,MAChC,KAAI,eAAe,WAAW,CAAC,MAAM,SAAS,WAAW,CACvD,OAAM,KAAK,WAAW;AAG1B,SAAO;;CAGT,MAAM,aAAa,iCAAiC,iCAAiC,CAAC,CAAC,QACpF,eAAe,yBAAyB,YAAY,OAAO,KAAK,KAClE;CAED,MAAM,QAAkB,CAAC,QAAQ;AACjC,MAAK,MAAM,cAAc,WACvB,KAAI,eAAe,WAAW,CAAC,MAAM,SAAS,WAAW,CACvD,OAAM,KAAK,WAAW;AAG1B,QAAO;;AAGT,SAAS,uBAAuB,QAAkD;CAChF,MAAM,UAAU,oBAAoB;EAClC,YAAY;EACZ,kBAAkB,OAAO;EACzB,cAAc,OAAO;EACtB,CAAC;CACF,MAAM,QAAuC,EAAE;AAC/C,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,aAAa,MAAM,UAAU,MAAM;AACzC,MAAI,CAAC,WACH;EAEF,MAAM,WAAW,yBAAyB,YAAY,QAAQ,MAAM;AACpE,MAAI,SACF,OAAM,KAAK,SAAS;;AAGxB,QAAO;;;AAIT,SAAgB,wBAAwB,QAAkD;AACxF,KAAI,CAAC,OAAO,QACV,QAAO,EAAE;CAGX,MAAM,aAAa,uBAAuB,OAAO;AACjD,KAAI,WAAW,SAAS,EACtB,QAAO;CAGT,MAAM,QAAQ,wBAAwB,OAAO,UAAU,OAAO,UAAU,OAAO;CAC/E,MAAM,QAAuC,EAAE;AAC/C,MAAK,MAAM,cAAc,OAAO;EAC9B,MAAM,WAAW,yBAAyB,YAAY,OAAO;AAC7D,MAAI,SACF,OAAM,KAAK,SAAS;;AAGxB,QAAO"}
|
|
@@ -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;
|