@xopcai/xopc 0.0.143 → 0.0.145
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/Combination-DolwnuKi.js +41 -0
- package/dist/gateway/static/root/assets/{activity-center-store-jITiX2vJ.js → activity-center-store-3l9ahUqb.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DiME9u_P.js → agent-avatar-dicebear-BEUxdhZx.js} +1 -1
- package/dist/gateway/static/root/assets/agents-1bpT0xRO.js +2 -0
- package/dist/gateway/static/root/assets/app-management-settings-panel-CRHSZJWi.js +1 -0
- package/dist/gateway/static/root/assets/{appearance-settings-DUfdNQqk.js → appearance-settings-BunADYJb.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-Cc4JgVUQ.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-B-Qk6cGr.js → archive-plugin-B6vTkH2A.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-Di6EEbQX.js → attachment-load-BjZXzaNv.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-DGxvRxtO.js → automation-suggestion-card-ZDA-Stg1.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-mb0fdVpq.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-ZaOZDH2I.js → binary-plugins-DYZfLvPz.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-BTFhjWfg.js → block-editor-C3MUlYC9.js} +46 -46
- package/dist/gateway/static/root/assets/browser-settings-page-Bb0xhaMN.js +8 -0
- package/dist/gateway/static/root/assets/capability-presets-api-BV76yN9a.js +1 -0
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-CUcGKZqC.js +2 -0
- package/dist/gateway/static/root/assets/channel-recipient-api-fTlTnWB4.js +1 -0
- package/dist/gateway/static/root/assets/channels-settings-BlSDMGdy.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-CJauF1CW.js +1 -0
- package/dist/gateway/static/root/assets/{code-editor--tMO38Tr.js → code-editor-D4_k-6QN.js} +1 -1
- package/dist/gateway/static/root/assets/connector-logo-BVFNT2zW.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-CMgAFGvb.js +2 -0
- package/dist/gateway/static/root/assets/copy-to-clipboard-HOJ79TWv.js +1 -0
- package/dist/gateway/static/root/assets/{desktop-pet---yzVxVR.js → desktop-pet-_jUmoXd3.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-Dc3gqBJE.js → desktop-pet-settings-DwGOy4AQ.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-DCmOGLtK.js → directory-picker-path-field-akvF0F-u.js} +1 -1
- package/dist/gateway/static/root/assets/dist-C99RGfAH.js +1 -0
- package/dist/gateway/static/root/assets/dreaming-settings-BLetQiYY.js +3 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-ByaKTTq4.js → extension-debug-page-Cof2ncxE.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-BmRzHAZr.js → extension-page-CwWktRNz.js} +1 -1
- package/dist/gateway/static/root/assets/extension-settings-page-CuggWOIl.js +1 -0
- package/dist/gateway/static/root/assets/gateway-config-api-B7sxIbza.js +1 -0
- package/dist/gateway/static/root/assets/gateway-settings-DIv0eKMf.js +1 -0
- package/dist/gateway/static/root/assets/gateway-startup-retry-DtoLihQE.js +1 -0
- package/dist/gateway/static/root/assets/goal-create-dialog-C4Kw0mfS.js +1 -0
- package/dist/gateway/static/root/assets/goal-detail-page-BMmzYpZL.js +4 -0
- package/dist/gateway/static/root/assets/goals-config-api-B0jm5JaA.js +1 -0
- package/dist/gateway/static/root/assets/goals-page-CGeT77PC.js +2 -0
- package/dist/gateway/static/root/assets/goals-settings-N5_iL7es.js +1 -0
- package/dist/gateway/static/root/assets/heartbeat-settings-XzH3lbRR.js +1 -0
- package/dist/gateway/static/root/assets/{html-workspace-editor-COqmtESs.js → html-workspace-editor-C3DKAkMc.js} +1 -1
- package/dist/gateway/static/root/assets/index-CSLF84-Q.css +1 -0
- package/dist/gateway/static/root/assets/index-I-ZCenVw.js +259 -0
- package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-x_v0nrIi.js +1 -0
- package/dist/gateway/static/root/assets/locale-store-CiM-D9jA.js +1 -0
- package/dist/gateway/static/root/assets/logs-page-BSh1t8cD.js +7 -0
- package/dist/gateway/static/root/assets/markdown-editor-BvUebh3C.js +2 -0
- package/dist/gateway/static/root/assets/{markdown-split-Dw-yE2-y.js → markdown-split-5rJx5flj.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-D9NNNUgE.js → media-plugins-DYXccwAd.js} +1 -1
- package/dist/gateway/static/root/assets/messages-Cb1VMeqP.js +5 -0
- package/dist/gateway/static/root/assets/models-hub-panel-BNpTenAI.js +2 -0
- package/dist/gateway/static/root/assets/{notes-page-CRgbJGgk.js → notes-page-BgLyndCO.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-teklkhcE.js +4 -0
- package/dist/gateway/static/root/assets/{page-tabs-D4hkWVt_.js → page-tabs-6Al9_sGz.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-CIckv0uy.js → preview-open-alternatives-Cm3G72Hv.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-CCB_dKoh.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-VOAFAHYj.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-USYu64Ru.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-B0b42kB2.js → save-bar-controls-BYtfzu_F.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-BtjEkczK.js → schedule-field-CvIRc6yK.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-Bf0bk3i6.js → schema-form-CQuKx0n7.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-oGC8O5h_.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-D4rP66R1.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-BTKTdTNX.js → settings-collapsible-section-CF3HHM-t.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-DL4P0sAj.js → settings-form-section-CwWjoRN6.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-BV6KFNj4.js → settings-loading-skeleton-aUFmJa13.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-B_4DsX5W.js → settings-page-B2UUU9TF.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-layout-CyGlrInB.js → settings-page-layout-DqpbTWqI.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-Ck9keROI.js +1 -0
- package/dist/gateway/static/root/assets/share-preview-page-BOhlPsS1.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-BYLsCuqj.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-BNHUyVML.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-CpXbQH4n.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-D85fBBuI.js → system-settings-panel-CWxSvuwD.js} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-Bt6vAkSB.js → theme-store-C9Dxcppm.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-CwEuYrHX.js → typed-models-lib-qROAvqTw.js} +1 -1
- package/dist/gateway/static/root/assets/use-channel-catalog-DE9cpn4G.js +1 -0
- package/dist/gateway/static/root/assets/user-context-page-DbNEK4Yp.js +1 -0
- package/dist/gateway/static/root/assets/user-profile-fields-editor-CWxc0ghP.js +1 -0
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-CWM0fEpb.js → user-profile-settings-panel-DW-Up6vt.js} +1 -1
- package/dist/gateway/static/root/assets/{utils--BO6xT3u.js → utils-D53uRXVr.js} +1 -1
- package/dist/gateway/static/root/assets/{vendor-codemirror-C1WKjCk5.js → vendor-codemirror--gTq3YAI.js} +17 -17
- package/dist/gateway/static/root/assets/voice-api-key-field-Cs7MOrg0.js +1 -0
- package/dist/gateway/static/root/assets/work-item-detail-page-D1ziG1Pp.js +1 -0
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-D_x9ELJp.js +3 -0
- package/dist/gateway/static/root/assets/workflows-page-DHGV_n6i.js +11 -0
- package/dist/gateway/static/root/index.html +10 -10
- package/dist/package.js +1 -1
- package/dist/src/agent/embedded/session-runner.js +3 -3
- package/dist/src/agent/embedded/session-runner.js.map +1 -1
- package/dist/src/agent/embedded/xopc-auth-storage.d.ts +3 -1
- package/dist/src/agent/embedded/xopc-auth-storage.js +11 -1
- package/dist/src/agent/embedded/xopc-auth-storage.js.map +1 -1
- package/dist/src/agent/memory/dreaming/config.js +1 -1
- package/dist/src/agent-runtime/index.js +1 -1
- package/dist/src/agent-runtime/runtime-profile.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/config/index.js +2 -2
- package/dist/src/config/schema.d.ts +60 -5
- package/dist/src/config/schema.js +30 -9
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/connectors/composio-catalog.js +11 -2
- package/dist/src/connectors/composio-catalog.js.map +1 -1
- package/dist/src/connectors/connector-memory-sync.js +135 -26
- package/dist/src/connectors/connector-memory-sync.js.map +1 -1
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/app.js +1 -1
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.d.ts +11 -0
- package/dist/src/gateway/hono/lib/config-payload.js +1 -0
- package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +6 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js.map +1 -1
- package/dist/src/gateway/hono/routes/memory.js +11 -1
- package/dist/src/gateway/hono/routes/memory.js.map +1 -1
- package/dist/src/gateway/hono/routes/status.js +3 -0
- package/dist/src/gateway/hono/routes/status.js.map +1 -1
- package/dist/src/gateway/hono/routes/voice.d.ts +5 -5
- package/dist/src/gateway/hono/routes/voice.js +288 -62
- package/dist/src/gateway/hono/routes/voice.js.map +1 -1
- package/dist/src/gateway/hono/routes/you.js +5 -1
- package/dist/src/gateway/hono/routes/you.js.map +1 -1
- package/dist/src/gateway/service.d.ts +8 -0
- package/dist/src/gateway/service.js +41 -11
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/knowledge/connected-knowledge-pipeline.d.ts +21 -0
- package/dist/src/knowledge/connected-knowledge-pipeline.js +267 -0
- package/dist/src/knowledge/connected-knowledge-pipeline.js.map +1 -0
- package/dist/src/knowledge/coordinator.d.ts +10 -0
- package/dist/src/knowledge/coordinator.js +52 -0
- package/dist/src/knowledge/coordinator.js.map +1 -0
- package/dist/src/knowledge/index.d.ts +2 -0
- package/dist/src/knowledge/index.js +3 -1
- package/dist/src/knowledge/ingestion-service.d.ts +7 -1
- package/dist/src/knowledge/ingestion-service.js +28 -0
- package/dist/src/knowledge/ingestion-service.js.map +1 -1
- package/dist/src/knowledge/types.d.ts +18 -0
- package/dist/src/media-understanding/audio-transcription-runner.js +5 -3
- package/dist/src/media-understanding/audio-transcription-runner.js.map +1 -1
- package/dist/src/media-understanding/runner.js +8 -3
- package/dist/src/media-understanding/runner.js.map +1 -1
- package/dist/src/media-understanding/types.d.ts +7 -0
- package/dist/src/storage/sqlite/index.d.ts +1 -1
- package/dist/src/storage/sqlite/index.js +2 -2
- package/dist/src/storage/sqlite/knowledge-repository.d.ts +26 -1
- package/dist/src/storage/sqlite/knowledge-repository.js +120 -6
- package/dist/src/storage/sqlite/knowledge-repository.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/037_connected_knowledge_pipeline.sql +47 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/user-context/projection.d.ts +13 -4
- package/dist/src/user-context/projection.js +19 -7
- package/dist/src/user-context/projection.js.map +1 -1
- package/dist/src/voice/language-profile.d.ts +20 -0
- package/dist/src/voice/language-profile.js +156 -0
- package/dist/src/voice/language-profile.js.map +1 -0
- package/dist/src/voice/local/model-files.d.ts +14 -0
- package/dist/src/voice/local/model-files.js +94 -0
- package/dist/src/voice/local/model-files.js.map +1 -0
- package/dist/src/voice/local/model-manager.d.ts +18 -0
- package/dist/src/voice/local/model-manager.js +135 -0
- package/dist/src/voice/local/model-manager.js.map +1 -0
- package/dist/src/voice/local/models.d.ts +28 -0
- package/dist/src/voice/local/models.js +101 -0
- package/dist/src/voice/local/models.js.map +1 -0
- package/dist/src/voice/local/runtime-client.d.ts +26 -0
- package/dist/src/voice/local/runtime-client.js +166 -0
- package/dist/src/voice/local/runtime-client.js.map +1 -0
- package/dist/src/voice/local/runtime-dispatcher.d.ts +9 -0
- package/dist/src/voice/local/runtime-dispatcher.js +22 -0
- package/dist/src/voice/local/runtime-dispatcher.js.map +1 -0
- package/dist/src/voice/local/runtime-worker.d.ts +1 -0
- package/dist/src/voice/local/runtime-worker.js +249 -0
- package/dist/src/voice/local/runtime-worker.js.map +1 -0
- package/dist/src/voice/local/wav.d.ts +6 -0
- package/dist/src/voice/local/wav.js +69 -0
- package/dist/src/voice/local/wav.js.map +1 -0
- package/dist/src/voice/metadata/builtin.js +46 -0
- package/dist/src/voice/metadata/builtin.js.map +1 -1
- package/dist/src/voice/stt/factory.js +8 -2
- package/dist/src/voice/stt/factory.js.map +1 -1
- package/dist/src/voice/stt/providers/alibaba-transcription.js +14 -8
- package/dist/src/voice/stt/providers/alibaba-transcription.js.map +1 -1
- package/dist/src/voice/stt/providers/index.d.ts +1 -0
- package/dist/src/voice/stt/providers/index.js +2 -1
- package/dist/src/voice/stt/providers/local-transcription.d.ts +2 -0
- package/dist/src/voice/stt/providers/local-transcription.js +51 -0
- package/dist/src/voice/stt/providers/local-transcription.js.map +1 -0
- package/dist/src/voice/stt/providers/openai-transcription.js +5 -3
- package/dist/src/voice/stt/providers/openai-transcription.js.map +1 -1
- package/dist/src/voice/stt/transcribe-core.js +5 -4
- package/dist/src/voice/stt/transcribe-core.js.map +1 -1
- package/dist/src/voice/stt/types.d.ts +4 -0
- package/dist/src/voice/stt/types.js +5 -4
- package/dist/src/voice/stt/types.js.map +1 -1
- package/dist/src/voice/tts/providers/edge-speech.js +2 -2
- package/dist/src/voice/tts/providers/edge-speech.js.map +1 -1
- package/dist/src/voice/tts/types.js +3 -3
- package/dist/src/voice/tts/types.js.map +1 -1
- package/package.json +4 -2
- package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +0 -41
- package/dist/gateway/static/root/assets/agents-DzBXhZXO.js +0 -2
- package/dist/gateway/static/root/assets/app-management-settings-panel-Cuo7SbUP.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-CKuKK5AU.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-D-FF_WKV.js +0 -4
- package/dist/gateway/static/root/assets/browser-settings-page-BvKLdEQt.js +0 -8
- package/dist/gateway/static/root/assets/capability-presets-api-BU-pVZiZ.js +0 -1
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-DgyeEwzh.js +0 -2
- package/dist/gateway/static/root/assets/channel-recipient-api-DuulnhIj.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-BbVsdD_I.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-B5lVrUsN.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-B8QqVy3I.js +0 -2
- package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +0 -1
- package/dist/gateway/static/root/assets/dreaming-settings-BgT5KZVe.js +0 -3
- package/dist/gateway/static/root/assets/extension-settings-page-JUtwIhbF.js +0 -1
- package/dist/gateway/static/root/assets/fetch-8QNN25Sh.js +0 -1
- package/dist/gateway/static/root/assets/gateway-config-api-CxicF50_.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-Bz2I_EO5.js +0 -1
- package/dist/gateway/static/root/assets/gateway-startup-retry-5YBnS9Fj.js +0 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-VcoyuZGN.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-Cilkap2G.js +0 -4
- package/dist/gateway/static/root/assets/goals-config-api-CwcHRdC0.js +0 -1
- package/dist/gateway/static/root/assets/goals-page-C6eoni4z.js +0 -2
- package/dist/gateway/static/root/assets/goals-settings-o4i-qAOS.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-DQQkETX8.js +0 -1
- package/dist/gateway/static/root/assets/index-BKRqeRHm.js +0 -257
- package/dist/gateway/static/root/assets/index-CphxClbs.css +0 -1
- package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-LT6qB-ua.js +0 -1
- package/dist/gateway/static/root/assets/locale-store-Ba72bFmP.js +0 -1
- package/dist/gateway/static/root/assets/logs-page-Dli2e3fS.js +0 -7
- package/dist/gateway/static/root/assets/markdown-editor-jD1LPiP-.js +0 -2
- package/dist/gateway/static/root/assets/messages-CzKxM6I4.js +0 -5
- package/dist/gateway/static/root/assets/models-hub-panel-DVn2Q1Ik.js +0 -2
- package/dist/gateway/static/root/assets/notes-workbench-BxZRHLWx.js +0 -2
- package/dist/gateway/static/root/assets/project-detail-page-Doz53xLn.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-DoMtcRLg.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-BeHVEgrm.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-Bk4bQsBe.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DdFyaRMm.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-DJ1_MLgX.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-BQBV7YrV.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-0VuFLBeU.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-DO87Ckeo.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-D3VdtxAn.js +0 -2
- package/dist/gateway/static/root/assets/use-channel-catalog-COXIfXcl.js +0 -1
- package/dist/gateway/static/root/assets/user-context-page-m9JVQ73F.js +0 -1
- package/dist/gateway/static/root/assets/user-profile-fields-editor-DtQiuY7E.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-LoT3wgZ9.js +0 -1
- package/dist/gateway/static/root/assets/work-item-detail-page-DSTMz_mO.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-B35lJzvm.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-BfSm9oR8.js +0 -11
|
@@ -4,6 +4,7 @@ import { resolveTtsProviderConfigSlice } from "../../../voice/tts/config-slice.j
|
|
|
4
4
|
import { isTTSAvailable } from "../../../voice/tts/factory.js";
|
|
5
5
|
import { speak } from "../../../voice/tts/speak-core.js";
|
|
6
6
|
import { mergeTtsConfigFromAppConfig } from "../../../voice/tts/merge-config.js";
|
|
7
|
+
import { getLocalVoiceRuntimeClient } from "../../../voice/local/runtime-client.js";
|
|
7
8
|
import { resolveSttProviderConfigSlice } from "../../../voice/stt/config-slice.js";
|
|
8
9
|
import { transcribe } from "../../../voice/stt/transcribe-core.js";
|
|
9
10
|
import { isSTTAvailable } from "../../../voice/stt/availability.js";
|
|
@@ -11,6 +12,7 @@ import "../../../voice/stt/index.js";
|
|
|
11
12
|
import { mergeSttConfigFromAppConfig } from "../../../channels/attachments/voice-stt-webchat.js";
|
|
12
13
|
import { listSttProvidersForApi } from "../../../voice/stt/list-providers.js";
|
|
13
14
|
import "../../../voice/tts/index.js";
|
|
15
|
+
import { listLocalVoiceModelStatuses, removeLocalVoiceModel, startLocalVoiceModelInstall } from "../../../voice/local/model-manager.js";
|
|
14
16
|
import { createGatewayRouteLogger } from "../lib/route-logger.js";
|
|
15
17
|
import { listTtsProvidersForApi } from "../../../voice/tts/list-providers.js";
|
|
16
18
|
//#region src/gateway/hono/routes/voice.ts
|
|
@@ -24,7 +26,14 @@ function readVoiceApiKeyFromConfigFileOnly(cfg, kind, providerId) {
|
|
|
24
26
|
}
|
|
25
27
|
const REFINE_TIMEOUT_MS = 15e3;
|
|
26
28
|
const MAX_AUDIO_BYTES = 25 * 1024 * 1024;
|
|
27
|
-
const
|
|
29
|
+
const MAX_SPEECH_TEXT_LENGTH = 1200;
|
|
30
|
+
function speechMimeType(format) {
|
|
31
|
+
if (format === "opus" || format === "ogg") return "audio/ogg";
|
|
32
|
+
if (format === "mp3" || format === "mpeg") return "audio/mpeg";
|
|
33
|
+
if (format === "wav") return "audio/wav";
|
|
34
|
+
return `audio/${format}`;
|
|
35
|
+
}
|
|
36
|
+
const LIGHT_REFINE_PROMPT = `你是语音转文字后处理助手。将语音转写原文整理为高质量文本输入。
|
|
28
37
|
|
|
29
38
|
规则:
|
|
30
39
|
1. 修正明显的语音识别错误
|
|
@@ -33,7 +42,14 @@ const REFINE_SYSTEM_PROMPT = `你是语音转文字后处理助手。将语音
|
|
|
33
42
|
4. 保持原意不变,不要扩写或改变语义
|
|
34
43
|
5. 如果原文已经很好,原样输出
|
|
35
44
|
6. 只输出整理后的文字,不要解释`;
|
|
36
|
-
|
|
45
|
+
const PUNCTUATION_REFINE_PROMPT = `为语音转写原文补充标点和合理分段。不要删词、改写、扩写或改变语义。只输出处理后的文字。`;
|
|
46
|
+
function resolveRefineModel(config, configuredRef) {
|
|
47
|
+
if (configuredRef) try {
|
|
48
|
+
return resolveModel(configuredRef);
|
|
49
|
+
} catch {
|
|
50
|
+
log.warn({ modelRef: configuredRef }, "Configured voice refinement model is invalid");
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
37
53
|
const envRef = process.env.XOPC_VOICE_REFINE_MODEL?.trim();
|
|
38
54
|
if (envRef) try {
|
|
39
55
|
return resolveModel(envRef);
|
|
@@ -49,7 +65,15 @@ function resolveRefineModel(config) {
|
|
|
49
65
|
}
|
|
50
66
|
async function refineTranscript(raw, config, signal) {
|
|
51
67
|
if (!raw.trim()) return void 0;
|
|
52
|
-
const
|
|
68
|
+
const refinement = config?.voice?.input?.refinement;
|
|
69
|
+
const mode = refinement?.mode ?? "off";
|
|
70
|
+
if (mode === "off") return void 0;
|
|
71
|
+
const instruction = mode === "punctuation" ? PUNCTUATION_REFINE_PROMPT : mode === "custom" ? refinement?.customInstruction?.trim() : LIGHT_REFINE_PROMPT;
|
|
72
|
+
if (!instruction) {
|
|
73
|
+
log.warn("Custom voice refinement is enabled without an instruction; returning raw only");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const model = resolveRefineModel(config, refinement?.model);
|
|
53
77
|
if (!model) {
|
|
54
78
|
log.debug("No LLM model available for voice refine; returning raw only");
|
|
55
79
|
return;
|
|
@@ -57,7 +81,7 @@ async function refineTranscript(raw, config, signal) {
|
|
|
57
81
|
try {
|
|
58
82
|
const userMsg = {
|
|
59
83
|
role: "user",
|
|
60
|
-
content: `${
|
|
84
|
+
content: `${instruction}\n\n原文:${raw}`,
|
|
61
85
|
timestamp: Date.now()
|
|
62
86
|
};
|
|
63
87
|
const timeoutSignal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(REFINE_TIMEOUT_MS) : void 0;
|
|
@@ -82,6 +106,30 @@ async function refineTranscript(raw, config, signal) {
|
|
|
82
106
|
}
|
|
83
107
|
function registerVoiceRoutes(authenticated, deps) {
|
|
84
108
|
const { service, strictRateLimitMiddleware } = deps;
|
|
109
|
+
authenticated.post("/api/voice/language", strictRateLimitMiddleware, async (c) => {
|
|
110
|
+
let body = {};
|
|
111
|
+
try {
|
|
112
|
+
body = await c.req.json();
|
|
113
|
+
} catch {
|
|
114
|
+
return c.json({
|
|
115
|
+
ok: false,
|
|
116
|
+
error: { message: "Invalid JSON body" }
|
|
117
|
+
}, 400);
|
|
118
|
+
}
|
|
119
|
+
if (body.language !== "en" && body.language !== "zh") return c.json({
|
|
120
|
+
ok: false,
|
|
121
|
+
error: { message: "language must be \"en\" or \"zh\"" }
|
|
122
|
+
}, 400);
|
|
123
|
+
const payload = await service.syncVoiceLanguage(body.language);
|
|
124
|
+
if (payload.error) return c.json({
|
|
125
|
+
ok: false,
|
|
126
|
+
error: { message: payload.error }
|
|
127
|
+
}, 500);
|
|
128
|
+
return c.json({
|
|
129
|
+
ok: true,
|
|
130
|
+
payload
|
|
131
|
+
});
|
|
132
|
+
});
|
|
85
133
|
/**
|
|
86
134
|
* GET /api/voice/providers
|
|
87
135
|
*
|
|
@@ -110,6 +158,64 @@ function registerVoiceRoutes(authenticated, deps) {
|
|
|
110
158
|
payload
|
|
111
159
|
});
|
|
112
160
|
});
|
|
161
|
+
authenticated.get("/api/voice/local/status", async (c) => {
|
|
162
|
+
const models = await listLocalVoiceModelStatuses();
|
|
163
|
+
try {
|
|
164
|
+
const runtime = await getLocalVoiceRuntimeClient().request("health", {}, {
|
|
165
|
+
timeoutMs: 1e4,
|
|
166
|
+
stopOnTimeout: false
|
|
167
|
+
});
|
|
168
|
+
return c.json({
|
|
169
|
+
ok: true,
|
|
170
|
+
payload: {
|
|
171
|
+
runtime: {
|
|
172
|
+
...runtime,
|
|
173
|
+
ready: true
|
|
174
|
+
},
|
|
175
|
+
models
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
180
|
+
return c.json({
|
|
181
|
+
ok: true,
|
|
182
|
+
payload: {
|
|
183
|
+
runtime: {
|
|
184
|
+
ready: false,
|
|
185
|
+
error: message
|
|
186
|
+
},
|
|
187
|
+
models
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
authenticated.post("/api/voice/local/models/:modelId/install", strictRateLimitMiddleware, (c) => {
|
|
193
|
+
try {
|
|
194
|
+
const status = startLocalVoiceModelInstall(c.req.param("modelId"));
|
|
195
|
+
return c.json({
|
|
196
|
+
ok: true,
|
|
197
|
+
payload: { model: status }
|
|
198
|
+
}, 202);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
201
|
+
return c.json({
|
|
202
|
+
ok: false,
|
|
203
|
+
error: { message }
|
|
204
|
+
}, 400);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
authenticated.delete("/api/voice/local/models/:modelId", strictRateLimitMiddleware, async (c) => {
|
|
208
|
+
try {
|
|
209
|
+
await removeLocalVoiceModel(c.req.param("modelId"));
|
|
210
|
+
return c.json({ ok: true });
|
|
211
|
+
} catch (error) {
|
|
212
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
213
|
+
return c.json({
|
|
214
|
+
ok: false,
|
|
215
|
+
error: { message }
|
|
216
|
+
}, 400);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
113
219
|
/**
|
|
114
220
|
* POST /api/voice/reveal-api-key — return plaintext voice provider apiKey from config file only.
|
|
115
221
|
* Body: `{ kind: 'stt' | 'tts', provider: string }`
|
|
@@ -146,6 +252,88 @@ function registerVoiceRoutes(authenticated, deps) {
|
|
|
146
252
|
}
|
|
147
253
|
});
|
|
148
254
|
});
|
|
255
|
+
/** Generate one bounded read-aloud chunk as binary audio. */
|
|
256
|
+
authenticated.post("/api/voice/speech", strictRateLimitMiddleware, async (c) => {
|
|
257
|
+
let body = {};
|
|
258
|
+
try {
|
|
259
|
+
body = await c.req.json();
|
|
260
|
+
} catch {
|
|
261
|
+
return c.json({
|
|
262
|
+
ok: false,
|
|
263
|
+
error: { message: "Invalid JSON body" }
|
|
264
|
+
}, 400);
|
|
265
|
+
}
|
|
266
|
+
const text = typeof body.text === "string" ? body.text.trim() : "";
|
|
267
|
+
if (!text) return c.json({
|
|
268
|
+
ok: false,
|
|
269
|
+
error: { message: "text is required" }
|
|
270
|
+
}, 400);
|
|
271
|
+
if (text.length > MAX_SPEECH_TEXT_LENGTH) return c.json({
|
|
272
|
+
ok: false,
|
|
273
|
+
error: { message: `text exceeds ${MAX_SPEECH_TEXT_LENGTH} characters` }
|
|
274
|
+
}, 400);
|
|
275
|
+
const language = typeof body.language === "string" ? body.language.trim() : "";
|
|
276
|
+
const voice = typeof body.voice === "string" ? body.voice.trim() : "";
|
|
277
|
+
if (language && !/^[a-z]{2,3}(?:-[A-Z]{2})?$/.test(language)) return c.json({
|
|
278
|
+
ok: false,
|
|
279
|
+
error: { message: "language must be a valid locale" }
|
|
280
|
+
}, 400);
|
|
281
|
+
if (voice.length > 100) return c.json({
|
|
282
|
+
ok: false,
|
|
283
|
+
error: { message: "voice exceeds 100 characters" }
|
|
284
|
+
}, 400);
|
|
285
|
+
const config = service.currentConfig;
|
|
286
|
+
const baseTtsConfig = mergeTtsConfigFromAppConfig(config.messages?.tts);
|
|
287
|
+
const provider = baseTtsConfig.provider;
|
|
288
|
+
const languageVoice = language.startsWith("en") ? "en-US-MichelleNeural" : language.startsWith("zh") ? "zh-CN-XiaoxiaoNeural" : void 0;
|
|
289
|
+
const providerOverride = provider === "edge" ? {
|
|
290
|
+
...language ? { lang: language } : {},
|
|
291
|
+
...voice || languageVoice ? { voice: voice || languageVoice } : {}
|
|
292
|
+
} : {};
|
|
293
|
+
const ttsConfig = {
|
|
294
|
+
...baseTtsConfig,
|
|
295
|
+
enabled: true,
|
|
296
|
+
providers: {
|
|
297
|
+
...baseTtsConfig.providers ?? {},
|
|
298
|
+
[provider]: {
|
|
299
|
+
...baseTtsConfig.providers?.[provider] ?? {},
|
|
300
|
+
...providerOverride
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
if (!isTTSAvailable(ttsConfig)) return c.json({
|
|
305
|
+
ok: false,
|
|
306
|
+
error: { message: `TTS provider \"${provider}\" is not configured.` }
|
|
307
|
+
}, 503);
|
|
308
|
+
try {
|
|
309
|
+
const result = await speak(text, ttsConfig, {
|
|
310
|
+
appConfig: config,
|
|
311
|
+
parseDirectives: false,
|
|
312
|
+
signal: c.req.raw.signal,
|
|
313
|
+
tts: voice ? { voice } : void 0
|
|
314
|
+
});
|
|
315
|
+
if (!result.audio.length) throw new Error(`TTS provider \"${result.provider}\" returned empty audio`);
|
|
316
|
+
return new Response(new Uint8Array(result.audio), { headers: {
|
|
317
|
+
"Content-Type": speechMimeType(result.format),
|
|
318
|
+
"Content-Length": String(result.audio.length),
|
|
319
|
+
"Cache-Control": "private, no-store",
|
|
320
|
+
"X-Content-Type-Options": "nosniff",
|
|
321
|
+
"X-Voice-Provider": result.provider,
|
|
322
|
+
...language ? { "X-Voice-Language": language } : {}
|
|
323
|
+
} });
|
|
324
|
+
} catch (error) {
|
|
325
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
326
|
+
if (!c.req.raw.signal.aborted) log.error({
|
|
327
|
+
err: error,
|
|
328
|
+
provider,
|
|
329
|
+
textLength: text.length
|
|
330
|
+
}, `Voice speech generation failed: ${msg}`);
|
|
331
|
+
return c.json({
|
|
332
|
+
ok: false,
|
|
333
|
+
error: { message: `Speech generation failed: ${msg}` }
|
|
334
|
+
}, 502);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
149
337
|
/**
|
|
150
338
|
* POST /api/voice/tts-test
|
|
151
339
|
*
|
|
@@ -230,84 +418,122 @@ function registerVoiceRoutes(authenticated, deps) {
|
|
|
230
418
|
}, 502);
|
|
231
419
|
}
|
|
232
420
|
});
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
*
|
|
236
|
-
* Body: { audio: string (base64), mimeType: string, language?: string }
|
|
237
|
-
* Response: { ok: true, payload: { raw: string, refined?: string, language?: string } }
|
|
238
|
-
*/
|
|
239
|
-
authenticated.post("/api/voice/transcribe", async (c) => {
|
|
240
|
-
let body = {};
|
|
241
|
-
try {
|
|
242
|
-
body = await c.req.json();
|
|
243
|
-
} catch {
|
|
244
|
-
return c.json({
|
|
245
|
-
ok: false,
|
|
246
|
-
error: { message: "Invalid JSON body" }
|
|
247
|
-
}, 400);
|
|
248
|
-
}
|
|
249
|
-
const { audio, mimeType, language } = body;
|
|
250
|
-
if (!audio || typeof audio !== "string") return c.json({
|
|
251
|
-
ok: false,
|
|
252
|
-
error: { message: "Missing required field: audio (base64)" }
|
|
253
|
-
}, 400);
|
|
254
|
-
if (!mimeType || typeof mimeType !== "string") return c.json({
|
|
255
|
-
ok: false,
|
|
256
|
-
error: { message: "Missing required field: mimeType" }
|
|
257
|
-
}, 400);
|
|
258
|
-
let audioBuffer;
|
|
259
|
-
try {
|
|
260
|
-
audioBuffer = Buffer.from(audio, "base64");
|
|
261
|
-
} catch {
|
|
262
|
-
return c.json({
|
|
263
|
-
ok: false,
|
|
264
|
-
error: { message: "Invalid base64 audio data" }
|
|
265
|
-
}, 400);
|
|
266
|
-
}
|
|
267
|
-
if (audioBuffer.length === 0) return c.json({
|
|
421
|
+
const runTranscription = async (c, input) => {
|
|
422
|
+
if (!input.audioBuffer.length) return c.json({
|
|
268
423
|
ok: false,
|
|
269
424
|
error: { message: "Empty audio data" }
|
|
270
425
|
}, 400);
|
|
271
|
-
if (audioBuffer.length > MAX_AUDIO_BYTES) return c.json({
|
|
426
|
+
if (input.audioBuffer.length > MAX_AUDIO_BYTES) return c.json({
|
|
272
427
|
ok: false,
|
|
273
428
|
error: { message: "Audio data exceeds 25 MB limit" }
|
|
274
429
|
}, 400);
|
|
430
|
+
if (!input.mimeType.toLowerCase().startsWith("audio/")) return c.json({
|
|
431
|
+
ok: false,
|
|
432
|
+
error: { message: "Uploaded file must have an audio MIME type" }
|
|
433
|
+
}, 400);
|
|
275
434
|
const config = service.currentConfig;
|
|
276
|
-
const
|
|
277
|
-
const sttConfig = mergeSttConfigFromAppConfig(sttConfigRaw, config.tools?.media);
|
|
435
|
+
const sttConfig = mergeSttConfigFromAppConfig(config.tools?.media?.audio, config.tools?.media);
|
|
278
436
|
if (!isSTTAvailable(sttConfig)) return c.json({
|
|
279
437
|
ok: false,
|
|
280
438
|
error: { message: "STT is not configured. Enable STT in gateway config (tools.media.audio)." }
|
|
281
439
|
}, 503);
|
|
282
|
-
|
|
283
|
-
let detectedLanguage;
|
|
440
|
+
const startedAt = Date.now();
|
|
284
441
|
try {
|
|
285
|
-
const result = await transcribe(audioBuffer, sttConfig, {
|
|
286
|
-
|
|
287
|
-
|
|
442
|
+
const result = await transcribe(input.audioBuffer, sttConfig, {
|
|
443
|
+
language: input.language || (sttConfig.provider === "alibaba" ? "zh" : void 0),
|
|
444
|
+
mime: input.mimeType,
|
|
445
|
+
fileName: input.fileName,
|
|
446
|
+
signal: c.req.raw.signal
|
|
447
|
+
});
|
|
448
|
+
const raw = result.text;
|
|
449
|
+
const detectedLanguage = result.language ?? input.language;
|
|
450
|
+
const refined = raw.trim() ? await refineTranscript(raw, config, c.req.raw.signal) : void 0;
|
|
451
|
+
return c.json({
|
|
452
|
+
ok: true,
|
|
453
|
+
payload: {
|
|
454
|
+
raw,
|
|
455
|
+
...refined ? { refined } : {},
|
|
456
|
+
...detectedLanguage ? { language: detectedLanguage } : {},
|
|
457
|
+
provider: result.provider,
|
|
458
|
+
attempts: result.attempts,
|
|
459
|
+
latencyMs: Date.now() - startedAt
|
|
460
|
+
}
|
|
461
|
+
});
|
|
288
462
|
} catch (error) {
|
|
289
463
|
const msg = error instanceof Error ? error.message : String(error);
|
|
290
|
-
log.error({
|
|
464
|
+
if (!c.req.raw.signal.aborted) log.error({
|
|
465
|
+
err: error,
|
|
466
|
+
mimeType: input.mimeType,
|
|
467
|
+
fileName: input.fileName
|
|
468
|
+
}, `Voice transcription failed: ${msg}`);
|
|
291
469
|
return c.json({
|
|
292
470
|
ok: false,
|
|
293
471
|
error: { message: `Transcription failed: ${msg}` }
|
|
294
472
|
}, 502);
|
|
295
473
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
474
|
+
};
|
|
475
|
+
/** Multipart endpoint used by browser and future native clients. */
|
|
476
|
+
authenticated.post("/api/voice/transcriptions", strictRateLimitMiddleware, async (c) => {
|
|
477
|
+
const contentLength = Number(c.req.header("content-length") ?? 0);
|
|
478
|
+
if (Number.isFinite(contentLength) && contentLength > MAX_AUDIO_BYTES + 1024 * 1024) return c.json({
|
|
479
|
+
ok: false,
|
|
480
|
+
error: { message: "Audio data exceeds 25 MB limit" }
|
|
481
|
+
}, 413);
|
|
482
|
+
let form;
|
|
483
|
+
try {
|
|
484
|
+
form = await c.req.formData();
|
|
485
|
+
} catch {
|
|
486
|
+
return c.json({
|
|
487
|
+
ok: false,
|
|
488
|
+
error: { message: "Invalid multipart form data" }
|
|
489
|
+
}, 400);
|
|
490
|
+
}
|
|
491
|
+
const audio = form.get("audio");
|
|
492
|
+
if (!(audio instanceof Blob)) return c.json({
|
|
493
|
+
ok: false,
|
|
494
|
+
error: { message: "Missing required file field: audio" }
|
|
495
|
+
}, 400);
|
|
496
|
+
const languageValue = form.get("language");
|
|
497
|
+
const language = typeof languageValue === "string" && languageValue.trim() ? languageValue.trim() : void 0;
|
|
498
|
+
const fileName = "name" in audio && typeof audio.name === "string" && audio.name.trim() ? audio.name.trim() : `audio-${Date.now()}`;
|
|
499
|
+
return runTranscription(c, {
|
|
500
|
+
audioBuffer: Buffer.from(await audio.arrayBuffer()),
|
|
501
|
+
mimeType: audio.type || "audio/webm",
|
|
502
|
+
fileName,
|
|
503
|
+
...language ? { language } : {}
|
|
302
504
|
});
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
505
|
+
});
|
|
506
|
+
/** Legacy JSON/base64 endpoint retained for mobile and external clients. */
|
|
507
|
+
authenticated.post("/api/voice/transcribe", strictRateLimitMiddleware, async (c) => {
|
|
508
|
+
let body = {};
|
|
509
|
+
try {
|
|
510
|
+
body = await c.req.json();
|
|
511
|
+
} catch {
|
|
512
|
+
return c.json({
|
|
513
|
+
ok: false,
|
|
514
|
+
error: { message: "Invalid JSON body" }
|
|
515
|
+
}, 400);
|
|
516
|
+
}
|
|
517
|
+
if (typeof body.audio !== "string" || !body.audio.trim()) return c.json({
|
|
518
|
+
ok: false,
|
|
519
|
+
error: { message: "Missing required field: audio (base64)" }
|
|
520
|
+
}, 400);
|
|
521
|
+
if (typeof body.mimeType !== "string" || !body.mimeType.trim()) return c.json({
|
|
522
|
+
ok: false,
|
|
523
|
+
error: { message: "Missing required field: mimeType" }
|
|
524
|
+
}, 400);
|
|
525
|
+
const encoded = body.audio.replace(/\s/g, "");
|
|
526
|
+
if (encoded.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(encoded)) return c.json({
|
|
527
|
+
ok: false,
|
|
528
|
+
error: { message: "Invalid base64 audio data" }
|
|
529
|
+
}, 400);
|
|
530
|
+
const language = typeof body.language === "string" && body.language.trim() ? body.language.trim() : void 0;
|
|
531
|
+
const fileName = typeof body.fileName === "string" && body.fileName.trim() ? body.fileName.trim() : `audio-${Date.now()}`;
|
|
532
|
+
return runTranscription(c, {
|
|
533
|
+
audioBuffer: Buffer.from(encoded, "base64"),
|
|
534
|
+
mimeType: body.mimeType.trim(),
|
|
535
|
+
fileName,
|
|
536
|
+
...language ? { language } : {}
|
|
311
537
|
});
|
|
312
538
|
});
|
|
313
539
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice.js","names":[],"sources":["../../../../../src/gateway/hono/routes/voice.ts"],"sourcesContent":["/**\n * Voice routes — POST /api/voice/transcribe\n *\n * Single endpoint that:\n * 1. Runs STT (Whisper preferred for low latency, Alibaba fallback)\n * 2. Optionally runs LLM refine on the raw transcript\n * 3. Returns { raw, refined?, language }\n *\n * LLM refine is auto-applied when a model is resolvable; gracefully degrades\n * to raw-only when no LLM is configured.\n */\n\nimport type { Hono } from 'hono';\nimport { type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport type { Config } from '../../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../../providers/index.js';\nimport { completeWithResolvedCredentials } from '../../../providers/model-call.js';\nimport { isSTTAvailable, transcribe } from '../../../voice/stt/index.js';\nimport { isTTSAvailable, mergeTtsConfigFromAppConfig, speak } from '../../../voice/tts/index.js';\nimport { listTtsProvidersForApi } from '../../../voice/tts/list-providers.js';\nimport { listSttProvidersForApi } from '../../../voice/stt/list-providers.js';\nimport { mergeSttConfigFromAppConfig } from '../../../channels/attachments/voice-stt-webchat.js';\nimport { resolveSttProviderConfigSlice } from '../../../voice/stt/config-slice.js';\nimport { resolveTtsProviderConfigSlice } from '../../../voice/tts/config-slice.js';\nimport { createGatewayRouteLogger } from '../lib/route-logger.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst log = createGatewayRouteLogger('Voice');\n\nfunction readVoiceApiKeyFromConfigFileOnly(\n cfg: Config,\n kind: 'stt' | 'tts',\n providerId: string,\n): string | undefined {\n const id = providerId.trim();\n if (!id) return undefined;\n const slice =\n kind === 'stt'\n ? resolveSttProviderConfigSlice(id, cfg.tools?.media?.audio)\n : resolveTtsProviderConfigSlice(id, cfg.messages?.tts);\n const key = slice.apiKey;\n return typeof key === 'string' && key.trim() ? key.trim() : undefined;\n}\n\nconst REFINE_TIMEOUT_MS = 15_000;\nconst MAX_AUDIO_BYTES = 25 * 1024 * 1024; // 25 MB\n\nconst REFINE_SYSTEM_PROMPT = `你是语音转文字后处理助手。将语音转写原文整理为高质量文本输入。\n\n规则:\n1. 修正明显的语音识别错误\n2. 添加正确的标点符号\n3. 去除口语赘词(嗯、啊、那个、就是说、然后就是)\n4. 保持原意不变,不要扩写或改变语义\n5. 如果原文已经很好,原样输出\n6. 只输出整理后的文字,不要解释`;\n\nfunction resolveRefineModel(config: Config | undefined): ReturnType<typeof resolveModel> | null {\n const envRef = process.env.XOPC_VOICE_REFINE_MODEL?.trim();\n if (envRef) {\n try {\n return resolveModel(envRef);\n } catch { /* fall through */ }\n }\n for (const candidate of ['openai/gpt-5.6-luna', 'google/gemini-3.5-flash']) {\n try {\n return resolveModel(candidate);\n } catch { /* next */ }\n }\n try {\n return resolveModel(getDefaultModelSync(config));\n } catch {\n return null;\n }\n}\n\nasync function refineTranscript(\n raw: string,\n config: Config | undefined,\n signal?: AbortSignal,\n): Promise<string | undefined> {\n if (!raw.trim()) return undefined;\n\n const model = resolveRefineModel(config);\n if (!model) {\n log.debug('No LLM model available for voice refine; returning raw only');\n return undefined;\n }\n\n try {\n const userMsg: UserMessage = {\n role: 'user',\n content: `${REFINE_SYSTEM_PROMPT}\\n\\n原文:${raw}`,\n timestamp: Date.now(),\n };\n\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(REFINE_TIMEOUT_MS)\n : undefined;\n const mergedSignal =\n signal && timeoutSignal && typeof AbortSignal.any === 'function'\n ? AbortSignal.any([signal, timeoutSignal])\n : signal ?? timeoutSignal;\n\n const result = await completeWithResolvedCredentials(\n model,\n { messages: [userMsg] },\n {\n maxTokens: Math.min(raw.length * 3, 4096),\n temperature: 0.2,\n signal: mergedSignal as AbortSignal,\n },\n );\n\n let out = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n out += String((c as { text?: string }).text || '');\n }\n }\n }\n\n const refined = out.trim();\n if (!refined || refined === raw.trim()) return undefined;\n return refined;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.warn({ errorMessage: msg }, 'Voice refine failed; returning raw only');\n return undefined;\n }\n}\n\nexport function registerVoiceRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n /**\n * GET /api/voice/providers\n *\n * Lists registered SpeechProviderPlugin ids with configured state for the\n * current gateway config (OpenClaw `tts.providers` equivalent).\n */\n authenticated.get('/api/voice/providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listTtsProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n /**\n * GET /api/voice/stt-providers\n *\n * Lists registered MediaUnderstandingProvider ids with configured state for\n * the current gateway config (OpenClaw `tools.media.audio.providers` equivalent).\n */\n authenticated.get('/api/voice/stt-providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listSttProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n /**\n * POST /api/voice/reveal-api-key — return plaintext voice provider apiKey from config file only.\n * Body: `{ kind: 'stt' | 'tts', provider: string }`\n */\n authenticated.post('/api/voice/reveal-api-key', strictRateLimitMiddleware, async (c) => {\n let body: { kind?: unknown; provider?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n const kind = body.kind === 'stt' || body.kind === 'tts' ? body.kind : null;\n const provider = typeof body.provider === 'string' ? body.provider.trim() : '';\n if (!kind) {\n return c.json({ ok: false, error: { message: 'kind must be \"stt\" or \"tts\"' } }, 400);\n }\n if (!provider) {\n return c.json({ ok: false, error: { message: 'provider is required' } }, 400);\n }\n\n const cfg = service.currentConfig as Config;\n const apiKey = readVoiceApiKeyFromConfigFileOnly(cfg, kind, provider);\n return c.json({\n ok: true,\n payload: {\n kind,\n provider,\n apiKey: apiKey ?? null,\n source: apiKey ? ('config' as const) : ('none' as const),\n },\n });\n });\n\n /**\n * POST /api/voice/tts-test\n *\n * Body: { text: string, provider?: string, model?: string, voice?: string }\n * Response: { ok: true, payload: { audio: string, format: string, provider: string } }\n */\n authenticated.post('/api/voice/tts-test', strictRateLimitMiddleware, async (c) => {\n let body: { text?: unknown; provider?: unknown; model?: unknown; voice?: unknown; providerConfig?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n\n const text = typeof body.text === 'string' ? body.text.trim() : '';\n if (!text) {\n return c.json({ ok: false, error: { message: 'text is required' } }, 400);\n }\n if (text.length > 1000) {\n return c.json({ ok: false, error: { message: 'text exceeds 1000 characters' } }, 400);\n }\n\n const config = service.currentConfig as Config;\n const baseTtsConfig = mergeTtsConfigFromAppConfig(config.messages?.tts);\n const provider = typeof body.provider === 'string' && body.provider.trim() ? body.provider.trim() : baseTtsConfig.provider;\n const providerConfig =\n body.providerConfig && typeof body.providerConfig === 'object' && !Array.isArray(body.providerConfig)\n ? (body.providerConfig as Record<string, unknown>)\n : undefined;\n const ttsConfig = {\n ...baseTtsConfig,\n enabled: true,\n provider,\n providers: {\n ...(baseTtsConfig.providers ?? {}),\n ...(providerConfig ? { [provider]: { ...(baseTtsConfig.providers?.[provider] ?? {}), ...providerConfig } } : {}),\n },\n fallback: { enabled: false, order: [provider] },\n };\n\n if (!isTTSAvailable(ttsConfig)) {\n return c.json({\n ok: false,\n error: { message: `TTS provider \"${provider}\" is not configured.` },\n }, 503);\n }\n\n try {\n const startedAt = Date.now();\n const result = await speak(text, ttsConfig, {\n appConfig: config,\n parseDirectives: false,\n tts: {\n ...(typeof body.model === 'string' && body.model.trim() ? { model: body.model.trim() } : {}),\n ...(typeof body.voice === 'string' && body.voice.trim() ? { voice: body.voice.trim() } : {}),\n },\n });\n if (!result.audio.length) {\n throw new Error(`TTS provider \"${result.provider}\" returned empty audio`);\n }\n const mimeType =\n result.format === 'opus' || result.format === 'ogg'\n ? 'audio/ogg'\n : result.format === 'mp3' || result.format === 'mpeg'\n ? 'audio/mpeg'\n : result.format === 'wav'\n ? 'audio/wav'\n : `audio/${result.format}`;\n return c.json({\n ok: true,\n payload: {\n audio: result.audio.toString('base64'),\n mimeType,\n format: result.format,\n provider: result.provider,\n latencyMs: Date.now() - startedAt,\n audioSize: result.audio.length,\n },\n });\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.error({ errorMessage: msg, provider }, 'Voice TTS test failed');\n return c.json({ ok: false, error: { message: `TTS test failed: ${msg}` } }, 502);\n }\n });\n\n /**\n * POST /api/voice/transcribe\n *\n * Body: { audio: string (base64), mimeType: string, language?: string }\n * Response: { ok: true, payload: { raw: string, refined?: string, language?: string } }\n */\n authenticated.post('/api/voice/transcribe', async (c) => {\n let body: { audio?: string; mimeType?: string; language?: string } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n\n const { audio, mimeType, language } = body;\n if (!audio || typeof audio !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing required field: audio (base64)' } }, 400);\n }\n if (!mimeType || typeof mimeType !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing required field: mimeType' } }, 400);\n }\n\n // Decode base64 audio\n let audioBuffer: Buffer;\n try {\n audioBuffer = Buffer.from(audio, 'base64');\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid base64 audio data' } }, 400);\n }\n\n if (audioBuffer.length === 0) {\n return c.json({ ok: false, error: { message: 'Empty audio data' } }, 400);\n }\n if (audioBuffer.length > MAX_AUDIO_BYTES) {\n return c.json({ ok: false, error: { message: 'Audio data exceeds 25 MB limit' } }, 400);\n }\n\n // Resolve STT config from app config\n const config = service.currentConfig as Config;\n const sttConfigRaw = config.tools?.media?.audio;\n const sttConfig = mergeSttConfigFromAppConfig(sttConfigRaw, config.tools?.media);\n\n if (!isSTTAvailable(sttConfig)) {\n return c.json({\n ok: false,\n error: { message: 'STT is not configured. Enable STT in gateway config (tools.media.audio).' },\n }, 503);\n }\n\n // Run STT\n let raw: string;\n let detectedLanguage: string | undefined;\n try {\n const result = await transcribe(audioBuffer, sttConfig, {\n language: language || (sttConfig.provider === 'alibaba' ? 'zh' : undefined),\n });\n raw = result.text;\n detectedLanguage = result.language ?? language;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.error({ errorMessage: msg }, 'Voice transcription failed');\n return c.json({ ok: false, error: { message: `Transcription failed: ${msg}` } }, 502);\n }\n\n if (!raw.trim()) {\n return c.json({\n ok: true,\n payload: { raw: '', language: detectedLanguage },\n });\n }\n\n // Run LLM refine (auto, best-effort)\n const refined = await refineTranscript(raw, config);\n\n return c.json({\n ok: true,\n payload: {\n raw,\n ...(refined ? { refined } : {}),\n ...(detectedLanguage ? { language: detectedLanguage } : {}),\n },\n });\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"voice.js","names":[],"sources":["../../../../../src/gateway/hono/routes/voice.ts"],"sourcesContent":["/**\n * Voice routes — POST /api/voice/transcriptions (multipart) and legacy\n * POST /api/voice/transcribe (JSON/base64).\n *\n * The endpoints share one execution path that:\n * 1. Runs the configured STT provider and fallback chain\n * 2. Optionally runs LLM refine on the raw transcript\n * 3. Returns { raw, refined?, language }\n *\n * LLM refinement is opt-in through `voice.input.refinement`.\n */\n\nimport type { Context, Hono } from 'hono';\nimport { type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport type { Config } from '../../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../../providers/index.js';\nimport { completeWithResolvedCredentials } from '../../../providers/model-call.js';\nimport { isSTTAvailable, transcribe } from '../../../voice/stt/index.js';\nimport { isTTSAvailable, mergeTtsConfigFromAppConfig, speak } from '../../../voice/tts/index.js';\nimport { listTtsProvidersForApi } from '../../../voice/tts/list-providers.js';\nimport { listSttProvidersForApi } from '../../../voice/stt/list-providers.js';\nimport { mergeSttConfigFromAppConfig } from '../../../channels/attachments/voice-stt-webchat.js';\nimport { resolveSttProviderConfigSlice } from '../../../voice/stt/config-slice.js';\nimport { resolveTtsProviderConfigSlice } from '../../../voice/tts/config-slice.js';\nimport {\n listLocalVoiceModelStatuses,\n removeLocalVoiceModel,\n startLocalVoiceModelInstall,\n} from '../../../voice/local/model-manager.js';\nimport { getLocalVoiceRuntimeClient } from '../../../voice/local/runtime-client.js';\nimport { createGatewayRouteLogger } from '../lib/route-logger.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst log = createGatewayRouteLogger('Voice');\n\nfunction readVoiceApiKeyFromConfigFileOnly(\n cfg: Config,\n kind: 'stt' | 'tts',\n providerId: string,\n): string | undefined {\n const id = providerId.trim();\n if (!id) return undefined;\n const slice =\n kind === 'stt'\n ? resolveSttProviderConfigSlice(id, cfg.tools?.media?.audio)\n : resolveTtsProviderConfigSlice(id, cfg.messages?.tts);\n const key = slice.apiKey;\n return typeof key === 'string' && key.trim() ? key.trim() : undefined;\n}\n\nconst REFINE_TIMEOUT_MS = 15_000;\nconst MAX_AUDIO_BYTES = 25 * 1024 * 1024; // 25 MB\nconst MAX_SPEECH_TEXT_LENGTH = 1_200;\n\nfunction speechMimeType(format: string): string {\n if (format === 'opus' || format === 'ogg') return 'audio/ogg';\n if (format === 'mp3' || format === 'mpeg') return 'audio/mpeg';\n if (format === 'wav') return 'audio/wav';\n return `audio/${format}`;\n}\n\nconst LIGHT_REFINE_PROMPT = `你是语音转文字后处理助手。将语音转写原文整理为高质量文本输入。\n\n规则:\n1. 修正明显的语音识别错误\n2. 添加正确的标点符号\n3. 去除口语赘词(嗯、啊、那个、就是说、然后就是)\n4. 保持原意不变,不要扩写或改变语义\n5. 如果原文已经很好,原样输出\n6. 只输出整理后的文字,不要解释`;\n\nconst PUNCTUATION_REFINE_PROMPT = `为语音转写原文补充标点和合理分段。不要删词、改写、扩写或改变语义。只输出处理后的文字。`;\n\nfunction resolveRefineModel(\n config: Config | undefined,\n configuredRef?: string,\n): ReturnType<typeof resolveModel> | null {\n if (configuredRef) {\n try {\n return resolveModel(configuredRef);\n } catch {\n log.warn({ modelRef: configuredRef }, 'Configured voice refinement model is invalid');\n return null;\n }\n }\n const envRef = process.env.XOPC_VOICE_REFINE_MODEL?.trim();\n if (envRef) {\n try {\n return resolveModel(envRef);\n } catch { /* fall through */ }\n }\n for (const candidate of ['openai/gpt-5.6-luna', 'google/gemini-3.5-flash']) {\n try {\n return resolveModel(candidate);\n } catch { /* next */ }\n }\n try {\n return resolveModel(getDefaultModelSync(config));\n } catch {\n return null;\n }\n}\n\nasync function refineTranscript(\n raw: string,\n config: Config | undefined,\n signal?: AbortSignal,\n): Promise<string | undefined> {\n if (!raw.trim()) return undefined;\n\n const refinement = config?.voice?.input?.refinement;\n const mode = refinement?.mode ?? 'off';\n if (mode === 'off') return undefined;\n\n const instruction =\n mode === 'punctuation'\n ? PUNCTUATION_REFINE_PROMPT\n : mode === 'custom'\n ? refinement?.customInstruction?.trim()\n : LIGHT_REFINE_PROMPT;\n if (!instruction) {\n log.warn('Custom voice refinement is enabled without an instruction; returning raw only');\n return undefined;\n }\n\n const model = resolveRefineModel(config, refinement?.model);\n if (!model) {\n log.debug('No LLM model available for voice refine; returning raw only');\n return undefined;\n }\n\n try {\n const userMsg: UserMessage = {\n role: 'user',\n content: `${instruction}\\n\\n原文:${raw}`,\n timestamp: Date.now(),\n };\n\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(REFINE_TIMEOUT_MS)\n : undefined;\n const mergedSignal =\n signal && timeoutSignal && typeof AbortSignal.any === 'function'\n ? AbortSignal.any([signal, timeoutSignal])\n : signal ?? timeoutSignal;\n\n const result = await completeWithResolvedCredentials(\n model,\n { messages: [userMsg] },\n {\n maxTokens: Math.min(raw.length * 3, 4096),\n temperature: 0.2,\n signal: mergedSignal as AbortSignal,\n },\n );\n\n let out = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n out += String((c as { text?: string }).text || '');\n }\n }\n }\n\n const refined = out.trim();\n if (!refined || refined === raw.trim()) return undefined;\n return refined;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.warn({ errorMessage: msg }, 'Voice refine failed; returning raw only');\n return undefined;\n }\n}\n\nexport function registerVoiceRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n authenticated.post('/api/voice/language', strictRateLimitMiddleware, async (c) => {\n let body: { language?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n if (body.language !== 'en' && body.language !== 'zh') {\n return c.json({ ok: false, error: { message: 'language must be \"en\" or \"zh\"' } }, 400);\n }\n const payload = await service.syncVoiceLanguage(body.language);\n if (payload.error) {\n return c.json({ ok: false, error: { message: payload.error } }, 500);\n }\n return c.json({ ok: true, payload });\n });\n\n /**\n * GET /api/voice/providers\n *\n * Lists registered SpeechProviderPlugin ids with configured state for the\n * current gateway config (OpenClaw `tts.providers` equivalent).\n */\n authenticated.get('/api/voice/providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listTtsProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n /**\n * GET /api/voice/stt-providers\n *\n * Lists registered MediaUnderstandingProvider ids with configured state for\n * the current gateway config (OpenClaw `tools.media.audio.providers` equivalent).\n */\n authenticated.get('/api/voice/stt-providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listSttProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n authenticated.get('/api/voice/local/status', async (c) => {\n const models = await listLocalVoiceModelStatuses();\n try {\n const runtime = await getLocalVoiceRuntimeClient().request<{\n ok: boolean;\n protocolVersion: number;\n engine: string;\n }>('health', {}, { timeoutMs: 10_000, stopOnTimeout: false });\n return c.json({ ok: true, payload: { runtime: { ...runtime, ready: true }, models } });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({\n ok: true,\n payload: { runtime: { ready: false, error: message }, models },\n });\n }\n });\n\n authenticated.post(\n '/api/voice/local/models/:modelId/install',\n strictRateLimitMiddleware,\n (c) => {\n try {\n const status = startLocalVoiceModelInstall(c.req.param('modelId'));\n return c.json({ ok: true, payload: { model: status } }, 202);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ ok: false, error: { message } }, 400);\n }\n },\n );\n\n authenticated.delete(\n '/api/voice/local/models/:modelId',\n strictRateLimitMiddleware,\n async (c) => {\n try {\n await removeLocalVoiceModel(c.req.param('modelId'));\n return c.json({ ok: true });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ ok: false, error: { message } }, 400);\n }\n },\n );\n\n /**\n * POST /api/voice/reveal-api-key — return plaintext voice provider apiKey from config file only.\n * Body: `{ kind: 'stt' | 'tts', provider: string }`\n */\n authenticated.post('/api/voice/reveal-api-key', strictRateLimitMiddleware, async (c) => {\n let body: { kind?: unknown; provider?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n const kind = body.kind === 'stt' || body.kind === 'tts' ? body.kind : null;\n const provider = typeof body.provider === 'string' ? body.provider.trim() : '';\n if (!kind) {\n return c.json({ ok: false, error: { message: 'kind must be \"stt\" or \"tts\"' } }, 400);\n }\n if (!provider) {\n return c.json({ ok: false, error: { message: 'provider is required' } }, 400);\n }\n\n const cfg = service.currentConfig as Config;\n const apiKey = readVoiceApiKeyFromConfigFileOnly(cfg, kind, provider);\n return c.json({\n ok: true,\n payload: {\n kind,\n provider,\n apiKey: apiKey ?? null,\n source: apiKey ? ('config' as const) : ('none' as const),\n },\n });\n });\n\n /** Generate one bounded read-aloud chunk as binary audio. */\n authenticated.post('/api/voice/speech', strictRateLimitMiddleware, async (c) => {\n let body: { text?: unknown; language?: unknown; voice?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n\n const text = typeof body.text === 'string' ? body.text.trim() : '';\n if (!text) {\n return c.json({ ok: false, error: { message: 'text is required' } }, 400);\n }\n if (text.length > MAX_SPEECH_TEXT_LENGTH) {\n return c.json({ ok: false, error: { message: `text exceeds ${MAX_SPEECH_TEXT_LENGTH} characters` } }, 400);\n }\n\n const language = typeof body.language === 'string' ? body.language.trim() : '';\n const voice = typeof body.voice === 'string' ? body.voice.trim() : '';\n if (language && !/^[a-z]{2,3}(?:-[A-Z]{2})?$/.test(language)) {\n return c.json({ ok: false, error: { message: 'language must be a valid locale' } }, 400);\n }\n if (voice.length > 100) {\n return c.json({ ok: false, error: { message: 'voice exceeds 100 characters' } }, 400);\n }\n\n const config = service.currentConfig as Config;\n const baseTtsConfig = mergeTtsConfigFromAppConfig(config.messages?.tts);\n const provider = baseTtsConfig.provider;\n const languageVoice = language.startsWith('en')\n ? 'en-US-MichelleNeural'\n : language.startsWith('zh')\n ? 'zh-CN-XiaoxiaoNeural'\n : undefined;\n const providerOverride = provider === 'edge'\n ? {\n ...(language ? { lang: language } : {}),\n ...(voice || languageVoice ? { voice: voice || languageVoice } : {}),\n }\n : {};\n const ttsConfig = {\n ...baseTtsConfig,\n enabled: true,\n providers: {\n ...(baseTtsConfig.providers ?? {}),\n [provider]: {\n ...(baseTtsConfig.providers?.[provider] ?? {}),\n ...providerOverride,\n },\n },\n };\n\n if (!isTTSAvailable(ttsConfig)) {\n return c.json({ ok: false, error: { message: `TTS provider \\\"${provider}\\\" is not configured.` } }, 503);\n }\n\n try {\n const result = await speak(text, ttsConfig, {\n appConfig: config,\n parseDirectives: false,\n signal: c.req.raw.signal,\n tts: voice ? { voice } : undefined,\n });\n if (!result.audio.length) {\n throw new Error(`TTS provider \\\"${result.provider}\\\" returned empty audio`);\n }\n return new Response(new Uint8Array(result.audio), {\n headers: {\n 'Content-Type': speechMimeType(result.format),\n 'Content-Length': String(result.audio.length),\n 'Cache-Control': 'private, no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'X-Voice-Provider': result.provider,\n ...(language ? { 'X-Voice-Language': language } : {}),\n },\n });\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n if (!c.req.raw.signal.aborted) {\n log.error({ err: error, provider, textLength: text.length }, `Voice speech generation failed: ${msg}`);\n }\n return c.json({ ok: false, error: { message: `Speech generation failed: ${msg}` } }, 502);\n }\n });\n\n /**\n * POST /api/voice/tts-test\n *\n * Body: { text: string, provider?: string, model?: string, voice?: string }\n * Response: { ok: true, payload: { audio: string, format: string, provider: string } }\n */\n authenticated.post('/api/voice/tts-test', strictRateLimitMiddleware, async (c) => {\n let body: { text?: unknown; provider?: unknown; model?: unknown; voice?: unknown; providerConfig?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n\n const text = typeof body.text === 'string' ? body.text.trim() : '';\n if (!text) {\n return c.json({ ok: false, error: { message: 'text is required' } }, 400);\n }\n if (text.length > 1000) {\n return c.json({ ok: false, error: { message: 'text exceeds 1000 characters' } }, 400);\n }\n\n const config = service.currentConfig as Config;\n const baseTtsConfig = mergeTtsConfigFromAppConfig(config.messages?.tts);\n const provider = typeof body.provider === 'string' && body.provider.trim() ? body.provider.trim() : baseTtsConfig.provider;\n const providerConfig =\n body.providerConfig && typeof body.providerConfig === 'object' && !Array.isArray(body.providerConfig)\n ? (body.providerConfig as Record<string, unknown>)\n : undefined;\n const ttsConfig = {\n ...baseTtsConfig,\n enabled: true,\n provider,\n providers: {\n ...(baseTtsConfig.providers ?? {}),\n ...(providerConfig ? { [provider]: { ...(baseTtsConfig.providers?.[provider] ?? {}), ...providerConfig } } : {}),\n },\n fallback: { enabled: false, order: [provider] },\n };\n\n if (!isTTSAvailable(ttsConfig)) {\n return c.json({\n ok: false,\n error: { message: `TTS provider \"${provider}\" is not configured.` },\n }, 503);\n }\n\n try {\n const startedAt = Date.now();\n const result = await speak(text, ttsConfig, {\n appConfig: config,\n parseDirectives: false,\n tts: {\n ...(typeof body.model === 'string' && body.model.trim() ? { model: body.model.trim() } : {}),\n ...(typeof body.voice === 'string' && body.voice.trim() ? { voice: body.voice.trim() } : {}),\n },\n });\n if (!result.audio.length) {\n throw new Error(`TTS provider \"${result.provider}\" returned empty audio`);\n }\n const mimeType =\n result.format === 'opus' || result.format === 'ogg'\n ? 'audio/ogg'\n : result.format === 'mp3' || result.format === 'mpeg'\n ? 'audio/mpeg'\n : result.format === 'wav'\n ? 'audio/wav'\n : `audio/${result.format}`;\n return c.json({\n ok: true,\n payload: {\n audio: result.audio.toString('base64'),\n mimeType,\n format: result.format,\n provider: result.provider,\n latencyMs: Date.now() - startedAt,\n audioSize: result.audio.length,\n },\n });\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.error({ errorMessage: msg, provider }, 'Voice TTS test failed');\n return c.json({ ok: false, error: { message: `TTS test failed: ${msg}` } }, 502);\n }\n });\n\n type TranscriptionInput = {\n audioBuffer: Buffer;\n mimeType: string;\n fileName: string;\n language?: string;\n };\n\n const runTranscription = async (c: Context, input: TranscriptionInput) => {\n if (!input.audioBuffer.length) {\n return c.json({ ok: false, error: { message: 'Empty audio data' } }, 400);\n }\n if (input.audioBuffer.length > MAX_AUDIO_BYTES) {\n return c.json({ ok: false, error: { message: 'Audio data exceeds 25 MB limit' } }, 400);\n }\n if (!input.mimeType.toLowerCase().startsWith('audio/')) {\n return c.json({ ok: false, error: { message: 'Uploaded file must have an audio MIME type' } }, 400);\n }\n\n const config = service.currentConfig as Config;\n const sttConfig = mergeSttConfigFromAppConfig(\n config.tools?.media?.audio,\n config.tools?.media,\n );\n if (!isSTTAvailable(sttConfig)) {\n return c.json({\n ok: false,\n error: { message: 'STT is not configured. Enable STT in gateway config (tools.media.audio).' },\n }, 503);\n }\n\n const startedAt = Date.now();\n try {\n const result = await transcribe(input.audioBuffer, sttConfig, {\n language: input.language || (sttConfig.provider === 'alibaba' ? 'zh' : undefined),\n mime: input.mimeType,\n fileName: input.fileName,\n signal: c.req.raw.signal,\n });\n const raw = result.text;\n const detectedLanguage = result.language ?? input.language;\n const refined = raw.trim()\n ? await refineTranscript(raw, config, c.req.raw.signal)\n : undefined;\n return c.json({\n ok: true,\n payload: {\n raw,\n ...(refined ? { refined } : {}),\n ...(detectedLanguage ? { language: detectedLanguage } : {}),\n provider: result.provider,\n attempts: result.attempts,\n latencyMs: Date.now() - startedAt,\n },\n });\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n if (!c.req.raw.signal.aborted) {\n log.error({ err: error, mimeType: input.mimeType, fileName: input.fileName }, `Voice transcription failed: ${msg}`);\n }\n return c.json({ ok: false, error: { message: `Transcription failed: ${msg}` } }, 502);\n }\n };\n\n /** Multipart endpoint used by browser and future native clients. */\n authenticated.post('/api/voice/transcriptions', strictRateLimitMiddleware, async (c) => {\n const contentLength = Number(c.req.header('content-length') ?? 0);\n if (Number.isFinite(contentLength) && contentLength > MAX_AUDIO_BYTES + 1024 * 1024) {\n return c.json({ ok: false, error: { message: 'Audio data exceeds 25 MB limit' } }, 413);\n }\n let form: FormData;\n try {\n form = await c.req.formData();\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid multipart form data' } }, 400);\n }\n const audio = form.get('audio');\n if (!(audio instanceof Blob)) {\n return c.json({ ok: false, error: { message: 'Missing required file field: audio' } }, 400);\n }\n const languageValue = form.get('language');\n const language = typeof languageValue === 'string' && languageValue.trim()\n ? languageValue.trim()\n : undefined;\n const fileName = 'name' in audio && typeof audio.name === 'string' && audio.name.trim()\n ? audio.name.trim()\n : `audio-${Date.now()}`;\n return runTranscription(c, {\n audioBuffer: Buffer.from(await audio.arrayBuffer()),\n mimeType: audio.type || 'audio/webm',\n fileName,\n ...(language ? { language } : {}),\n });\n });\n\n /** Legacy JSON/base64 endpoint retained for mobile and external clients. */\n authenticated.post('/api/voice/transcribe', strictRateLimitMiddleware, async (c) => {\n let body: { audio?: unknown; mimeType?: unknown; language?: unknown; fileName?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n if (typeof body.audio !== 'string' || !body.audio.trim()) {\n return c.json({ ok: false, error: { message: 'Missing required field: audio (base64)' } }, 400);\n }\n if (typeof body.mimeType !== 'string' || !body.mimeType.trim()) {\n return c.json({ ok: false, error: { message: 'Missing required field: mimeType' } }, 400);\n }\n const encoded = body.audio.replace(/\\s/g, '');\n if (encoded.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(encoded)) {\n return c.json({ ok: false, error: { message: 'Invalid base64 audio data' } }, 400);\n }\n const language = typeof body.language === 'string' && body.language.trim()\n ? body.language.trim()\n : undefined;\n const fileName = typeof body.fileName === 'string' && body.fileName.trim()\n ? body.fileName.trim()\n : `audio-${Date.now()}`;\n return runTranscription(c, {\n audioBuffer: Buffer.from(encoded, 'base64'),\n mimeType: body.mimeType.trim(),\n fileName,\n ...(language ? { language } : {}),\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;gBAgBgF;AAkBhF,MAAM,MAAM,yBAAyB,QAAQ;AAE7C,SAAS,kCACP,KACA,MACA,YACoB;CACpB,MAAM,KAAK,WAAW,MAAM;AAC5B,KAAI,CAAC,GAAI,QAAO,KAAA;CAKhB,MAAM,OAHJ,SAAS,QACL,8BAA8B,IAAI,IAAI,OAAO,OAAO,MAAM,GAC1D,8BAA8B,IAAI,IAAI,UAAU,IAAI,EACxC;AAClB,QAAO,OAAO,QAAQ,YAAY,IAAI,MAAM,GAAG,IAAI,MAAM,GAAG,KAAA;;AAG9D,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB,KAAK,OAAO;AACpC,MAAM,yBAAyB;AAE/B,SAAS,eAAe,QAAwB;AAC9C,KAAI,WAAW,UAAU,WAAW,MAAO,QAAO;AAClD,KAAI,WAAW,SAAS,WAAW,OAAQ,QAAO;AAClD,KAAI,WAAW,MAAO,QAAO;AAC7B,QAAO,SAAS;;AAGlB,MAAM,sBAAsB;;;;;;;;;AAU5B,MAAM,4BAA4B;AAElC,SAAS,mBACP,QACA,eACwC;AACxC,KAAI,cACF,KAAI;AACF,SAAO,aAAa,cAAc;SAC5B;AACN,MAAI,KAAK,EAAE,UAAU,eAAe,EAAE,+CAA+C;AACrF,SAAO;;CAGX,MAAM,SAAS,QAAQ,IAAI,yBAAyB,MAAM;AAC1D,KAAI,OACF,KAAI;AACF,SAAO,aAAa,OAAO;SACrB;AAEV,MAAK,MAAM,aAAa,CAAC,uBAAuB,0BAA0B,CACxE,KAAI;AACF,SAAO,aAAa,UAAU;SACxB;AAEV,KAAI;AACF,SAAO,aAAa,oBAAoB,OAAO,CAAC;SAC1C;AACN,SAAO;;;AAIX,eAAe,iBACb,KACA,QACA,QAC6B;AAC7B,KAAI,CAAC,IAAI,MAAM,CAAE,QAAO,KAAA;CAExB,MAAM,aAAa,QAAQ,OAAO,OAAO;CACzC,MAAM,OAAO,YAAY,QAAQ;AACjC,KAAI,SAAS,MAAO,QAAO,KAAA;CAE3B,MAAM,cACJ,SAAS,gBACL,4BACA,SAAS,WACP,YAAY,mBAAmB,MAAM,GACrC;AACR,KAAI,CAAC,aAAa;AAChB,MAAI,KAAK,gFAAgF;AACzF;;CAGF,MAAM,QAAQ,mBAAmB,QAAQ,YAAY,MAAM;AAC3D,KAAI,CAAC,OAAO;AACV,MAAI,MAAM,8DAA8D;AACxE;;AAGF,KAAI;EACF,MAAM,UAAuB;GAC3B,MAAM;GACN,SAAS,GAAG,YAAY,SAAS;GACjC,WAAW,KAAK,KAAK;GACtB;EAED,MAAM,gBACJ,OAAO,gBAAgB,eAAe,OAAO,YAAY,YAAY,aACjE,YAAY,QAAQ,kBAAkB,GACtC,KAAA;EACN,MAAM,eACJ,UAAU,iBAAiB,OAAO,YAAY,QAAQ,aAClD,YAAY,IAAI,CAAC,QAAQ,cAAc,CAAC,GACxC,UAAU;EAEhB,MAAM,SAAS,MAAM,gCACnB,OACA,EAAE,UAAU,CAAC,QAAQ,EAAE,EACvB;GACE,WAAW,KAAK,IAAI,IAAI,SAAS,GAAG,KAAK;GACzC,aAAa;GACb,QAAQ;GACT,CACF;EAED,IAAI,MAAM;AACV,MAAI,MAAM,QAAQ,OAAO,QAAQ;QAC1B,MAAM,KAAK,OAAO,QACrB,KAAI,KAAK,OAAO,MAAM,YAAa,EAAwB,SAAS,OAClE,QAAO,OAAQ,EAAwB,QAAQ,GAAG;;EAKxD,MAAM,UAAU,IAAI,MAAM;AAC1B,MAAI,CAAC,WAAW,YAAY,IAAI,MAAM,CAAE,QAAO,KAAA;AAC/C,SAAO;UACA,OAAO;EACd,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAClE,MAAI,KAAK,EAAE,cAAc,KAAK,EAAE,0CAA0C;AAC1E;;;AAIJ,SAAgB,oBAAoB,eAAqB,MAAoC;CAC3F,MAAM,EAAE,SAAS,8BAA8B;AAE/C,eAAc,KAAK,uBAAuB,2BAA2B,OAAO,MAAM;EAChF,IAAI,OAA+B,EAAE;AACrC,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,qBAAqB;IAAE,EAAE,IAAI;;AAE5E,MAAI,KAAK,aAAa,QAAQ,KAAK,aAAa,KAC9C,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,qCAAiC;GAAE,EAAE,IAAI;EAExF,MAAM,UAAU,MAAM,QAAQ,kBAAkB,KAAK,SAAS;AAC9D,MAAI,QAAQ,MACV,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,QAAQ,OAAO;GAAE,EAAE,IAAI;AAEtE,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM;GAAS,CAAC;GACpC;;;;;;;AAQF,eAAc,IAAI,yBAAyB,MAAM;EAC/C,MAAM,SAAS,QAAQ;EACvB,MAAM,UAAU,uBAAuB,OAAO;AAC9C,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM;GAAS,CAAC;GACpC;;;;;;;AAQF,eAAc,IAAI,6BAA6B,MAAM;EACnD,MAAM,SAAS,QAAQ;EACvB,MAAM,UAAU,uBAAuB,OAAO;AAC9C,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM;GAAS,CAAC;GACpC;AAEF,eAAc,IAAI,2BAA2B,OAAO,MAAM;EACxD,MAAM,SAAS,MAAM,6BAA6B;AAClD,MAAI;GACF,MAAM,UAAU,MAAM,4BAA4B,CAAC,QAIhD,UAAU,EAAE,EAAE;IAAE,WAAW;IAAQ,eAAe;IAAO,CAAC;AAC7D,UAAO,EAAE,KAAK;IAAE,IAAI;IAAM,SAAS;KAAE,SAAS;MAAE,GAAG;MAAS,OAAO;MAAM;KAAE;KAAQ;IAAE,CAAC;WAC/E,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,UAAO,EAAE,KAAK;IACZ,IAAI;IACJ,SAAS;KAAE,SAAS;MAAE,OAAO;MAAO,OAAO;MAAS;KAAE;KAAQ;IAC/D,CAAC;;GAEJ;AAEF,eAAc,KACZ,4CACA,4BACC,MAAM;AACL,MAAI;GACF,MAAM,SAAS,4BAA4B,EAAE,IAAI,MAAM,UAAU,CAAC;AAClE,UAAO,EAAE,KAAK;IAAE,IAAI;IAAM,SAAS,EAAE,OAAO,QAAQ;IAAE,EAAE,IAAI;WACrD,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS;IAAE,EAAE,IAAI;;GAG1D;AAED,eAAc,OACZ,oCACA,2BACA,OAAO,MAAM;AACX,MAAI;AACF,SAAM,sBAAsB,EAAE,IAAI,MAAM,UAAU,CAAC;AACnD,UAAO,EAAE,KAAK,EAAE,IAAI,MAAM,CAAC;WACpB,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS;IAAE,EAAE,IAAI;;GAG1D;;;;;AAMD,eAAc,KAAK,6BAA6B,2BAA2B,OAAO,MAAM;EACtF,IAAI,OAA+C,EAAE;AACrD,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,qBAAqB;IAAE,EAAE,IAAI;;EAE5E,MAAM,OAAO,KAAK,SAAS,SAAS,KAAK,SAAS,QAAQ,KAAK,OAAO;EACtE,MAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,SAAS,MAAM,GAAG;AAC5E,MAAI,CAAC,KACH,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,mCAA+B;GAAE,EAAE,IAAI;AAEtF,MAAI,CAAC,SACH,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,wBAAwB;GAAE,EAAE,IAAI;EAG/E,MAAM,MAAM,QAAQ;EACpB,MAAM,SAAS,kCAAkC,KAAK,MAAM,SAAS;AACrE,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IACP;IACA;IACA,QAAQ,UAAU;IAClB,QAAQ,SAAU,WAAsB;IACzC;GACF,CAAC;GACF;;AAGF,eAAc,KAAK,qBAAqB,2BAA2B,OAAO,MAAM;EAC9E,IAAI,OAAgE,EAAE;AACtE,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,qBAAqB;IAAE,EAAE,IAAI;;EAG5E,MAAM,OAAO,OAAO,KAAK,SAAS,WAAW,KAAK,KAAK,MAAM,GAAG;AAChE,MAAI,CAAC,KACH,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,oBAAoB;GAAE,EAAE,IAAI;AAE3E,MAAI,KAAK,SAAS,uBAChB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,gBAAgB,uBAAuB,cAAc;GAAE,EAAE,IAAI;EAG5G,MAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,SAAS,MAAM,GAAG;EAC5E,MAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,MAAM,MAAM,GAAG;AACnE,MAAI,YAAY,CAAC,6BAA6B,KAAK,SAAS,CAC1D,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,mCAAmC;GAAE,EAAE,IAAI;AAE1F,MAAI,MAAM,SAAS,IACjB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,gCAAgC;GAAE,EAAE,IAAI;EAGvF,MAAM,SAAS,QAAQ;EACvB,MAAM,gBAAgB,4BAA4B,OAAO,UAAU,IAAI;EACvE,MAAM,WAAW,cAAc;EAC/B,MAAM,gBAAgB,SAAS,WAAW,KAAK,GAC3C,yBACA,SAAS,WAAW,KAAK,GACvB,yBACA,KAAA;EACN,MAAM,mBAAmB,aAAa,SAClC;GACE,GAAI,WAAW,EAAE,MAAM,UAAU,GAAG,EAAE;GACtC,GAAI,SAAS,gBAAgB,EAAE,OAAO,SAAS,eAAe,GAAG,EAAE;GACpE,GACD,EAAE;EACN,MAAM,YAAY;GAChB,GAAG;GACH,SAAS;GACT,WAAW;IACT,GAAI,cAAc,aAAa,EAAE;KAChC,WAAW;KACV,GAAI,cAAc,YAAY,aAAa,EAAE;KAC7C,GAAG;KACJ;IACF;GACF;AAED,MAAI,CAAC,eAAe,UAAU,CAC5B,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,kBAAkB,SAAS,wBAAwB;GAAE,EAAE,IAAI;AAG1G,MAAI;GACF,MAAM,SAAS,MAAM,MAAM,MAAM,WAAW;IAC1C,WAAW;IACX,iBAAiB;IACjB,QAAQ,EAAE,IAAI,IAAI;IAClB,KAAK,QAAQ,EAAE,OAAO,GAAG,KAAA;IAC1B,CAAC;AACF,OAAI,CAAC,OAAO,MAAM,OAChB,OAAM,IAAI,MAAM,kBAAkB,OAAO,SAAS,yBAAyB;AAE7E,UAAO,IAAI,SAAS,IAAI,WAAW,OAAO,MAAM,EAAE,EAChD,SAAS;IACP,gBAAgB,eAAe,OAAO,OAAO;IAC7C,kBAAkB,OAAO,OAAO,MAAM,OAAO;IAC7C,iBAAiB;IACjB,0BAA0B;IAC1B,oBAAoB,OAAO;IAC3B,GAAI,WAAW,EAAE,oBAAoB,UAAU,GAAG,EAAE;IACrD,EACF,CAAC;WACK,OAAO;GACd,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAClE,OAAI,CAAC,EAAE,IAAI,IAAI,OAAO,QACpB,KAAI,MAAM;IAAE,KAAK;IAAO;IAAU,YAAY,KAAK;IAAQ,EAAE,mCAAmC,MAAM;AAExG,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,6BAA6B,OAAO;IAAE,EAAE,IAAI;;GAE3F;;;;;;;AAQF,eAAc,KAAK,uBAAuB,2BAA2B,OAAO,MAAM;EAChF,IAAI,OAA2G,EAAE;AACjH,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,qBAAqB;IAAE,EAAE,IAAI;;EAG5E,MAAM,OAAO,OAAO,KAAK,SAAS,WAAW,KAAK,KAAK,MAAM,GAAG;AAChE,MAAI,CAAC,KACH,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,oBAAoB;GAAE,EAAE,IAAI;AAE3E,MAAI,KAAK,SAAS,IAChB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,gCAAgC;GAAE,EAAE,IAAI;EAGvF,MAAM,SAAS,QAAQ;EACvB,MAAM,gBAAgB,4BAA4B,OAAO,UAAU,IAAI;EACvE,MAAM,WAAW,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,MAAM,GAAG,KAAK,SAAS,MAAM,GAAG,cAAc;EAClH,MAAM,iBACJ,KAAK,kBAAkB,OAAO,KAAK,mBAAmB,YAAY,CAAC,MAAM,QAAQ,KAAK,eAAe,GAChG,KAAK,iBACN,KAAA;EACN,MAAM,YAAY;GAChB,GAAG;GACH,SAAS;GACT;GACA,WAAW;IACT,GAAI,cAAc,aAAa,EAAE;IACjC,GAAI,iBAAiB,GAAG,WAAW;KAAE,GAAI,cAAc,YAAY,aAAa,EAAE;KAAG,GAAG;KAAgB,EAAE,GAAG,EAAE;IAChH;GACD,UAAU;IAAE,SAAS;IAAO,OAAO,CAAC,SAAS;IAAE;GAChD;AAED,MAAI,CAAC,eAAe,UAAU,CAC5B,QAAO,EAAE,KAAK;GACZ,IAAI;GACJ,OAAO,EAAE,SAAS,iBAAiB,SAAS,uBAAuB;GACpE,EAAE,IAAI;AAGT,MAAI;GACF,MAAM,YAAY,KAAK,KAAK;GAC5B,MAAM,SAAS,MAAM,MAAM,MAAM,WAAW;IAC1C,WAAW;IACX,iBAAiB;IACjB,KAAK;KACH,GAAI,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,MAAM,GAAG,EAAE,OAAO,KAAK,MAAM,MAAM,EAAE,GAAG,EAAE;KAC3F,GAAI,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,MAAM,GAAG,EAAE,OAAO,KAAK,MAAM,MAAM,EAAE,GAAG,EAAE;KAC5F;IACF,CAAC;AACF,OAAI,CAAC,OAAO,MAAM,OAChB,OAAM,IAAI,MAAM,iBAAiB,OAAO,SAAS,wBAAwB;GAE3E,MAAM,WACJ,OAAO,WAAW,UAAU,OAAO,WAAW,QAC1C,cACA,OAAO,WAAW,SAAS,OAAO,WAAW,SAC3C,eACA,OAAO,WAAW,QAChB,cACA,SAAS,OAAO;AAC1B,UAAO,EAAE,KAAK;IACZ,IAAI;IACJ,SAAS;KACP,OAAO,OAAO,MAAM,SAAS,SAAS;KACtC;KACA,QAAQ,OAAO;KACf,UAAU,OAAO;KACjB,WAAW,KAAK,KAAK,GAAG;KACxB,WAAW,OAAO,MAAM;KACzB;IACF,CAAC;WACK,OAAO;GACd,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAClE,OAAI,MAAM;IAAE,cAAc;IAAK;IAAU,EAAE,wBAAwB;AACnE,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,oBAAoB,OAAO;IAAE,EAAE,IAAI;;GAElF;CASF,MAAM,mBAAmB,OAAO,GAAY,UAA8B;AACxE,MAAI,CAAC,MAAM,YAAY,OACrB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,oBAAoB;GAAE,EAAE,IAAI;AAE3E,MAAI,MAAM,YAAY,SAAS,gBAC7B,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,kCAAkC;GAAE,EAAE,IAAI;AAEzF,MAAI,CAAC,MAAM,SAAS,aAAa,CAAC,WAAW,SAAS,CACpD,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,8CAA8C;GAAE,EAAE,IAAI;EAGrG,MAAM,SAAS,QAAQ;EACvB,MAAM,YAAY,4BAChB,OAAO,OAAO,OAAO,OACrB,OAAO,OAAO,MACf;AACD,MAAI,CAAC,eAAe,UAAU,CAC5B,QAAO,EAAE,KAAK;GACZ,IAAI;GACJ,OAAO,EAAE,SAAS,4EAA4E;GAC/F,EAAE,IAAI;EAGT,MAAM,YAAY,KAAK,KAAK;AAC5B,MAAI;GACF,MAAM,SAAS,MAAM,WAAW,MAAM,aAAa,WAAW;IAC5D,UAAU,MAAM,aAAa,UAAU,aAAa,YAAY,OAAO,KAAA;IACvE,MAAM,MAAM;IACZ,UAAU,MAAM;IAChB,QAAQ,EAAE,IAAI,IAAI;IACnB,CAAC;GACF,MAAM,MAAM,OAAO;GACnB,MAAM,mBAAmB,OAAO,YAAY,MAAM;GAClD,MAAM,UAAU,IAAI,MAAM,GACtB,MAAM,iBAAiB,KAAK,QAAQ,EAAE,IAAI,IAAI,OAAO,GACrD,KAAA;AACJ,UAAO,EAAE,KAAK;IACZ,IAAI;IACJ,SAAS;KACP;KACA,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;KAC9B,GAAI,mBAAmB,EAAE,UAAU,kBAAkB,GAAG,EAAE;KAC1D,UAAU,OAAO;KACjB,UAAU,OAAO;KACjB,WAAW,KAAK,KAAK,GAAG;KACzB;IACF,CAAC;WACK,OAAO;GACd,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAClE,OAAI,CAAC,EAAE,IAAI,IAAI,OAAO,QACpB,KAAI,MAAM;IAAE,KAAK;IAAO,UAAU,MAAM;IAAU,UAAU,MAAM;IAAU,EAAE,+BAA+B,MAAM;AAErH,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,yBAAyB,OAAO;IAAE,EAAE,IAAI;;;;AAKzF,eAAc,KAAK,6BAA6B,2BAA2B,OAAO,MAAM;EACtF,MAAM,gBAAgB,OAAO,EAAE,IAAI,OAAO,iBAAiB,IAAI,EAAE;AACjE,MAAI,OAAO,SAAS,cAAc,IAAI,gBAAgB,kBAAkB,OAAO,KAC7E,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,kCAAkC;GAAE,EAAE,IAAI;EAEzF,IAAI;AACJ,MAAI;AACF,UAAO,MAAM,EAAE,IAAI,UAAU;UACvB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,+BAA+B;IAAE,EAAE,IAAI;;EAEtF,MAAM,QAAQ,KAAK,IAAI,QAAQ;AAC/B,MAAI,EAAE,iBAAiB,MACrB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,sCAAsC;GAAE,EAAE,IAAI;EAE7F,MAAM,gBAAgB,KAAK,IAAI,WAAW;EAC1C,MAAM,WAAW,OAAO,kBAAkB,YAAY,cAAc,MAAM,GACtE,cAAc,MAAM,GACpB,KAAA;EACJ,MAAM,WAAW,UAAU,SAAS,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,MAAM,GACnF,MAAM,KAAK,MAAM,GACjB,SAAS,KAAK,KAAK;AACvB,SAAO,iBAAiB,GAAG;GACzB,aAAa,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;GACnD,UAAU,MAAM,QAAQ;GACxB;GACA,GAAI,WAAW,EAAE,UAAU,GAAG,EAAE;GACjC,CAAC;GACF;;AAGF,eAAc,KAAK,yBAAyB,2BAA2B,OAAO,MAAM;EAClF,IAAI,OAAwF,EAAE;AAC9F,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,qBAAqB;IAAE,EAAE,IAAI;;AAE5E,MAAI,OAAO,KAAK,UAAU,YAAY,CAAC,KAAK,MAAM,MAAM,CACtD,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,0CAA0C;GAAE,EAAE,IAAI;AAEjG,MAAI,OAAO,KAAK,aAAa,YAAY,CAAC,KAAK,SAAS,MAAM,CAC5D,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,oCAAoC;GAAE,EAAE,IAAI;EAE3F,MAAM,UAAU,KAAK,MAAM,QAAQ,OAAO,GAAG;AAC7C,MAAI,QAAQ,SAAS,MAAM,KAAK,CAAC,yBAAyB,KAAK,QAAQ,CACrE,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,6BAA6B;GAAE,EAAE,IAAI;EAEpF,MAAM,WAAW,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,MAAM,GACtE,KAAK,SAAS,MAAM,GACpB,KAAA;EACJ,MAAM,WAAW,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,MAAM,GACtE,KAAK,SAAS,MAAM,GACpB,SAAS,KAAK,KAAK;AACvB,SAAO,iBAAiB,GAAG;GACzB,aAAa,OAAO,KAAK,SAAS,SAAS;GAC3C,UAAU,KAAK,SAAS,MAAM;GAC9B;GACA,GAAI,WAAW,EAAE,UAAU,GAAG,EAAE;GACjC,CAAC;GACF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runSqliteWriteTransaction } from "../../../storage/sqlite/transaction.js";
|
|
2
2
|
import { deleteMemoryRecord, getMemoryRecord, listMemoryRecords, upsertMemoryRecord } from "../../../storage/sqlite/memory-records-repository.js";
|
|
3
|
+
import { listKnowledgeSourceItems, listKnowledgeSyncRuns } from "../../../storage/sqlite/knowledge-repository.js";
|
|
3
4
|
import { USER_TRUST_LEVELS, isUserTrustLevel } from "../../../user-context/trust-policy.js";
|
|
4
5
|
import { getUserTrustPolicy, setUserTrustPolicy } from "../../../storage/sqlite/user-trust-repository.js";
|
|
5
6
|
import { getUserProfilePromptState, setUserProfilePromptState } from "../../../storage/sqlite/user-profile-setup-repository.js";
|
|
@@ -190,7 +191,10 @@ function registerYouRoutes(authenticated, deps) {
|
|
|
190
191
|
const trustPolicy = getUserTrustPolicy();
|
|
191
192
|
const sourceDefinitions = listConnectorCatalog();
|
|
192
193
|
const sourceInstances = listConnectorInstances(config);
|
|
193
|
-
const sources = projectPersonalContextSources(sourceDefinitions, sourceInstances, allUserContextRecords
|
|
194
|
+
const sources = projectPersonalContextSources(sourceDefinitions, sourceInstances, allUserContextRecords, {
|
|
195
|
+
sourceItems: listKnowledgeSourceItems({ limit: 500 }),
|
|
196
|
+
syncRuns: listKnowledgeSyncRuns({ limit: 500 })
|
|
197
|
+
});
|
|
194
198
|
const activeGoals = new GoalService().list({
|
|
195
199
|
agentId,
|
|
196
200
|
status: [
|