@xopcai/xopc 0.0.241 → 0.0.243
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-CLnbVm9G.js → activity-store-CAah-_8h.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DuBewmbW.js → agent-avatar-dicebear-DDH4HgP7.js} +1 -1
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-BDCfJZhh.js +1 -0
- package/dist/gateway/static/root/assets/agents-B0ZWgf-U.js +1 -0
- package/dist/gateway/static/root/assets/{api-DjBZe303.js → api-B2tKxbcz.js} +1 -1
- package/dist/gateway/static/root/assets/app-management-settings-panel-Blfwszzn.js +1 -0
- package/dist/gateway/static/root/assets/{appearance-settings-B-iul0RG.js → appearance-settings-C3V7Na75.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-BiEks0-F.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-BOx0EJZs.js → archive-plugin-BmSx7wzY.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-Cfk1lwBo.js → attachment-load-BS-S4LJ9.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-BTSGvaeS.js → automations-page-C7Y3L30D.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-A3zPkPuo.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-ChZyZK45.js → binary-plugins-BM9B5kcm.js} +1 -1
- package/dist/gateway/static/root/assets/block-editor-BbnpkYP9.js +185 -0
- package/dist/gateway/static/root/assets/browser-settings-page-Dk4FQ7fY.js +8 -0
- package/dist/gateway/static/root/assets/{browser-workflow-inputs-jvfcgFG-.js → browser-workflow-inputs-CRnkgZHI.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-workflows-page-DrHW7XNi.js → browser-workflows-page-B9qGL-it.js} +2 -2
- package/dist/gateway/static/root/assets/{button-CSEbl4zT.js → button-CtTvuElV.js} +1 -1
- package/dist/gateway/static/root/assets/capabilities-settings-panel--tR_El9e.js +7 -0
- package/dist/gateway/static/root/assets/channels-settings-D347vl_J.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-CC9MFway.js → channels-status-swr-DuY4MGxe.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-DeeZrVbV.js → chat-terminal-dock-DABPvwBT.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-B-tvJkRJ.js +2 -0
- package/dist/gateway/static/root/assets/{cron-expression-CiqQOeQ3.js → cron-expression-D3bsKbNd.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-CGAMGPfU.js → date-picker-BsGM6Yty.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-CxWeUTsc.js → dependency-picker-Dj3g6YXZ.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-C3QmYO75.js → desktop-pet-C1QT0Sur.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-CAADkWYy.js → desktop-pet-settings-r6kAOnfY.js} +1 -1
- package/dist/gateway/static/root/assets/dist-CpzYSoz7.js +1 -0
- package/dist/gateway/static/root/assets/{dist-DaxPZj9e.js → dist-DBS3OB8K.js} +1 -1
- package/dist/gateway/static/root/assets/dist-DLmwHyAh.js +1 -0
- package/dist/gateway/static/root/assets/es2015-BvLh9GaE.js +41 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-FQXjDJ8P.js → extension-debug-page-DOkf7nh9.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-BJE1D5Ji.js → extension-page-Cq2IA4Ho.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-5Ik2tNry.js → extension-settings-page-DQn4qjDZ.js} +1 -1
- package/dist/gateway/static/root/assets/{files-DQjhZjr2.js → files-D6vf3S30.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-cTj71t2a.js → gateway-config-api-DM3poL22.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-Bxe_R-JH.js → gateway-config-swr-BC3pgb_P.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-KXBUjNcx.js → gateway-settings-DPgH1hnh.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-startup-retry-DJG-I0wW.js → gateway-startup-retry-uMcSfLru.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-C8cVzIcs.js → heartbeat-settings-Bk80-E3b.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-DKo5JiMR.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-Dc_ojQuj.js → image-generation-api-D1qlQA-A.js} +1 -1
- package/dist/gateway/static/root/assets/index-B0WJL9N1.js +71 -0
- package/dist/gateway/static/root/assets/index-Dbh5SXyV.css +2 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-pOKl7blR.js → keyboard-shortcuts-settings-BEwvd8ZU.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page--eJ5rmNj.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-Bi6wwKgm.js → local-apps-page-CgkY9E-r.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-C5wr3_pc.js → locale-store-CMZljTJx.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-Z49Td0J9.js +2 -0
- package/dist/gateway/static/root/assets/{management-settings-DGlVizWi.js → management-settings-Dv8NSJKt.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-split-CXP5zBx_.js → markdown-split-Cm0RBB-t.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-view-D6LxlDxr.js → markdown-view-PPCLt7fu.js} +3 -3
- package/dist/gateway/static/root/assets/{media-plugins-BsENGLr2.js → media-plugins-Bcr5sU1f.js} +1 -1
- package/dist/gateway/static/root/assets/messages-CTSgFlE4.js +3 -0
- package/dist/gateway/static/root/assets/mobile-pairing-wizard-kKDiXofY.js +1 -0
- package/dist/gateway/static/root/assets/{models-hub-cache-Y7GUjxHx.js → models-hub-cache-ZrbGRiGY.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-D2EqpDyo.js → notes-page-CgsaRaGi.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-C1DEE09k.js +4 -0
- package/dist/gateway/static/root/assets/{oauth-api-C5NVcsKE.js → oauth-api-4VeMbkTJ.js} +1 -1
- package/dist/gateway/static/root/assets/popover-select-C-Hj-nXR.js +1 -0
- package/dist/gateway/static/root/assets/{product-delivery-dUJgmBQZ.js → product-delivery-CV4pDOtG.js} +1 -1
- package/dist/gateway/static/root/assets/product-open-page-BU0HiZtv.js +1 -0
- package/dist/gateway/static/root/assets/project-detail-page-isRapnp5.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-zvsjg0mi.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-FXdFaoWM.js → registry-api-CWOtPFrE.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-C9TGZ9Hc.js +1 -0
- package/dist/gateway/static/root/assets/runtime-tools-settings-panel-2zm9edMA.js +5 -0
- package/dist/gateway/static/root/assets/{schema-form-BUzdHWx6.js → schema-form-54A5gzkN.js} +1 -1
- package/dist/gateway/static/root/assets/{schemas-zIBW3wiV.js → schemas-BHS7bo06.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-BP8RyiUn.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-D4EMxWAZ.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-C0OBLqpu.js → settings-page-DzDhoyxv.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-NpLQkFCh.js → setup-status-panel-uDicHU1w.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-BtbPYaYC.js → share-preview-page-lOmLCWFS.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-T4Yvouvv.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-DVb7nrXz.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-CYk8pN97.js → skills-page-CnyNH3To.js} +1 -1
- package/dist/gateway/static/root/assets/skills-page.utils-CgDtf2qs.js +1 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-DmCRSvtd.js → system-settings-panel-C8yWfwtR.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-Tzfjlbf3.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-DUko1ZnE.js → theme-store-uxUwr10D.js} +1 -1
- package/dist/gateway/static/root/assets/tunnel-api-Bvg679no.js +1 -0
- package/dist/gateway/static/root/assets/{use-autosave-W_P8Kiv4.js → use-autosave-D88VGoyG.js} +1 -1
- package/dist/gateway/static/root/assets/user-context-page-RijPtyfO.js +1 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-BZD0j1jc.js → voice-api-key-field-CBHLsMcc.js} +1 -1
- package/dist/gateway/static/root/assets/voice-config-api-MqyI6cgq.js +1 -0
- package/dist/gateway/static/root/assets/work-discovery-overlay-GGe2TQ98.js +1 -0
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-C0layUE9.js +3 -0
- package/dist/gateway/static/root/assets/workflows-page-DQXIzBAc.js +5 -0
- package/dist/gateway/static/root/index.html +21 -22
- package/dist/package.js +1 -1
- package/dist/packages/realtime-protocol/src/voice-audio.js +20 -0
- package/dist/packages/realtime-protocol/src/voice.js +208 -0
- package/dist/src/agent/agent-manager.js +1 -0
- package/dist/src/agent/embedded/run-for-session.d.ts +1 -0
- package/dist/src/agent/embedded/run-for-session.js +2 -1
- package/dist/src/agent/inbound/turn-dispatcher.d.ts +1 -0
- package/dist/src/agent/inbound/turn-dispatcher.js +2 -1
- package/dist/src/agent/prompt/voice-presentation.d.ts +2 -0
- package/dist/src/agent/prompt/voice-presentation.js +12 -0
- package/dist/src/agent/service/direct-turn-helpers.d.ts +1 -0
- package/dist/src/agent/service/direct-turn-helpers.js +1 -0
- package/dist/src/agent/service/process-direct-streaming.d.ts +1 -0
- package/dist/src/agent/service/process-direct-streaming.js +2 -1
- package/dist/src/agent/session/session-config-service.js +4 -2
- package/dist/src/agent/skills/installer.d.ts +1 -0
- package/dist/src/agent/skills/installer.js +9 -6
- package/dist/src/agent/tools/factory.d.ts +2 -0
- package/dist/src/agent/tools/factory.js +5 -1
- package/dist/src/automations/draft/automation-draft-service.js +1 -1
- package/dist/src/config/models-json.d.ts +8 -8
- package/dist/src/config/schema.d.ts +60 -0
- package/dist/src/config/schema.js +36 -4
- package/dist/src/extensions/install.js +1 -1
- package/dist/src/files/file-service.d.ts +1 -0
- package/dist/src/files/file-service.js +4 -0
- package/dist/src/gateway/chat-stream/thinking-delta-coalescer.js +8 -3
- package/dist/src/gateway/hono/lib/config-payload.d.ts +1 -11
- package/dist/src/gateway/hono/lib/config-payload.js +2 -2
- package/dist/src/gateway/hono/lib/safe-voice-config.d.ts +14 -0
- package/dist/src/gateway/hono/lib/safe-voice-config.js +29 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +2 -2
- package/dist/src/gateway/hono/routes/files.js +7 -0
- package/dist/src/gateway/hono/routes/lazy-fallback.js +18 -4
- package/dist/src/gateway/hono/routes/runtime-tools.js +7 -1
- package/dist/src/gateway/hono/routes/session-input-handler.js +14 -0
- package/dist/src/gateway/hono/routes/sessions.js +4 -1
- package/dist/src/gateway/hono/routes/voice.js +140 -3
- package/dist/src/gateway/server.js +1 -0
- package/dist/src/gateway/service/agent-runner.d.ts +1 -0
- package/dist/src/gateway/service/agent-runner.js +1 -6
- package/dist/src/gateway/service/run-gateway-agent.d.ts +1 -0
- package/dist/src/gateway/service/run-gateway-agent.js +19 -1
- package/dist/src/gateway/service.d.ts +2 -0
- package/dist/src/gateway/service.js +73 -0
- package/dist/src/media-understanding/types.d.ts +61 -0
- package/dist/src/runtime-tools/archive.js +17 -3
- package/dist/src/runtime-tools/command.d.ts +1 -0
- package/dist/src/runtime-tools/command.js +29 -8
- package/dist/src/runtime-tools/environment.d.ts +1 -0
- package/dist/src/runtime-tools/environment.js +11 -16
- package/dist/src/runtime-tools/install-lock.d.ts +1 -1
- package/dist/src/runtime-tools/install-lock.js +34 -21
- package/dist/src/runtime-tools/manager.d.ts +1 -1
- package/dist/src/runtime-tools/manager.js +99 -64
- package/dist/src/runtime-tools/manifest-store.d.ts +2 -0
- package/dist/src/runtime-tools/manifest-store.js +29 -8
- package/dist/src/runtime-tools/paths.d.ts +2 -1
- package/dist/src/runtime-tools/paths.js +19 -8
- package/dist/src/runtime-tools/probe.d.ts +2 -0
- package/dist/src/runtime-tools/probe.js +13 -1
- package/dist/src/runtime-tools/prune.js +21 -7
- package/dist/src/session/client-history.js +12 -0
- package/dist/src/session/manager.d.ts +1 -0
- package/dist/src/session/session-context-for-llm.js +8 -0
- package/dist/src/session/store.d.ts +1 -0
- package/dist/src/session/store.js +1 -0
- package/dist/src/session/stored-rows-to-file-entries.js +5 -1
- package/dist/src/session/voice-transcript.d.ts +9 -0
- package/dist/src/session/voice-transcript.js +45 -0
- package/dist/src/storage/sqlite/config-repository.js +9 -0
- package/dist/src/storage/sqlite/remote-context-repository.d.ts +3 -0
- package/dist/src/storage/sqlite/remote-context-repository.js +40 -0
- package/dist/src/storage/sqlite/session-repository.js +13 -0
- package/dist/src/storage/sqlite/user-context-repository.d.ts +3 -1
- package/dist/src/storage/sqlite/user-context-repository.js +50 -4
- package/dist/src/tui/tui.js +2 -1
- package/dist/src/user-context/automatic-context.d.ts +17 -0
- package/dist/src/user-context/automatic-context.js +85 -0
- package/dist/src/user-context/changes.d.ts +7 -0
- package/dist/src/user-context/changes.js +28 -0
- package/dist/src/user-context/planner.js +1 -24
- package/dist/src/user-context/retriever.js +10 -11
- package/dist/src/user-context/selection-policy.d.ts +20 -0
- package/dist/src/user-context/selection-policy.js +33 -0
- package/dist/src/user-context/sources/index.js +2 -2
- package/dist/src/user-context/sources/repository.d.ts +2 -1
- package/dist/src/user-context/sources/repository.js +26 -5
- package/dist/src/voice/dashscope/streaming-stt-session.d.ts +2 -0
- package/dist/src/voice/dashscope/streaming-stt-session.js +249 -0
- package/dist/src/voice/dashscope/streaming-tts-stream.d.ts +12 -0
- package/dist/src/voice/dashscope/streaming-tts-stream.js +153 -0
- package/dist/src/voice/realtime/agentEngine.d.ts +10 -0
- package/dist/src/voice/realtime/agentEngine.js +403 -0
- package/dist/src/voice/realtime/audio-playback-window.d.ts +9 -0
- package/dist/src/voice/realtime/audio-playback-window.js +49 -0
- package/dist/src/voice/realtime/conversation-context.d.ts +11 -0
- package/dist/src/voice/realtime/conversation-context.js +76 -0
- package/dist/src/voice/realtime/engine.d.ts +13 -0
- package/dist/src/voice/realtime/engine.js +1 -0
- package/dist/src/voice/realtime/memory-context.d.ts +17 -0
- package/dist/src/voice/realtime/memory-context.js +100 -0
- package/dist/src/voice/realtime/omniEngine.d.ts +18 -0
- package/dist/src/voice/realtime/omniEngine.js +443 -0
- package/dist/src/voice/realtime/omniRoute.d.ts +10 -0
- package/dist/src/voice/realtime/omniRoute.js +31 -0
- package/dist/src/voice/realtime/runtime.d.ts +99 -0
- package/dist/src/voice/realtime/runtime.js +622 -0
- package/dist/src/voice/realtime/speakable-segmenter.d.ts +10 -0
- package/dist/src/voice/realtime/speakable-segmenter.js +87 -0
- package/dist/src/voice/stt/list-providers.js +2 -1
- package/dist/src/voice/stt/providers/alibaba-transcription.js +13 -1
- package/dist/src/voice/stt/providers/xopc-cloud-transcription.js +23 -0
- package/dist/src/voice/tts/factory.js +1 -0
- package/dist/src/voice/tts/openai-compatible-speech.js +3 -1
- package/dist/src/voice/tts/providers/alibaba-speech.d.ts +1 -5
- package/dist/src/voice/tts/providers/alibaba-speech.js +25 -8
- package/dist/src/voice/tts/providers/edge-speech.js +21 -22
- package/dist/src/voice/tts/providers/minimax-speech.js +6 -3
- package/dist/src/voice/tts/providers/xopc-cloud-speech.js +26 -1
- package/dist/src/voice/tts/speak-core.d.ts +6 -2
- package/dist/src/voice/tts/speak-core.js +46 -34
- package/dist/src/voice/tts/speech-provider-types.d.ts +1 -0
- package/package.json +6 -6
- package/dist/gateway/static/root/assets/Combination-14nmIgxH.js +0 -41
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-DbdgX6AA.js +0 -1
- package/dist/gateway/static/root/assets/agents-BX3uRVm4.js +0 -1
- package/dist/gateway/static/root/assets/app-chrome-BeB4KgOf.js +0 -1
- package/dist/gateway/static/root/assets/app-management-settings-panel-D8evZyFQ.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-CoZd_cZu.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace-C8RRKT-a.js +0 -6
- package/dist/gateway/static/root/assets/block-editor-DCH9b2Ht.js +0 -184
- package/dist/gateway/static/root/assets/browser-settings-page-D1GPwIfx.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-CrXB1Q-l.js +0 -7
- package/dist/gateway/static/root/assets/channels-settings-DWMSYaNZ.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-DXTl1fNB.js +0 -2
- package/dist/gateway/static/root/assets/dist-BPoKqzeH.js +0 -1
- package/dist/gateway/static/root/assets/dist-DqPZM2MH.js +0 -1
- package/dist/gateway/static/root/assets/home-page-NdWtBda5.js +0 -1
- package/dist/gateway/static/root/assets/index-7i2P3312.js +0 -70
- package/dist/gateway/static/root/assets/index-DTo3IcbW.css +0 -2
- package/dist/gateway/static/root/assets/local-app-workbench-page-C_j_od5f.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-D6PDZEVP.js +0 -2
- package/dist/gateway/static/root/assets/messages-_tYhRAHD.js +0 -3
- package/dist/gateway/static/root/assets/mobile-pairing-wizard-DcEz7qcX.js +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-Bu0a83Oj.js +0 -4
- package/dist/gateway/static/root/assets/popover-select-DrILF6mR.js +0 -1
- package/dist/gateway/static/root/assets/product-open-page-DMhf1rdz.js +0 -1
- package/dist/gateway/static/root/assets/project-detail-page-DDHi0Y4Q.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-CVMFKpmu.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-B74aErBY.js +0 -1
- package/dist/gateway/static/root/assets/runtime-tools-settings-panel-Bv-akndY.js +0 -5
- package/dist/gateway/static/root/assets/sessions-page-D2wmGNZE.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-CYCCUMEt.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-rf8f1OGq.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-BkvclZ_o.js +0 -1
- package/dist/gateway/static/root/assets/skills-page.utils-DGjnz-NG.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-6a2RdDl2.js +0 -1
- package/dist/gateway/static/root/assets/tunnel-api-Chfiba_x.js +0 -1
- package/dist/gateway/static/root/assets/user-context-page-SeOHP6XO.js +0 -1
- package/dist/gateway/static/root/assets/voice-config-api-BO6A8V3Z.js +0 -1
- package/dist/gateway/static/root/assets/work-discovery-overlay-BfwEoOGL.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-X7Z5_lRX.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-BklMzkg8.js +0 -5
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { stripMarkdown } from "../tts/preprocess.js";
|
|
2
|
+
//#region src/voice/realtime/speakable-segmenter.ts
|
|
3
|
+
const SENTENCE_END = /[。!?!?;;::\n]/u;
|
|
4
|
+
/** Streams spoken prose, keeping code and link targets out of synthesis. */
|
|
5
|
+
var SpeakableSegmenter = class {
|
|
6
|
+
pending = "";
|
|
7
|
+
fence = null;
|
|
8
|
+
constructor(maxCharacters = 120) {
|
|
9
|
+
this.maxCharacters = maxCharacters;
|
|
10
|
+
}
|
|
11
|
+
push(delta) {
|
|
12
|
+
this.pending += delta;
|
|
13
|
+
const phrases = [];
|
|
14
|
+
while (this.pending) {
|
|
15
|
+
if (this.fence) {
|
|
16
|
+
const end = this.pending.indexOf(this.fence);
|
|
17
|
+
if (end < 0) {
|
|
18
|
+
this.pending = this.pending.slice(-2);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
this.pending = this.pending.slice(end + this.fence.length);
|
|
22
|
+
this.fence = null;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const openingFence = this.pending.match(/^\s*(```|~~~)/);
|
|
26
|
+
if (openingFence) {
|
|
27
|
+
this.pending = this.pending.trimStart();
|
|
28
|
+
const newline = this.pending.indexOf("\n");
|
|
29
|
+
if (newline < 0) break;
|
|
30
|
+
this.fence = openingFence[1];
|
|
31
|
+
this.pending = this.pending.slice(newline + 1);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (/^\s*\|/.test(this.pending)) {
|
|
35
|
+
const newline = this.pending.indexOf("\n", this.pending.search(/\|/));
|
|
36
|
+
if (newline < 0) break;
|
|
37
|
+
this.pending = this.pending.slice(newline + 1);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
let boundary = -1;
|
|
41
|
+
let brackets = 0;
|
|
42
|
+
let parentheses = 0;
|
|
43
|
+
let inlineCode = false;
|
|
44
|
+
for (let index = 0; index < this.pending.length; index += 1) {
|
|
45
|
+
if (!parentheses && /^https?:\/\//.test(this.pending.slice(index))) {
|
|
46
|
+
const end = this.pending.slice(index).search(/\s/);
|
|
47
|
+
if (end < 0) break;
|
|
48
|
+
index += end - 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const char = this.pending[index];
|
|
52
|
+
if (char === "`") inlineCode = !inlineCode;
|
|
53
|
+
if (char === "[") brackets += 1;
|
|
54
|
+
if (char === "]") brackets = Math.max(0, brackets - 1);
|
|
55
|
+
if (char === "(") parentheses += 1;
|
|
56
|
+
if (char === ")") parentheses = Math.max(0, parentheses - 1);
|
|
57
|
+
if (inlineCode || brackets || parentheses || char === "]" && this.pending[index + 1] === "(") continue;
|
|
58
|
+
if (SENTENCE_END.test(char)) {
|
|
59
|
+
boundary = index + 1;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
if (index + 1 >= this.maxCharacters) {
|
|
63
|
+
const window = this.pending.slice(0, index + 1);
|
|
64
|
+
const whitespace = Math.max(window.lastIndexOf(" "), window.lastIndexOf(","), window.lastIndexOf(","));
|
|
65
|
+
boundary = whitespace > this.maxCharacters / 2 ? whitespace + 1 : index + 1;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (boundary < 0) break;
|
|
70
|
+
const phrase = this.toSpeech(this.pending.slice(0, boundary));
|
|
71
|
+
this.pending = this.pending.slice(boundary);
|
|
72
|
+
if (phrase) phrases.push(phrase);
|
|
73
|
+
}
|
|
74
|
+
return phrases;
|
|
75
|
+
}
|
|
76
|
+
flush() {
|
|
77
|
+
const phrase = this.fence || /^\s*(```|~~~)/.test(this.pending) ? "" : this.toSpeech(this.pending);
|
|
78
|
+
this.pending = "";
|
|
79
|
+
this.fence = null;
|
|
80
|
+
return phrase ? [phrase] : [];
|
|
81
|
+
}
|
|
82
|
+
toSpeech(text) {
|
|
83
|
+
return stripMarkdown(text).replace(/https?:\/\/\S+/g, "").replace(/[*_~]/g, "").replace(/^\s*\|.*\|\s*$/gm, "").trim();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
export { SpeakableSegmenter };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { listMediaUnderstandingProviders } from "../../media-understanding/registry.js";
|
|
1
|
+
import { getMediaUnderstandingProvider, listMediaUnderstandingProviders } from "../../media-understanding/registry.js";
|
|
2
2
|
import { resolveSTTProviderConfig } from "./factory.js";
|
|
3
3
|
import { mergeSttConfigFromAppConfig } from "./config.js";
|
|
4
4
|
import { getVoiceProviderMetadata } from "../metadata/registry.js";
|
|
@@ -35,6 +35,7 @@ function fallbackMetadata(providerId, aliases) {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
function isSttProviderConfigured(providerId, config) {
|
|
38
|
+
if (!getMediaUnderstandingProvider(providerId)?.transcribeAudio) return false;
|
|
38
39
|
return resolveSTTProviderConfig(providerId, config) !== null;
|
|
39
40
|
}
|
|
40
41
|
/** List registered audio STT providers with configured state and UI metadata. */
|
|
@@ -3,10 +3,12 @@ import { init_logger } from "../../../utils/logger.js";
|
|
|
3
3
|
import { postJsonRequest } from "../../../media-shared/http/provider-http.js";
|
|
4
4
|
import "../../../media-shared/http/index.js";
|
|
5
5
|
import { registerMediaUnderstandingProvider } from "../../../media-understanding/registry.js";
|
|
6
|
+
import { openDashScopeStreamingStt } from "../../dashscope/streaming-stt-session.js";
|
|
6
7
|
//#region src/voice/stt/providers/alibaba-transcription.ts
|
|
7
8
|
init_logger();
|
|
8
9
|
const log = createLogger("STT:Alibaba");
|
|
9
10
|
const DEFAULT_MODEL = "qwen-audio-3.0-asr-flash";
|
|
11
|
+
const DEFAULT_STREAMING_MODEL = "qwen-audio-3.0-asr-flash-streaming";
|
|
10
12
|
const DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation";
|
|
11
13
|
function audioFormat(request) {
|
|
12
14
|
const extension = request.fileName.split(".").pop()?.toLowerCase();
|
|
@@ -85,7 +87,17 @@ const alibabaTranscriptionProvider = {
|
|
|
85
87
|
envKey: "DASHSCOPE_API_KEY",
|
|
86
88
|
defaultModels: { audio: DEFAULT_MODEL },
|
|
87
89
|
autoPriority: { audio: 10 },
|
|
88
|
-
transcribeAudio
|
|
90
|
+
transcribeAudio,
|
|
91
|
+
streamingAudio: {
|
|
92
|
+
inputSampleRates: [16e3],
|
|
93
|
+
turnDetection: ["server_vad"],
|
|
94
|
+
defaultModel: DEFAULT_STREAMING_MODEL,
|
|
95
|
+
models: [DEFAULT_STREAMING_MODEL]
|
|
96
|
+
},
|
|
97
|
+
openAudioStream: (request) => openDashScopeStreamingStt({
|
|
98
|
+
...request,
|
|
99
|
+
model: request.model || DEFAULT_STREAMING_MODEL
|
|
100
|
+
})
|
|
89
101
|
};
|
|
90
102
|
registerMediaUnderstandingProvider(alibabaTranscriptionProvider);
|
|
91
103
|
//#endregion
|
|
@@ -3,10 +3,12 @@ import { init_xopc_cloud_config, resolveXopcModelRouterUrl } from "../../../prov
|
|
|
3
3
|
import { getProviderAuthService, init_provider_auth_service } from "../../../providers/provider-auth-service.js";
|
|
4
4
|
import { compareCatalogModels } from "../../../providers/model-catalog-ranking.js";
|
|
5
5
|
import { registerMediaUnderstandingProvider } from "../../../media-understanding/registry.js";
|
|
6
|
+
import { openDashScopeStreamingStt } from "../../dashscope/streaming-stt-session.js";
|
|
6
7
|
//#region src/voice/stt/providers/xopc-cloud-transcription.ts
|
|
7
8
|
init_model_catalog_store();
|
|
8
9
|
init_provider_auth_service();
|
|
9
10
|
init_xopc_cloud_config();
|
|
11
|
+
const DEFAULT_STREAMING_MODEL = "qwen-audio-3.0-asr-flash-streaming";
|
|
10
12
|
function defaultModel() {
|
|
11
13
|
const source = getModelCatalogStore().getSource("xopc-cloud");
|
|
12
14
|
return source?.models.filter((model) => model.availability === "available" && model.kind === "stt").sort((left, right) => compareCatalogModels(left, right, source.recommended?.stt))[0]?.id;
|
|
@@ -44,6 +46,27 @@ const xopcCloudTranscriptionProvider = {
|
|
|
44
46
|
model,
|
|
45
47
|
...request.language ? { language: request.language } : {}
|
|
46
48
|
};
|
|
49
|
+
},
|
|
50
|
+
streamingAudio: {
|
|
51
|
+
inputSampleRates: [16e3],
|
|
52
|
+
turnDetection: ["server_vad"],
|
|
53
|
+
defaultModel: DEFAULT_STREAMING_MODEL,
|
|
54
|
+
models: [DEFAULT_STREAMING_MODEL]
|
|
55
|
+
},
|
|
56
|
+
openAudioStream: async (request) => {
|
|
57
|
+
const accessToken = await getProviderAuthService().resolveApiKey("xopc-cloud", request.signal);
|
|
58
|
+
if (!accessToken) throw new Error("XOPC Cloud authorization is unavailable");
|
|
59
|
+
const source = getModelCatalogStore().getSource("xopc-cloud");
|
|
60
|
+
const baseUrl = (request.baseUrl ?? source?.baseUrl ?? resolveXopcModelRouterUrl()).replace(/\/+$/, "");
|
|
61
|
+
const relayUrl = new URL(`${baseUrl}/audio/transcriptions/realtime`);
|
|
62
|
+
relayUrl.protocol = relayUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
63
|
+
relayUrl.searchParams.set("model", request.model || DEFAULT_STREAMING_MODEL);
|
|
64
|
+
return openDashScopeStreamingStt({
|
|
65
|
+
...request,
|
|
66
|
+
apiKey: accessToken,
|
|
67
|
+
baseUrl: relayUrl.toString(),
|
|
68
|
+
model: request.model || DEFAULT_STREAMING_MODEL
|
|
69
|
+
});
|
|
47
70
|
}
|
|
48
71
|
};
|
|
49
72
|
registerMediaUnderstandingProvider(xopcCloudTranscriptionProvider);
|
|
@@ -100,6 +100,7 @@ function isTTSAvailable(config) {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
function isProviderConfigured(provider, config) {
|
|
103
|
+
if (!getSpeechProvider(provider)) return false;
|
|
103
104
|
return resolveSpeechProvider(provider, config) !== null;
|
|
104
105
|
}
|
|
105
106
|
function getAvailableProviders(config) {
|
|
@@ -187,7 +187,8 @@ function createOpenAiCompatibleSpeechProvider(options) {
|
|
|
187
187
|
response_format: responseFormat,
|
|
188
188
|
...speed === void 0 ? {} : { speed },
|
|
189
189
|
...buildExtraJsonBodyFields(config, options.extraJsonBodyFields)
|
|
190
|
-
}
|
|
190
|
+
},
|
|
191
|
+
signal: req.signal
|
|
191
192
|
});
|
|
192
193
|
return {
|
|
193
194
|
audioBuffer: Buffer.from(await response.arrayBuffer()),
|
|
@@ -216,6 +217,7 @@ function createOpenAiCompatibleSpeechProvider(options) {
|
|
|
216
217
|
const response = await fetchWithTimeoutGuarded(`${baseUrl}/audio/speech`, {
|
|
217
218
|
timeoutMs: req.timeoutMs,
|
|
218
219
|
label: options.apiErrorLabel ?? `${options.label} TTS streaming`,
|
|
220
|
+
signal: req.signal,
|
|
219
221
|
init: {
|
|
220
222
|
method: "POST",
|
|
221
223
|
headers: {
|
|
@@ -6,11 +6,6 @@
|
|
|
6
6
|
* doesn't fit the OpenAI-compatible factory.
|
|
7
7
|
*
|
|
8
8
|
* Implementation notes (per docs/voice-rearchitecture.md §8.4.1):
|
|
9
|
-
* - `synthesizeStream` is intentionally not implemented. Native qwen-tts
|
|
10
|
-
* streaming uses a WebSocket protocol
|
|
11
|
-
* (wss://dashscope.aliyuncs.com/api-ws/v1/inference); speak-core's stream
|
|
12
|
-
* fallback wraps `synthesize` output as a single-chunk ReadableStream so
|
|
13
|
-
* callers see the same shape.
|
|
14
9
|
* - `maxTextLength = 512` (DashScope qwen-tts hard limit). Enforced upstream
|
|
15
10
|
* via `truncateAtSentenceBoundary` before this provider sees the text.
|
|
16
11
|
* - The audio URL returned by DashScope is hosted on Alibaba's CDN (variable
|
|
@@ -24,4 +19,5 @@
|
|
|
24
19
|
* as a hardening pass.
|
|
25
20
|
*/
|
|
26
21
|
import type { SpeechProviderPlugin } from '../speech-provider-types.js';
|
|
22
|
+
export declare const ALIBABA_REALTIME_TTS_MODEL = "qwen3-tts-flash-realtime";
|
|
27
23
|
export declare const alibabaSpeechProvider: SpeechProviderPlugin;
|
|
@@ -4,6 +4,7 @@ import { ProviderHttpError } from "../../../media-shared/http/provider-http-erro
|
|
|
4
4
|
import { fetchWithTimeoutGuarded, postJsonRequest } from "../../../media-shared/http/provider-http.js";
|
|
5
5
|
import "../../../media-shared/http/index.js";
|
|
6
6
|
import { registerSpeechProvider } from "../speech-registry.js";
|
|
7
|
+
import { openDashScopeStreamingTts } from "../../dashscope/streaming-tts-stream.js";
|
|
7
8
|
//#region src/voice/tts/providers/alibaba-speech.ts
|
|
8
9
|
/**
|
|
9
10
|
* Alibaba DashScope TTS provider (qwen-tts model).
|
|
@@ -13,11 +14,6 @@ import { registerSpeechProvider } from "../speech-registry.js";
|
|
|
13
14
|
* doesn't fit the OpenAI-compatible factory.
|
|
14
15
|
*
|
|
15
16
|
* Implementation notes (per docs/voice-rearchitecture.md §8.4.1):
|
|
16
|
-
* - `synthesizeStream` is intentionally not implemented. Native qwen-tts
|
|
17
|
-
* streaming uses a WebSocket protocol
|
|
18
|
-
* (wss://dashscope.aliyuncs.com/api-ws/v1/inference); speak-core's stream
|
|
19
|
-
* fallback wraps `synthesize` output as a single-chunk ReadableStream so
|
|
20
|
-
* callers see the same shape.
|
|
21
17
|
* - `maxTextLength = 512` (DashScope qwen-tts hard limit). Enforced upstream
|
|
22
18
|
* via `truncateAtSentenceBoundary` before this provider sees the text.
|
|
23
19
|
* - The audio URL returned by DashScope is hosted on Alibaba's CDN (variable
|
|
@@ -34,6 +30,7 @@ init_logger();
|
|
|
34
30
|
const log = createLogger("SpeechProvider:Alibaba");
|
|
35
31
|
const DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation";
|
|
36
32
|
const DEFAULT_MODEL = "qwen-tts";
|
|
33
|
+
const ALIBABA_REALTIME_TTS_MODEL = "qwen3-tts-flash-realtime";
|
|
37
34
|
const DEFAULT_VOICE = "longxiaochun";
|
|
38
35
|
const ENV_KEY = "DASHSCOPE_API_KEY";
|
|
39
36
|
const MAX_TEXT_LENGTH = 512;
|
|
@@ -135,7 +132,8 @@ const alibabaSpeechProvider = {
|
|
|
135
132
|
model,
|
|
136
133
|
input: { text: req.text },
|
|
137
134
|
parameters: { voice }
|
|
138
|
-
}
|
|
135
|
+
},
|
|
136
|
+
signal: req.signal
|
|
139
137
|
})).json();
|
|
140
138
|
if (data.output?.finish_reason === "null" && !data.output?.audio?.url) throw new Error(`Alibaba TTS API error: ${JSON.stringify(data)}`);
|
|
141
139
|
let audioBuffer;
|
|
@@ -144,7 +142,8 @@ const alibabaSpeechProvider = {
|
|
|
144
142
|
const audioResponse = await fetchWithTimeoutGuarded(audioUrl, {
|
|
145
143
|
timeoutMs: req.timeoutMs,
|
|
146
144
|
label: "Alibaba TTS audio download",
|
|
147
|
-
allowPrivateNetwork: false
|
|
145
|
+
allowPrivateNetwork: false,
|
|
146
|
+
signal: req.signal
|
|
148
147
|
});
|
|
149
148
|
if (!audioResponse.ok) throw new ProviderHttpError({
|
|
150
149
|
label: "Alibaba TTS audio download",
|
|
@@ -166,8 +165,26 @@ const alibabaSpeechProvider = {
|
|
|
166
165
|
fileExtension: "wav",
|
|
167
166
|
voiceCompatible: false
|
|
168
167
|
};
|
|
168
|
+
},
|
|
169
|
+
synthesizeStream: async (req) => {
|
|
170
|
+
const config = readProviderConfig(req.providerConfig);
|
|
171
|
+
const apiKey = resolveApiKey(config);
|
|
172
|
+
if (!apiKey) throw new Error(`Alibaba TTS API key missing (set ${ENV_KEY} or messages.tts.providers.alibaba.apiKey)`);
|
|
173
|
+
const overrides = req.providerOverrides ?? {};
|
|
174
|
+
const voice = trimToUndefined(overrides.voice ?? overrides.voiceId) ?? config.voice;
|
|
175
|
+
const instructions = trimToUndefined(overrides.instructions);
|
|
176
|
+
return openDashScopeStreamingTts({
|
|
177
|
+
apiKey,
|
|
178
|
+
baseUrl: config.baseUrl,
|
|
179
|
+
model: ALIBABA_REALTIME_TTS_MODEL,
|
|
180
|
+
voice,
|
|
181
|
+
text: req.text,
|
|
182
|
+
...instructions ? { instructions } : {},
|
|
183
|
+
signal: req.signal,
|
|
184
|
+
timeoutMs: req.timeoutMs
|
|
185
|
+
});
|
|
169
186
|
}
|
|
170
187
|
};
|
|
171
188
|
registerSpeechProvider(alibabaSpeechProvider);
|
|
172
189
|
//#endregion
|
|
173
|
-
export { alibabaSpeechProvider };
|
|
190
|
+
export { ALIBABA_REALTIME_TTS_MODEL, alibabaSpeechProvider };
|
|
@@ -94,16 +94,31 @@ function parseDirectiveTokenInternal(ctx) {
|
|
|
94
94
|
default: return { handled: false };
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
async function waitForNonEmptyFile(filePath, timeoutMs) {
|
|
97
|
+
async function waitForNonEmptyFile(filePath, timeoutMs, signal) {
|
|
98
98
|
const deadline = Date.now() + Math.min(Math.max(timeoutMs, 1e3), 5e3);
|
|
99
99
|
while (Date.now() < deadline) {
|
|
100
|
+
if (signal.aborted) throw signal.reason;
|
|
100
101
|
try {
|
|
101
102
|
if (statSync(filePath).size > 0) return;
|
|
102
103
|
} catch {}
|
|
103
104
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
|
-
async function
|
|
107
|
+
async function withAbort(promise, signal) {
|
|
108
|
+
if (signal.aborted) throw signal.reason;
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const onAbort = () => reject(signal.reason);
|
|
111
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
112
|
+
promise.then((value) => {
|
|
113
|
+
signal.removeEventListener("abort", onAbort);
|
|
114
|
+
resolve(value);
|
|
115
|
+
}, (error) => {
|
|
116
|
+
signal.removeEventListener("abort", onAbort);
|
|
117
|
+
reject(error);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async function synthesizeToBuffer(text, config, voiceOverride, timeoutMs, signal) {
|
|
107
122
|
const { EdgeTTS } = await import("node-edge-tts");
|
|
108
123
|
const voice = voiceOverride ?? config.voice;
|
|
109
124
|
const tempDir = mkdtempSync(path.join(tmpdir(), "tts-edge-"));
|
|
@@ -115,7 +130,7 @@ async function synthesizeToBuffer(text, config, voiceOverride, timeoutMs) {
|
|
|
115
130
|
outputFormat: config.outputFormat,
|
|
116
131
|
textLength: text.length
|
|
117
132
|
}, "Calling Edge TTS");
|
|
118
|
-
await new EdgeTTS({
|
|
133
|
+
await withAbort(new EdgeTTS({
|
|
119
134
|
voice,
|
|
120
135
|
lang: config.lang,
|
|
121
136
|
outputFormat: config.outputFormat,
|
|
@@ -124,8 +139,8 @@ async function synthesizeToBuffer(text, config, voiceOverride, timeoutMs) {
|
|
|
124
139
|
pitch: config.pitch,
|
|
125
140
|
volume: config.volume,
|
|
126
141
|
timeout: timeoutMs
|
|
127
|
-
}).ttsPromise(text, outputPath);
|
|
128
|
-
await waitForNonEmptyFile(outputPath, timeoutMs);
|
|
142
|
+
}).ttsPromise(text, outputPath), signal);
|
|
143
|
+
await waitForNonEmptyFile(outputPath, timeoutMs, signal);
|
|
129
144
|
const buffer = readFileSync(outputPath);
|
|
130
145
|
if (buffer.length === 0) throw new Error(`Edge TTS produced an empty ${format} file for voice ${voice}`);
|
|
131
146
|
return {
|
|
@@ -163,29 +178,13 @@ const edgeSpeechProvider = {
|
|
|
163
178
|
if (!config.enabled) throw new Error("Edge TTS is disabled in config (messages.tts.providers.edge.enabled = false)");
|
|
164
179
|
const overrides = req.providerOverrides ?? {};
|
|
165
180
|
const voiceOverride = trimToUndefined(overrides.voice ?? overrides.voiceId);
|
|
166
|
-
const { buffer, outputFormat, ext } = await synthesizeToBuffer(req.text, config, voiceOverride, req.timeoutMs);
|
|
181
|
+
const { buffer, outputFormat, ext } = await synthesizeToBuffer(req.text, config, voiceOverride, req.timeoutMs, req.signal);
|
|
167
182
|
return {
|
|
168
183
|
audioBuffer: buffer,
|
|
169
184
|
outputFormat,
|
|
170
185
|
fileExtension: ext,
|
|
171
186
|
voiceCompatible: outputFormat === "opus" || outputFormat === "ogg"
|
|
172
187
|
};
|
|
173
|
-
},
|
|
174
|
-
synthesizeStream: async (req) => {
|
|
175
|
-
const config = readProviderConfig(req.providerConfig);
|
|
176
|
-
if (!config.enabled) throw new Error("Edge TTS is disabled in config (messages.tts.providers.edge.enabled = false)");
|
|
177
|
-
const overrides = req.providerOverrides ?? {};
|
|
178
|
-
const voiceOverride = trimToUndefined(overrides.voice ?? overrides.voiceId);
|
|
179
|
-
const { buffer, outputFormat, ext } = await synthesizeToBuffer(req.text, config, voiceOverride, req.timeoutMs);
|
|
180
|
-
return {
|
|
181
|
-
audioStream: new ReadableStream({ start(controller) {
|
|
182
|
-
controller.enqueue(new Uint8Array(buffer));
|
|
183
|
-
controller.close();
|
|
184
|
-
} }),
|
|
185
|
-
outputFormat,
|
|
186
|
-
fileExtension: ext,
|
|
187
|
-
voiceCompatible: outputFormat === "opus" || outputFormat === "ogg"
|
|
188
|
-
};
|
|
189
188
|
}
|
|
190
189
|
};
|
|
191
190
|
registerSpeechProvider(edgeSpeechProvider);
|
|
@@ -268,19 +268,22 @@ const minimaxSpeechProvider = {
|
|
|
268
268
|
model,
|
|
269
269
|
voiceId: voice,
|
|
270
270
|
text: req.text,
|
|
271
|
-
timeoutMs: callTimeoutMs
|
|
271
|
+
timeoutMs: callTimeoutMs,
|
|
272
|
+
signal: req.signal
|
|
272
273
|
});
|
|
273
274
|
const fileId = await pollTask({
|
|
274
275
|
baseUrl: config.baseUrl,
|
|
275
276
|
apiKey,
|
|
276
277
|
taskId,
|
|
277
|
-
timeoutMs: callTimeoutMs
|
|
278
|
+
timeoutMs: callTimeoutMs,
|
|
279
|
+
signal: req.signal
|
|
278
280
|
});
|
|
279
281
|
const audioBuffer = await downloadAudio({
|
|
280
282
|
baseUrl: config.baseUrl,
|
|
281
283
|
apiKey,
|
|
282
284
|
fileId,
|
|
283
|
-
timeoutMs: callTimeoutMs
|
|
285
|
+
timeoutMs: callTimeoutMs,
|
|
286
|
+
signal: req.signal
|
|
284
287
|
});
|
|
285
288
|
log.debug({
|
|
286
289
|
size: audioBuffer.length,
|
|
@@ -3,6 +3,7 @@ import { init_xopc_cloud_config, resolveXopcModelRouterUrl } from "../../../prov
|
|
|
3
3
|
import { getProviderAuthService, init_provider_auth_service } from "../../../providers/provider-auth-service.js";
|
|
4
4
|
import { compareCatalogModels } from "../../../providers/model-catalog-ranking.js";
|
|
5
5
|
import { registerSpeechProvider } from "../speech-registry.js";
|
|
6
|
+
import { openDashScopeStreamingTts } from "../../dashscope/streaming-tts-stream.js";
|
|
6
7
|
//#region src/voice/tts/providers/xopc-cloud-speech.ts
|
|
7
8
|
init_model_catalog_store();
|
|
8
9
|
init_provider_auth_service();
|
|
@@ -92,7 +93,7 @@ const xopcCloudSpeechProvider = {
|
|
|
92
93
|
...speed !== void 0 && catalogModel.tts?.speed ? { speed } : {},
|
|
93
94
|
...instructions && catalogModel.tts?.instructions ? { instructions } : {}
|
|
94
95
|
}),
|
|
95
|
-
signal:
|
|
96
|
+
signal: request.signal,
|
|
96
97
|
redirect: "error"
|
|
97
98
|
});
|
|
98
99
|
if (!response.ok) {
|
|
@@ -108,6 +109,30 @@ const xopcCloudSpeechProvider = {
|
|
|
108
109
|
fileExtension,
|
|
109
110
|
voiceCompatible: fileExtension === "opus" || fileExtension === "ogg"
|
|
110
111
|
};
|
|
112
|
+
},
|
|
113
|
+
synthesizeStream: async (request) => {
|
|
114
|
+
const config = readConfig(request.providerConfig);
|
|
115
|
+
const model = typeof request.providerOverrides?.model === "string" ? request.providerOverrides.model : config.model;
|
|
116
|
+
const voice = typeof request.providerOverrides?.voice === "string" ? request.providerOverrides.voice : config.voice;
|
|
117
|
+
if (!model || !voice) throw new Error("XOPC Cloud realtime TTS model or voice is unavailable");
|
|
118
|
+
const catalogModel = availableModels().find((entry) => entry.id === model);
|
|
119
|
+
if (!catalogModel?.tts?.streaming || !catalogModel.tts.outputFormats.includes("pcm")) throw new Error(`XOPC Cloud TTS model does not support PCM streaming: ${model}`);
|
|
120
|
+
const token = await getProviderAuthService().resolveApiKey("xopc-cloud", request.signal);
|
|
121
|
+
if (!token) throw new Error("XOPC Cloud authorization is unavailable");
|
|
122
|
+
const relayUrl = new URL(`${config.baseUrl.replace(/\/+$/, "")}/audio/speech/realtime`);
|
|
123
|
+
relayUrl.protocol = relayUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
124
|
+
relayUrl.searchParams.set("model", model);
|
|
125
|
+
const instructions = typeof request.providerOverrides?.instructions === "string" ? request.providerOverrides.instructions.trim() : config.instructions;
|
|
126
|
+
return openDashScopeStreamingTts({
|
|
127
|
+
apiKey: token,
|
|
128
|
+
baseUrl: relayUrl.toString(),
|
|
129
|
+
model,
|
|
130
|
+
voice,
|
|
131
|
+
text: request.text,
|
|
132
|
+
...instructions ? { instructions } : {},
|
|
133
|
+
signal: request.signal,
|
|
134
|
+
timeoutMs: request.timeoutMs
|
|
135
|
+
});
|
|
111
136
|
}
|
|
112
137
|
};
|
|
113
138
|
registerSpeechProvider(xopcCloudSpeechProvider);
|
|
@@ -23,9 +23,13 @@ export interface SpeakOptions {
|
|
|
23
23
|
parseDirectives?: boolean;
|
|
24
24
|
modelOverrides?: TTSModelOverrideConfig;
|
|
25
25
|
appConfig?: Config;
|
|
26
|
-
/** Optional caller-supplied
|
|
26
|
+
/** Optional caller-supplied cancellation signal. */
|
|
27
27
|
signal?: AbortSignal;
|
|
28
28
|
}
|
|
29
|
+
export interface SpeakStreamOptions extends SpeakOptions {
|
|
30
|
+
/** Disable provider/model fallback after a realtime route has been selected. */
|
|
31
|
+
allowFallback?: boolean;
|
|
32
|
+
}
|
|
29
33
|
/**
|
|
30
34
|
* Run the TTS chain (primary → fallback) and return the first successful result.
|
|
31
35
|
*/
|
|
@@ -51,4 +55,4 @@ export interface SpeakStreamResult {
|
|
|
51
55
|
* Streaming TTS — tries the chain and returns the first provider's stream.
|
|
52
56
|
* Providers without `synthesizeStream` are wrapped via wrapBufferAsStream.
|
|
53
57
|
*/
|
|
54
|
-
export declare function speakStream(text: string, config: TTSConfig, options?:
|
|
58
|
+
export declare function speakStream(text: string, config: TTSConfig, options?: SpeakStreamOptions): Promise<SpeakStreamResult>;
|
|
@@ -87,7 +87,7 @@ async function prepareTextForSynthesis(text, config, options) {
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
/** Build the SpeechSynthesisRequest from a resolved provider + prepared text. */
|
|
90
|
-
function buildSynthesisRequest(resolved, preparedText, ttsOptions, directiveOverrides, appConfig, target) {
|
|
90
|
+
function buildSynthesisRequest(resolved, preparedText, ttsOptions, directiveOverrides, appConfig, target, signal) {
|
|
91
91
|
const providerOverrides = mergeOverrides(ttsOptions, pickProviderOverrides(directiveOverrides, resolved.providerId));
|
|
92
92
|
const callTimeoutMs = ttsOptions?.timeoutMs ?? resolved.timeoutMs;
|
|
93
93
|
return {
|
|
@@ -96,6 +96,7 @@ function buildSynthesisRequest(resolved, preparedText, ttsOptions, directiveOver
|
|
|
96
96
|
providerConfig: resolved.providerConfig,
|
|
97
97
|
target,
|
|
98
98
|
timeoutMs: callTimeoutMs,
|
|
99
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(callTimeoutMs)]) : AbortSignal.timeout(callTimeoutMs),
|
|
99
100
|
...providerOverrides ? { providerOverrides } : {}
|
|
100
101
|
};
|
|
101
102
|
}
|
|
@@ -145,7 +146,7 @@ async function speak(text, config, options) {
|
|
|
145
146
|
textLength: prepared.text.length,
|
|
146
147
|
provider: resolved.providerId
|
|
147
148
|
}, "Converting text to speech");
|
|
148
|
-
const request = buildSynthesisRequest(resolved, prepared.text, prepared.ttsOptions, prepared.directiveOverrides, options?.appConfig, "audio-file");
|
|
149
|
+
const request = buildSynthesisRequest(resolved, prepared.text, prepared.ttsOptions, prepared.directiveOverrides, options?.appConfig, "audio-file", options?.signal);
|
|
149
150
|
const result = await resolved.plugin.synthesize(request);
|
|
150
151
|
const durationSeconds = (Date.now() - startTime) / 1e3;
|
|
151
152
|
attempts.push({
|
|
@@ -209,7 +210,7 @@ async function speakWithProvider(text, config, providerName, options) {
|
|
|
209
210
|
if (typeof resolved.plugin.synthesize !== "function") throw new Error(`Provider '${providerName}' does not implement synthesize()`);
|
|
210
211
|
const prepared = await prepareTextForSynthesis(text, config, options);
|
|
211
212
|
const startTime = Date.now();
|
|
212
|
-
const request = buildSynthesisRequest(resolved, prepared.text, prepared.ttsOptions, prepared.directiveOverrides, options?.appConfig, "audio-file");
|
|
213
|
+
const request = buildSynthesisRequest(resolved, prepared.text, prepared.ttsOptions, prepared.directiveOverrides, options?.appConfig, "audio-file", options?.signal);
|
|
213
214
|
return {
|
|
214
215
|
...toTtsResult(resolved, await resolved.plugin.synthesize(request), (Date.now() - startTime) / 1e3),
|
|
215
216
|
attempts: [{
|
|
@@ -229,41 +230,52 @@ async function speakWithProvider(text, config, providerName, options) {
|
|
|
229
230
|
* Providers without `synthesizeStream` are wrapped via wrapBufferAsStream.
|
|
230
231
|
*/
|
|
231
232
|
async function speakStream(text, config, options) {
|
|
233
|
+
options?.signal?.throwIfAborted();
|
|
232
234
|
if (!config.enabled) throw new Error("TTS is not enabled");
|
|
233
235
|
if (config.trigger === "tagged" && !/\[\[tts/.test(text)) throw new Error("TTS trigger is tagged but no [[tts]] directive found");
|
|
234
236
|
const prepared = await prepareTextForSynthesis(text, config, options);
|
|
235
|
-
|
|
237
|
+
options?.signal?.throwIfAborted();
|
|
238
|
+
const chain = options?.allowFallback === false ? [resolveSpeechProvider(prepared.selectedProvider, config)].filter((provider) => provider !== null) : resolveSpeechProviderChain(config);
|
|
239
|
+
if (chain.length === 0) throw new Error(`TTS provider "${prepared.selectedProvider}" is not available`);
|
|
236
240
|
let lastError;
|
|
237
|
-
for (const resolved of chain)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
241
|
+
for (const [index, resolved] of chain.entries()) {
|
|
242
|
+
options?.signal?.throwIfAborted();
|
|
243
|
+
try {
|
|
244
|
+
const baseRequest = buildSynthesisRequest(resolved, prepared.text, prepared.ttsOptions, prepared.directiveOverrides, options?.appConfig, "voice-note", options?.signal);
|
|
245
|
+
let streamResult;
|
|
246
|
+
if (typeof resolved.plugin.synthesizeStream === "function") streamResult = await resolved.plugin.synthesizeStream(baseRequest);
|
|
247
|
+
else if (typeof resolved.plugin.synthesize === "function") streamResult = wrapBufferAsStream(await resolved.plugin.synthesize(baseRequest));
|
|
248
|
+
else throw new Error(`Provider "${resolved.providerId}" implements neither synthesize nor synthesizeStream`);
|
|
249
|
+
if (options?.signal?.aborted) {
|
|
250
|
+
await streamResult.release?.();
|
|
251
|
+
options.signal.throwIfAborted();
|
|
252
|
+
}
|
|
253
|
+
log.info({
|
|
254
|
+
provider: resolved.providerId,
|
|
255
|
+
outputFormat: streamResult.outputFormat,
|
|
256
|
+
voiceCompatible: streamResult.voiceCompatible,
|
|
257
|
+
streamed: typeof resolved.plugin.synthesizeStream === "function"
|
|
258
|
+
}, "TTS stream ready");
|
|
259
|
+
const release = streamResult.release ?? (async () => {});
|
|
260
|
+
return {
|
|
261
|
+
audioStream: streamResult.audioStream,
|
|
262
|
+
outputFormat: streamResult.outputFormat,
|
|
263
|
+
fileExtension: streamResult.fileExtension,
|
|
264
|
+
voiceCompatible: streamResult.voiceCompatible,
|
|
265
|
+
provider: resolved.providerId,
|
|
266
|
+
release,
|
|
267
|
+
ttsText: prepared.text,
|
|
268
|
+
wasPreprocessed: prepared.wasPreprocessed,
|
|
269
|
+
wasSummarized: prepared.wasSummarized
|
|
270
|
+
};
|
|
271
|
+
} catch (error) {
|
|
272
|
+
options?.signal?.throwIfAborted();
|
|
273
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
274
|
+
log.warn({
|
|
275
|
+
err: lastError,
|
|
276
|
+
provider: resolved.providerId
|
|
277
|
+
}, `TTS stream provider "${resolved.providerId}" failed${index < chain.length - 1 ? ", trying next" : ""}`);
|
|
278
|
+
}
|
|
267
279
|
}
|
|
268
280
|
throw lastError ?? /* @__PURE__ */ new Error("All TTS providers failed (streaming)");
|
|
269
281
|
}
|
|
@@ -86,6 +86,7 @@ export interface SpeechSynthesisRequest {
|
|
|
86
86
|
providerOverrides?: SpeechProviderOverrides;
|
|
87
87
|
/** Hard timeout from the orchestrator (already merged with provider defaults). */
|
|
88
88
|
timeoutMs: number;
|
|
89
|
+
signal: AbortSignal;
|
|
89
90
|
}
|
|
90
91
|
export type SpeechSynthesisStreamRequest = SpeechSynthesisRequest;
|
|
91
92
|
/** Voice catalog entry returned by `listVoices`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xopcai/xopc",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.243",
|
|
4
4
|
"description": "Local-first personal AI assistant with reviewable user understanding, durable tasks, workflows, automations, and local or cloud models.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -67,10 +67,10 @@
|
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@composio/core": "0.14.1",
|
|
69
69
|
"@composio/experimental": "0.2.0",
|
|
70
|
-
"@earendil-works/pi-agent-core": "0.
|
|
71
|
-
"@earendil-works/pi-ai": "0.
|
|
72
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
73
|
-
"@earendil-works/pi-tui": "0.
|
|
70
|
+
"@earendil-works/pi-agent-core": "0.85.1",
|
|
71
|
+
"@earendil-works/pi-ai": "0.85.1",
|
|
72
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
73
|
+
"@earendil-works/pi-tui": "0.85.1",
|
|
74
74
|
"@grammyjs/runner": "^2.0.3",
|
|
75
75
|
"@hono/node-server": "^2.0.4",
|
|
76
76
|
"@inquirer/prompts": "^8.5.0",
|
|
@@ -164,8 +164,8 @@
|
|
|
164
164
|
"vitest": "4.1.5",
|
|
165
165
|
"vue": "^3.5.35",
|
|
166
166
|
"@xopcai/endpoint-tools-protocol": "0.0.0",
|
|
167
|
-
"@xopcai/realtime-protocol": "0.0.0",
|
|
168
167
|
"@xopcai/gateway-contract": "0.0.0",
|
|
168
|
+
"@xopcai/realtime-protocol": "0.0.0",
|
|
169
169
|
"@xopcai/realtime-client": "0.0.0"
|
|
170
170
|
},
|
|
171
171
|
"scripts": {
|