cartethyia 2.7.0 → 2.7.2
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/.env.example +107 -106
- package/README.md +25 -2
- package/dist/dashboard/assets/{BannedPage-P76QLGJA.js → BannedPage-XaUmiBrC.js} +1 -1
- package/dist/dashboard/assets/BannedPage-XaUmiBrC.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-DdBggJS9.js → CliToolDetailPage-CqD7QiLM.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-aN1iXhhP.js → CliToolsPage-0XUo4UMn.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-DE-F1nxZ.js → CombosPage-CXGS_Ct-.js} +1 -1
- package/dist/dashboard/assets/CombosPage-CXGS_Ct-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-3CUxMy2X.js → CustomizationPage-DAG5J9EY.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BdmRLnq2.js +1 -0
- package/dist/dashboard/assets/LoginPage-BdmRLnq2.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-DKVslqG3.js → ModelPicker-BH8Tehdi.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js +1 -0
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js +4 -0
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js +9 -0
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-BjldQwIU.js → QuotaPage-7P2n--Fv.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-DUIoLRxk.js → SettingsPage-CxZ1fTkN.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js +1 -0
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-fI7JkdIB.js → SortableList-Dqf4zpS-.js} +1 -1
- package/dist/dashboard/assets/SortableList-Dqf4zpS-.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CHVZqskZ.js → StudioPage-Byj2lAEm.js} +3 -3
- package/dist/dashboard/assets/StudioPage-Byj2lAEm.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-CTkLAuRc.js +56 -0
- package/dist/dashboard/assets/UsagePage-CTkLAuRc.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-DFY_fZU4.js → api-keys-CUi6UXsR.js} +1 -1
- package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css +2 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
- package/dist/dashboard/assets/{assert-CwdnZARt.js → assert-67xXH5rf.js} +1 -1
- package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
- package/dist/dashboard/assets/{badge-Dr-uf0Au.js → badge-DC2L43Mj.js} +1 -1
- package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BddmMdoS.js → banner-BBVcb0J3.js} +1 -1
- package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
- package/dist/dashboard/assets/{card-CK0PXWlX.js → card-Cca1XC6G.js} +1 -1
- package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-CcB6mGh2.js → cli-tools-KvsWa1Jl.js} +1 -1
- package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DGb2P9Yx.js → clipboard-button-DUzEbynq.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-LkfuL50u.js → dialog-DFvWhwTa.js} +1 -1
- package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-ZbEZ_84c.js → drawer-CFM2sMX1.js} +1 -1
- package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BJfsVMvA.js → entry-Bqy8RFvM.js} +1 -1
- package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CWBY5A0f.js → entry-ClCMCWWQ.js} +3 -3
- package/dist/dashboard/assets/entry-ClCMCWWQ.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CvC1-Zqx.js → entry-_xHo5x6r.js} +1 -1
- package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CWWZ-uCZ.js +1 -0
- package/dist/dashboard/assets/formatters-CWWZ-uCZ.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-BxpznFLt.js → github-badge-C9e8Ts7e.js} +1 -1
- package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
- package/dist/dashboard/assets/{index-CDzP3ovJ.js → index-DFVFrnWW.js} +2 -2
- package/dist/dashboard/assets/index-DFVFrnWW.js.gz +0 -0
- package/dist/dashboard/assets/{inline-Ae13MQ5w.js → inline-BFUcvllj.js} +1 -1
- package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
- package/dist/dashboard/assets/{input-D23fo7cp.js → input-C_ILAvBb.js} +1 -1
- package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
- package/dist/dashboard/assets/{layout-CSsxjj5P.js → layout-CBIyT6Rt.js} +1 -1
- package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-dmbwZMDY.js → list-row-ebJ_-Nsb.js} +1 -1
- package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
- package/dist/dashboard/assets/{live-BG1oUsZZ.js → live-D0mm--ME.js} +1 -1
- package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-DSF-e4ho.js → mutation-sMD965oO.js} +1 -1
- package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-Bm_aECY7.js → numbers-BO9dKaFr.js} +1 -1
- package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
- package/dist/dashboard/assets/providers/inception.svg +5 -0
- package/dist/dashboard/assets/providers/inception.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-CF56nYIV.js → query-keys-CfL_Zdoh.js} +1 -1
- package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CeA71n7G.js → routing-BnIpi1IJ.js} +1 -1
- package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
- package/dist/dashboard/assets/{select-KIT0rC6G.js → select-DBNVLdUu.js} +1 -1
- package/dist/dashboard/assets/select-DBNVLdUu.js.gz +0 -0
- package/dist/dashboard/assets/{settings-B_2gkePf.js → settings-BAFhDgX8.js} +1 -1
- package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
- package/dist/dashboard/assets/{stack-D7WOSzcX.js → stack-EMrOMt4I.js} +1 -1
- package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
- package/dist/dashboard/assets/{state-BBxtKZOQ.js → state-CWoo7ty5.js} +1 -1
- package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DNS_D5Ea.js → switch-CCjT6IkF.js} +1 -1
- package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BgoxdoMk.js → useMutation-DjkLHUcE.js} +1 -1
- package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +54 -53
- package/migrations/full-postgres/0024_add_pool_provider_cooldowns.sql +12 -0
- package/migrations/full-postgres/0025_add_pool_random_strategy.sql +3 -0
- package/migrations/lite-sqlite3/0024_add_pool_provider_cooldowns.sql +12 -0
- package/migrations/lite-sqlite3/0025_add_pool_random_strategy.sql +20 -0
- package/package.json +96 -96
- package/scripts/cli/console-menu.ts +5 -2
- package/scripts/cli/tray.ts +21 -6
- package/src/boot/app.ts +684 -714
- package/src/boot/dependencies.ts +65 -62
- package/src/config/cooldowns.ts +16 -0
- package/src/config/providers.ts +13 -1
- package/src/config/resources.ts +70 -70
- package/src/config/security.ts +37 -37
- package/src/config/spec.ts +371 -358
- package/src/console/cli-tools/contracts.ts +10 -4
- package/src/console/console-router.ts +233 -225
- package/src/console/domain-registration.ts +398 -381
- package/src/console/providers/catalog/catalog-projections.ts +2 -6
- package/src/console/providers/catalog/contracts.ts +19 -0
- package/src/console/providers/catalog/provider-operations.ts +38 -0
- package/src/console/providers/catalog/routes.ts +14 -0
- package/src/console/providers/catalog/store-helpers.ts +326 -0
- package/src/console/providers/catalog/store.ts +1292 -1563
- package/src/console/quota/account-quota-global.ts +2 -2
- package/src/console/quota/account-quota-shared.ts +1 -1
- package/src/console/quota/account-quota-tenant.ts +35 -12
- package/src/console/quota/quota-view.ts +2 -2
- package/src/console/routing/pools/contracts.ts +41 -1
- package/src/console/routing/pools/operations.ts +183 -4
- package/src/console/routing/pools/relay-deploy.ts +17 -4
- package/src/console/routing/pools/routes.ts +30 -0
- package/src/console/routing/pools/store.ts +98 -8
- package/src/main.ts +183 -167
- package/src/network/outbound-fetch.ts +124 -208
- package/src/network/pool/agent.ts +15 -0
- package/src/network/pool/pool-state.ts +7 -0
- package/src/network/pool/selector.ts +110 -2
- package/src/network/pool-cooldown-store.ts +252 -0
- package/src/network/pool-health-machine.ts +5 -3
- package/src/network/pool-health.ts +36 -9
- package/src/observability/event-query.ts +57 -0
- package/src/observability/metrics.ts +467 -467
- package/src/observability/runtime-metrics.ts +269 -269
- package/src/persistence/health-event-flush.ts +2 -0
- package/src/persistence/schema.ts +19 -0
- package/src/persistence/usage-flush.ts +8 -3
- package/src/protocol/canonical-model.ts +1 -1
- package/src/protocol/dsml.ts +377 -0
- package/src/protocol/gateway-error.ts +232 -232
- package/src/{providers/provider-reasoning.ts → protocol/reasoning.ts} +10 -9
- package/src/protocol/request/chat.ts +7 -7
- package/src/protocol/request/codex.ts +2 -2
- package/src/protocol/request/gemini.ts +1 -1
- package/src/protocol/request/messages.ts +6 -6
- package/src/protocol/request/responses.ts +7 -7
- package/src/protocol/response/dsml.ts +12 -133
- package/src/{transport → protocol}/translation/attachment-integrity.ts +2 -2
- package/src/{transport → protocol}/translation/cache-controls.ts +1 -1
- package/src/{transport → protocol}/translation/capabilities.ts +2 -2
- package/src/{transport → protocol}/translation/normalize.ts +1 -1
- package/src/{transport → protocol}/translation/quirks.ts +1 -1
- package/src/{transport → protocol}/translation/search.ts +1 -1
- package/src/{transport → protocol}/translation/thinking.ts +23 -1
- package/src/{transport → protocol}/translation/tool-id.ts +1 -1
- package/src/{transport → protocol}/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream-parsing.ts +176 -0
- package/src/providers/adapters/compatible-adapter.ts +11 -2
- package/src/providers/authentication/oauth-refresh-service.ts +9 -5
- package/src/providers/default-registry.ts +22 -5
- package/src/providers/discovery/probe-phases.ts +16 -3
- package/src/providers/discovery/probing-service.ts +4 -1
- package/src/providers/integrations/anthropic.ts +1 -1
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -4
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +141 -8
- package/src/providers/integrations/buddy/buddy-checkin.ts +5 -1
- package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -82
- package/src/providers/integrations/buddy/buddy-identity.ts +25 -0
- package/src/providers/integrations/buddy/buddy-server-tools.ts +240 -0
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +36 -0
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +6 -31
- package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +19 -4
- package/src/providers/integrations/buddy/turing-device-token.ts +262 -0
- package/src/providers/integrations/buddy/workbuddy/provider.ts +2 -0
- package/src/providers/integrations/buddy/workbuddy/shared.ts +56 -3
- package/src/providers/integrations/claude/claude.ts +1 -1
- package/src/providers/integrations/cloudflareai/cloudflareai.ts +1 -1
- package/src/providers/integrations/codex/codex-errors.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +1 -1
- package/src/providers/integrations/factory/factory.ts +1 -1
- package/src/providers/integrations/github/github.ts +1 -1
- package/src/providers/integrations/grok/grok.ts +1 -1
- package/src/providers/integrations/inception/provider.ts +91 -0
- package/src/providers/integrations/inferhub.ts +1 -1
- package/src/providers/integrations/kiro/kiro-request.ts +1 -1
- package/src/providers/integrations/mistral.ts +193 -19
- package/src/providers/integrations/ollamacloud-models.ts +50 -0
- package/src/providers/integrations/opencode/fingerprint.ts +19 -2
- package/src/providers/integrations/opencode/prompt.ts +111 -0
- package/src/providers/integrations/opencode/provider.ts +101 -23
- package/src/providers/integrations/opencode/tool-definitions.ts +4 -4
- package/src/providers/integrations/qoder/qoder-checkin.ts +303 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +12 -1
- package/src/providers/integrations/qoder/qoder-realm.ts +20 -0
- package/src/providers/integrations/qoder/qoder-text-tools.ts +277 -0
- package/src/providers/integrations/qoder/qoder.ts +315 -400
- package/src/providers/integrations/shared/install-id.ts +2 -1
- package/src/providers/integrations/trae/trae-cache.ts +110 -0
- package/src/providers/integrations/trae/trae-oauth.ts +29 -4
- package/src/providers/integrations/trae/trae-protocol.ts +2 -1
- package/src/providers/integrations/trae/trae.ts +106 -23
- package/src/providers/operations/account-error-classification.ts +1684 -0
- package/src/providers/operations/account-health-service.ts +27 -1503
- package/src/providers/operations/account-reset-service.ts +2 -0
- package/src/providers/operations/provider-catalog-service.ts +11 -1
- package/src/providers/provider-contracts.ts +7 -4
- package/src/providers/provider-metadata.ts +25 -4
- package/src/providers/provider-probe.ts +2 -2
- package/src/{console → providers}/quota/cache.ts +1 -1
- package/src/{console → providers}/quota/refresh.ts +5 -5
- package/src/security/cli-client-fingerprint.ts +9 -2
- package/src/transport/dispatch/attempt-finalize.ts +114 -12
- package/src/transport/dispatch/attempt-loop.ts +3 -0
- package/src/transport/dispatch/empty-stream.ts +9 -2
- package/src/transport/dispatch/generation.ts +37 -55
- package/src/transport/dispatch/handler-deps.ts +32 -0
- package/src/transport/dispatch/leases.ts +9 -4
- package/src/transport/dispatch/proxy-request.ts +6 -30
- package/src/transport/dispatch/responses-compact.ts +22 -43
- package/src/transport/dispatch/streaming/finalize.ts +3 -0
- package/src/transport/dispatch/streaming/stream.ts +0 -8
- package/src/transport/dispatch/systemone.ts +22 -42
- package/src/transport/dispatch/token-count.ts +23 -39
- package/src/transport/dispatch/upstream.ts +1 -3
- package/src/transport/dispatch/websearch.ts +22 -41
- package/src/transport/failure-policy.ts +92 -178
- package/src/transport/guards/abuse-guard.ts +10 -0
- package/src/transport/guards/auth-guard.ts +109 -109
- package/src/transport/guards/error-lifecycle.ts +1 -1
- package/src/transport/guards/pipeline.ts +220 -220
- package/src/transport/guards/request-context.ts +245 -238
- package/src/transport/request/degradation.ts +20 -1
- package/src/transport/request/inflight.ts +4 -114
- package/src/transport/request/preparer.ts +6 -6
- package/src/transport/request/state.ts +66 -38
- package/src/transport/request/tenant-preferences.ts +1 -1
- package/src/transport/routing/route-catalog.ts +75 -43
- package/src/transport/routing/router.ts +30 -2
- package/src/transport/surface/messages/parse.ts +1 -1
- package/src/transport/surface/responses/encode.ts +2 -3
- package/src/transport/surface/responses/parse.ts +49 -7
- package/src/types/undici.d.ts +33 -0
- package/src/workers/buddy-growth-tasks.ts +346 -425
- package/src/workers/daily-checkin.ts +17 -6
- package/src/workers/quota-refresh-sweep.ts +2 -2
- package/src/workers/scheduler.ts +17 -1
- package/dist/dashboard/assets/BannedPage-P76QLGJA.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DdBggJS9.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-aN1iXhhP.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DE-F1nxZ.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-3CUxMy2X.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-B_0FUKWc.js +0 -1
- package/dist/dashboard/assets/LoginPage-B_0FUKWc.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-DKVslqG3.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js +0 -1
- package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js +0 -7
- package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BjldQwIU.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DUIoLRxk.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-_bCaTM0v.js +0 -1
- package/dist/dashboard/assets/SetupPage-_bCaTM0v.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-fI7JkdIB.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-CHVZqskZ.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DiFBymHj.js +0 -56
- package/dist/dashboard/assets/UsagePage-DiFBymHj.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DFY_fZU4.js.gz +0 -0
- package/dist/dashboard/assets/app-BOJ1_xUB.css +0 -2
- package/dist/dashboard/assets/app-BOJ1_xUB.css.gz +0 -0
- package/dist/dashboard/assets/assert-CwdnZARt.js.gz +0 -0
- package/dist/dashboard/assets/badge-Dr-uf0Au.js.gz +0 -0
- package/dist/dashboard/assets/banner-BddmMdoS.js.gz +0 -0
- package/dist/dashboard/assets/card-CK0PXWlX.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CcB6mGh2.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DGb2P9Yx.js.gz +0 -0
- package/dist/dashboard/assets/cog-CdWhsajs.js +0 -1
- package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
- package/dist/dashboard/assets/dialog-LkfuL50u.js.gz +0 -0
- package/dist/dashboard/assets/drawer-ZbEZ_84c.js.gz +0 -0
- package/dist/dashboard/assets/entry-BJfsVMvA.js.gz +0 -0
- package/dist/dashboard/assets/entry-CWBY5A0f.js.gz +0 -0
- package/dist/dashboard/assets/entry-CvC1-Zqx.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-BxpznFLt.js.gz +0 -0
- package/dist/dashboard/assets/index-CDzP3ovJ.js.gz +0 -0
- package/dist/dashboard/assets/inline-Ae13MQ5w.js.gz +0 -0
- package/dist/dashboard/assets/input-D23fo7cp.js.gz +0 -0
- package/dist/dashboard/assets/layout-CSsxjj5P.js.gz +0 -0
- package/dist/dashboard/assets/list-row-dmbwZMDY.js.gz +0 -0
- package/dist/dashboard/assets/live-BG1oUsZZ.js.gz +0 -0
- package/dist/dashboard/assets/mutation-DSF-e4ho.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Bm_aECY7.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-CF56nYIV.js.gz +0 -0
- package/dist/dashboard/assets/routing-CeA71n7G.js.gz +0 -0
- package/dist/dashboard/assets/select-KIT0rC6G.js.gz +0 -0
- package/dist/dashboard/assets/settings-B_2gkePf.js.gz +0 -0
- package/dist/dashboard/assets/stack-D7WOSzcX.js.gz +0 -0
- package/dist/dashboard/assets/state-BBxtKZOQ.js.gz +0 -0
- package/dist/dashboard/assets/switch-DNS_D5Ea.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BgoxdoMk.js.gz +0 -0
- /package/dist/dashboard/assets/{app-CaXZ9SCn.js → app-BaWd0puM.js} +0 -0
- /package/dist/dashboard/assets/{app-CaXZ9SCn.js.gz → app-BaWd0puM.js.gz} +0 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical DSML (DeepSeek Markup Language) tool-call recovery.
|
|
3
|
+
*
|
|
4
|
+
* DeepSeek-family models occasionally write their tool calls as XML-like DSML
|
|
5
|
+
* markup inside the plain-text content stream instead of the structured
|
|
6
|
+
* `tool_calls` field. Two upstream families in this gateway see it:
|
|
7
|
+
*
|
|
8
|
+
* - CodeBuddy/WorkBuddy emit strict blocks (`<|DSML| calls>…</|DSML| calls>`)
|
|
9
|
+
* that must be parsed exactly, with declared-tool allowlisting.
|
|
10
|
+
* - Qoder emits looser blocks (`<[^>]*DSML[^>]*calls>…`) that tolerate any
|
|
11
|
+
* intermediate characters in the tag, and its parameter values are plain
|
|
12
|
+
* strings.
|
|
13
|
+
*
|
|
14
|
+
* The two share the same recovery idea, so the mechanics live here once and
|
|
15
|
+
* each caller picks its dialect through options. The defaults reproduce the
|
|
16
|
+
* CodeBuddy dialect exactly (`app/dsml.py` in codebuddy2api); Qoder passes
|
|
17
|
+
* `fuzzy: true` and its id factory, mirroring `qoder_proxy.py`.
|
|
18
|
+
*/
|
|
19
|
+
import { randomUUID } from "node:crypto";
|
|
20
|
+
|
|
21
|
+
export interface DsmlToolCall {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly arguments: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DsmlRecovery {
|
|
28
|
+
readonly calls: readonly DsmlToolCall[];
|
|
29
|
+
readonly clean: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DsmlParseOptions {
|
|
33
|
+
/** Separator spellings a strict block may use (CodeBuddy dialect). */
|
|
34
|
+
readonly separators?: readonly string[];
|
|
35
|
+
/** Block names a strict opener may name. */
|
|
36
|
+
readonly blockNames?: readonly string[];
|
|
37
|
+
/** When true, match the Qoder fuzzy form (`<[^>]*DSML[^>]*…`). */
|
|
38
|
+
readonly fuzzy?: boolean;
|
|
39
|
+
/** How parameter values decode: `json` (CodeBuddy) or `string` (Qoder). */
|
|
40
|
+
readonly params?: "json" | "string";
|
|
41
|
+
/** Call id factory. Defaults to sequential for strict, uuid for fuzzy. */
|
|
42
|
+
readonly id?: (call: { readonly name: string }, index: number, seq: number) => string;
|
|
43
|
+
/** Non-empty allowlist makes the strict dialect reject undeclared names. */
|
|
44
|
+
readonly allowedNames?: ReadonlySet<string>;
|
|
45
|
+
/**
|
|
46
|
+
* When true, JSON stringifies argument objects with recursively sorted keys,
|
|
47
|
+
* matching the reference bridge (`json.dumps(..., sort_keys=True)`). The
|
|
48
|
+
* strict dialect defaults to true; the fuzzy dialect never sorts, matching
|
|
49
|
+
* its reference's `json.dumps(params)`.
|
|
50
|
+
*/
|
|
51
|
+
readonly sortKeys?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Character cap for an unclosed strict block. Past it, the block is treated
|
|
54
|
+
* as plain text and passed through instead of buffered indefinitely — same
|
|
55
|
+
* fail-open as `MAX_BLOCK_BYTES` in the reference bridge (256 KB).
|
|
56
|
+
*/
|
|
57
|
+
readonly maxBlockBytes?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const DEFAULT_SEPARATORS = ["||", "|", "|"] as const;
|
|
61
|
+
const DEFAULT_BLOCK_NAMES = ["calls", "function_calls"] as const;
|
|
62
|
+
const DEFAULT_MAX_BLOCK_BYTES = 256 * 1024;
|
|
63
|
+
const TOOL_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,63}$/;
|
|
64
|
+
|
|
65
|
+
/** Escapes regex metacharacters so a `|` separator is literal, not alternation. */
|
|
66
|
+
export function escapeRegExp(value: string): string {
|
|
67
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function openers(separators: readonly string[], blockNames: readonly string[]): readonly string[] {
|
|
71
|
+
return separators.flatMap((separator) => blockNames.map((name) => `<${separator}DSML${separator} ${name}>`));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function sequentialId(_call: { readonly name: string }, _index: number, seq: number): string {
|
|
75
|
+
return `call_dsml_${seq}_${_call.name}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function uuidId(_call: { readonly name: string }, _index: number, _seq: number): string {
|
|
79
|
+
return `call_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface DsmlParseResult {
|
|
83
|
+
readonly text: string;
|
|
84
|
+
readonly calls: readonly DsmlToolCall[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Streaming strict-prefix parser (CodeBuddy dialect). Buffers text until a
|
|
89
|
+
* complete block is seen, emits clean prose as it settles, and recovers calls
|
|
90
|
+
* from complete blocks only. Incomplete blocks stay text; an unclosed block
|
|
91
|
+
* past `maxBlockBytes` passes through as text instead of buffering forever.
|
|
92
|
+
* With no allowlist the generic name rule applies; with one, undeclared names
|
|
93
|
+
* reject the whole block. The caller feeds chunks with `push` and drains the
|
|
94
|
+
* tail with `flush`.
|
|
95
|
+
*/
|
|
96
|
+
export class DsmlStreamParser {
|
|
97
|
+
readonly #separators: readonly string[];
|
|
98
|
+
readonly #blockNames: readonly string[];
|
|
99
|
+
readonly #openers: readonly string[];
|
|
100
|
+
readonly #allowedNames: ReadonlySet<string>;
|
|
101
|
+
readonly #params: "json" | "string";
|
|
102
|
+
readonly #sortKeys: boolean;
|
|
103
|
+
readonly #maxBlockBytes: number;
|
|
104
|
+
readonly #id: (call: { readonly name: string }, index: number, seq: number) => string;
|
|
105
|
+
#pending = "";
|
|
106
|
+
#callSequence = 0;
|
|
107
|
+
|
|
108
|
+
constructor(options: DsmlParseOptions = {}) {
|
|
109
|
+
this.#separators = options.separators ?? DEFAULT_SEPARATORS;
|
|
110
|
+
this.#blockNames = options.blockNames ?? DEFAULT_BLOCK_NAMES;
|
|
111
|
+
this.#openers = openers(this.#separators, this.#blockNames);
|
|
112
|
+
this.#allowedNames = options.allowedNames ?? new Set();
|
|
113
|
+
this.#params = options.params ?? "json";
|
|
114
|
+
this.#sortKeys = options.sortKeys ?? true;
|
|
115
|
+
this.#maxBlockBytes = options.maxBlockBytes ?? DEFAULT_MAX_BLOCK_BYTES;
|
|
116
|
+
this.#id = options.id ?? sequentialId;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
push(chunk: string): DsmlParseResult {
|
|
120
|
+
this.#pending += chunk;
|
|
121
|
+
let cursor = 0;
|
|
122
|
+
let visible = "";
|
|
123
|
+
const calls: DsmlToolCall[] = [];
|
|
124
|
+
for (;;) {
|
|
125
|
+
let openIndex = -1;
|
|
126
|
+
let opener = "";
|
|
127
|
+
for (const candidate of this.#openers) {
|
|
128
|
+
const index = this.#pending.indexOf(candidate, cursor);
|
|
129
|
+
if (index >= 0 && (openIndex < 0 || index < openIndex)) {
|
|
130
|
+
openIndex = index;
|
|
131
|
+
opener = candidate;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (openIndex < 0) {
|
|
135
|
+
const safeEnd = this.#settledEnd(cursor);
|
|
136
|
+
visible += this.#pending.slice(cursor, safeEnd);
|
|
137
|
+
cursor = safeEnd;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
visible += this.#pending.slice(cursor, openIndex);
|
|
141
|
+
const separator = this.#separators.find((value) => opener.startsWith(`<${value}DSML`));
|
|
142
|
+
const blockName = this.#blockNames.find((value) => opener.endsWith(` ${value}>`));
|
|
143
|
+
if (separator === undefined || blockName === undefined) {
|
|
144
|
+
visible += "<";
|
|
145
|
+
cursor = openIndex + 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const close = `</${separator}DSML${separator} ${blockName}>`;
|
|
149
|
+
const closeIndex = this.#pending.indexOf(close, openIndex + opener.length);
|
|
150
|
+
if (closeIndex < 0) {
|
|
151
|
+
// An unclosed block past the cap is not markup — pass it through as
|
|
152
|
+
// text instead of buffering it indefinitely. Same fail-open as
|
|
153
|
+
// `feed()` in the reference bridge's `MAX_BLOCK_BYTES` guard.
|
|
154
|
+
if (this.#pending.length - openIndex > this.#maxBlockBytes) {
|
|
155
|
+
visible += this.#pending.slice(openIndex);
|
|
156
|
+
cursor = this.#pending.length;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
cursor = openIndex;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
const end = closeIndex + close.length;
|
|
163
|
+
const block = this.#pending.slice(openIndex, end);
|
|
164
|
+
const parsed = parseStrictBlock(block, opener, close, separator, this.#allowedNames, this.#params, this.#sortKeys);
|
|
165
|
+
if (parsed === undefined) visible += block;
|
|
166
|
+
else {
|
|
167
|
+
parsed.forEach((call, index) => {
|
|
168
|
+
calls.push({ ...call, id: this.#id(call, index, this.#callSequence++) });
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
cursor = end;
|
|
172
|
+
}
|
|
173
|
+
this.#pending = this.#pending.slice(cursor);
|
|
174
|
+
return { text: visible, calls };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
flush(): DsmlParseResult {
|
|
178
|
+
const text = this.#pending;
|
|
179
|
+
this.#pending = "";
|
|
180
|
+
return { text, calls: [] };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#settledEnd(start: number): number {
|
|
184
|
+
const remaining = this.#pending.slice(start);
|
|
185
|
+
const maxOpener = this.#openers.reduce((max, opener) => Math.max(max, opener.length - 1), 0);
|
|
186
|
+
for (let length = Math.min(maxOpener, remaining.length); length > 0; length -= 1) {
|
|
187
|
+
const suffix = remaining.slice(-length);
|
|
188
|
+
if (this.#openers.some((opener) => opener.startsWith(suffix))) return this.#pending.length - length;
|
|
189
|
+
}
|
|
190
|
+
return this.#pending.length;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function parseStrictBlock(
|
|
195
|
+
block: string,
|
|
196
|
+
opener: string,
|
|
197
|
+
close: string,
|
|
198
|
+
separator: string,
|
|
199
|
+
allowedNames: ReadonlySet<string>,
|
|
200
|
+
params: "json" | "string",
|
|
201
|
+
sortKeys: boolean,
|
|
202
|
+
): Omit<DsmlToolCall, "id">[] | undefined {
|
|
203
|
+
const inner = block.slice(opener.length, -close.length);
|
|
204
|
+
const pipe = escapeRegExp(separator);
|
|
205
|
+
const invokePattern = new RegExp(`<${pipe}DSML${pipe}\\s+invoke\\s+name="([^"]*)"\\s*>`, "g");
|
|
206
|
+
const calls: Omit<DsmlToolCall, "id">[] = [];
|
|
207
|
+
// The allowlist is strict only when the caller knows the declared tools: a
|
|
208
|
+
// caller without one (a direct parse) keeps the generic name rule, while a
|
|
209
|
+
// dispatch that declared its tools must never execute a name it did not offer.
|
|
210
|
+
const strict = allowedNames.size > 0;
|
|
211
|
+
let cursor = 0;
|
|
212
|
+
for (const invoke of inner.matchAll(invokePattern)) {
|
|
213
|
+
const name = invoke[1] ?? "";
|
|
214
|
+
if (!TOOL_NAME.test(name) || (strict && !allowedNames.has(name)) || invoke.index === undefined) return undefined;
|
|
215
|
+
if (inner.slice(cursor, invoke.index).trim().length > 0) return undefined;
|
|
216
|
+
const bodyStart = invoke.index + invoke[0].length;
|
|
217
|
+
const invokeClose = `</${separator}DSML${separator} invoke>`;
|
|
218
|
+
const invokeEnd = inner.indexOf(invokeClose, bodyStart);
|
|
219
|
+
if (invokeEnd < 0) return undefined;
|
|
220
|
+
const parameterPattern = new RegExp(`<${pipe}DSML${pipe}\\s+parameter\\s+name="([^"]*)"(?:\\s+string="(true|false)")?\\s*>`, "g");
|
|
221
|
+
const body = inner.slice(bodyStart, invokeEnd);
|
|
222
|
+
const args: Record<string, unknown> = {};
|
|
223
|
+
let parameterCursor = 0;
|
|
224
|
+
for (const parameter of body.matchAll(parameterPattern)) {
|
|
225
|
+
const key = parameter[1] ?? "";
|
|
226
|
+
if (key.length === 0 || parameter.index === undefined || body.slice(parameterCursor, parameter.index).trim().length > 0) return undefined;
|
|
227
|
+
const valueStart = parameter.index + parameter[0].length;
|
|
228
|
+
const parameterClose = `</${separator}DSML${separator} parameter>`;
|
|
229
|
+
const valueEnd = body.indexOf(parameterClose, valueStart);
|
|
230
|
+
if (valueEnd < 0) return undefined;
|
|
231
|
+
const raw = body.slice(valueStart, valueEnd);
|
|
232
|
+
if (params === "string") args[key] = raw;
|
|
233
|
+
else if (parameter[2] === "true") args[key] = raw;
|
|
234
|
+
else {
|
|
235
|
+
try { args[key] = JSON.parse(raw); }
|
|
236
|
+
catch { args[key] = raw; }
|
|
237
|
+
}
|
|
238
|
+
parameterCursor = valueEnd + parameterClose.length;
|
|
239
|
+
}
|
|
240
|
+
if (body.slice(parameterCursor).trim().length > 0) return undefined;
|
|
241
|
+
calls.push({ name, arguments: JSON.stringify(sortKeys ? normalizeForSort(args) : args) });
|
|
242
|
+
cursor = invokeEnd + invokeClose.length;
|
|
243
|
+
}
|
|
244
|
+
return calls.length > 0 && inner.slice(cursor).trim().length === 0 ? calls : undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Returns `value` with plain-object keys recursively sorted, mirroring the
|
|
249
|
+
* reference bridge's `json.dumps(..., sort_keys=True)`. Arrays keep their
|
|
250
|
+
* order — sorting applies to object keys only.
|
|
251
|
+
*/
|
|
252
|
+
function normalizeForSort(value: unknown): unknown {
|
|
253
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
|
|
254
|
+
const record = value as Record<string, unknown>;
|
|
255
|
+
const out: Record<string, unknown> = {};
|
|
256
|
+
for (const key of Object.keys(record).sort()) out[key] = normalizeForSort(record[key]);
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** The Qoder fuzzy block scanner, mirroring the reference bridge's regexes. */
|
|
261
|
+
const FUZZY_CALLS_RE = /<[^>]*DSML[^>]*calls>([\s\S]*?)<\/[^>]*DSML[^>]*calls>/;
|
|
262
|
+
const FUZZY_INVOKE_RE = /<[^>]*DSML[^>]*invoke\s+name=["']([^"']+)["']>([\s\S]*?)<\/[^>]*invoke>/g;
|
|
263
|
+
const FUZZY_PARAM_RE = /<[^>]*DSML[^>]*parameter\s+name=["']([^"']+)["'][^>]*>([\s\S]*?)<\/[^>]*parameter>/g;
|
|
264
|
+
|
|
265
|
+
function parseFuzzyDsml(text: string, options: DsmlParseOptions): DsmlRecovery {
|
|
266
|
+
const id = options.id ?? uuidId;
|
|
267
|
+
if (text.length === 0 || !text.includes("DSML")) return { calls: [], clean: text };
|
|
268
|
+
|
|
269
|
+
const callsMatch = FUZZY_CALLS_RE.exec(text);
|
|
270
|
+
let block: string;
|
|
271
|
+
let clean: string;
|
|
272
|
+
|
|
273
|
+
if (callsMatch) {
|
|
274
|
+
block = callsMatch[1] ?? "";
|
|
275
|
+
const before = text.slice(0, callsMatch.index).trimEnd();
|
|
276
|
+
const after = text.slice(callsMatch.index + callsMatch[0].length).trimStart();
|
|
277
|
+
clean = (before + " " + after).trim();
|
|
278
|
+
} else {
|
|
279
|
+
// No <calls> wrapper (issue #51): find bare <invoke> blocks in the whole
|
|
280
|
+
// text, then cut the prose off at the last `<` before the first invoke —
|
|
281
|
+
// exclusive of the invoke's own tag start, exactly like the reference's
|
|
282
|
+
// `text.rfind("<", 0, first_index)`.
|
|
283
|
+
FUZZY_INVOKE_RE.lastIndex = 0;
|
|
284
|
+
const first = FUZZY_INVOKE_RE.exec(text);
|
|
285
|
+
if (!first) return { calls: [], clean: text };
|
|
286
|
+
block = text;
|
|
287
|
+
const cut = text.lastIndexOf("<", first.index - 1);
|
|
288
|
+
clean = text.slice(0, cut >= 0 ? cut : first.index).trim();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const calls: DsmlToolCall[] = [];
|
|
292
|
+
FUZZY_INVOKE_RE.lastIndex = 0;
|
|
293
|
+
let inv: RegExpExecArray | null;
|
|
294
|
+
let seq = 0;
|
|
295
|
+
while ((inv = FUZZY_INVOKE_RE.exec(block)) !== null) {
|
|
296
|
+
const params: Record<string, string> = {};
|
|
297
|
+
FUZZY_PARAM_RE.lastIndex = 0;
|
|
298
|
+
let par: RegExpExecArray | null;
|
|
299
|
+
while ((par = FUZZY_PARAM_RE.exec(inv[2] ?? "")) !== null) {
|
|
300
|
+
params[par[1] ?? ""] = (par[2] ?? "").trim();
|
|
301
|
+
}
|
|
302
|
+
const call = { name: inv[1] ?? "", arguments: JSON.stringify(params) };
|
|
303
|
+
calls.push({ ...call, id: id(call, calls.length, seq++) });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return { calls, clean };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Recovers DSML tool calls from one plain-text string using the Qoder fuzzy
|
|
311
|
+
* dialect. Always returns `{ calls, clean }` — no-DSML input returns the text
|
|
312
|
+
* unchanged with no calls.
|
|
313
|
+
*/
|
|
314
|
+
export function parseFuzzyDsmlToolCalls(text: string): DsmlRecovery {
|
|
315
|
+
return parseFuzzyDsml(text, {});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Recovers DSML tool calls from one plain-text string. The dialect is chosen
|
|
320
|
+
* by `options.fuzzy`; each dialect keeps its exact public shape:
|
|
321
|
+
*
|
|
322
|
+
* - strict (CodeBuddy): returns `{ text, calls }`, with `text` the clean
|
|
323
|
+
* prose; `undefined` when nothing was recovered.
|
|
324
|
+
* - fuzzy (Qoder): returns `{ clean, calls }`, with no-DSML input returning
|
|
325
|
+
* `{ calls: [], clean: text }`.
|
|
326
|
+
*
|
|
327
|
+
* Callers that need the streaming strict parser use {@link DsmlStreamParser}.
|
|
328
|
+
*/
|
|
329
|
+
export function parseDsmlToolCalls(
|
|
330
|
+
text: string,
|
|
331
|
+
options: DsmlParseOptions = {},
|
|
332
|
+
): DsmlRecovery | DsmlParseResult | undefined {
|
|
333
|
+
if (options.fuzzy === true) return parseFuzzyDsml(text, options);
|
|
334
|
+
if (!text.includes("DSML")) return undefined;
|
|
335
|
+
const parser = new DsmlStreamParser(options);
|
|
336
|
+
const first = parser.push(text);
|
|
337
|
+
const last = parser.flush();
|
|
338
|
+
const recovered = [...first.calls, ...last.calls];
|
|
339
|
+
return recovered.length === 0 ? undefined : { text: `${first.text}${last.text}`.trim(), calls: recovered };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* The holdable tail of a content chunk: a short suffix that looks like the
|
|
344
|
+
* start of a DSML tag and may finish in the next frame. Empty when there is
|
|
345
|
+
* nothing worth holding. Mirrors `_dsml_tail_hold` in the reference bridge.
|
|
346
|
+
*/
|
|
347
|
+
export function dsmlTailHold(text: string): string {
|
|
348
|
+
const at = text.lastIndexOf("<");
|
|
349
|
+
if (at < 0 || text.length - at > 24) return "";
|
|
350
|
+
const tail = text.slice(at);
|
|
351
|
+
// A complete tag already — nothing to wait for.
|
|
352
|
+
if (tail.includes(">")) return "";
|
|
353
|
+
const body = tail.replace(/\uFF5C/g, "|").toUpperCase().slice(1).replace(/^\//, "");
|
|
354
|
+
if (body.length === 0) return tail; // bare "<" or "</"
|
|
355
|
+
if (!body.startsWith("|")) return "";
|
|
356
|
+
const rest = body.slice(1);
|
|
357
|
+
if (rest.length === 0) return tail;
|
|
358
|
+
if ("DSML".startsWith(rest) || rest.startsWith("DSML")) return tail;
|
|
359
|
+
return "";
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const DSML_CLOSE_TAG_RE = /<\/(?:[^>]*DSML[^>]*|parameter|invoke|calls|tool_calls|function_calls)\s*>\s*$/i;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Removes leftover DSML closing tags from the tail of a text. Anchored to the
|
|
366
|
+
* end and looped, so several trailing tags come off one per pass; a closing
|
|
367
|
+
* tag in the middle of a text is not residue and is preserved.
|
|
368
|
+
*/
|
|
369
|
+
export function stripDsmlCloseTags(text: string): string {
|
|
370
|
+
let previous: string | null = null;
|
|
371
|
+
let out = text.trimEnd();
|
|
372
|
+
while (previous !== out) {
|
|
373
|
+
previous = out;
|
|
374
|
+
out = out.replace(DSML_CLOSE_TAG_RE, "").trimEnd();
|
|
375
|
+
}
|
|
376
|
+
return out;
|
|
377
|
+
}
|