cartethyia 2.7.9 → 2.8.0
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 +3 -2
- package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Cc0vKpMG.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-CXsaOAO2.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BeLpJ4ma.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-DnV0uZ3m.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-CBxAjo63.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CgoPc5B9.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-D9bFQern.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-B_p4tL8k.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
- package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-D_0PiZtg.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-BJ87Pp0U.js → ProxyPage-BwNC_-Cx.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-CctTga2C.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage-febLU4r1.js} +1 -1
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-M6fvR5Wz.js} +1 -1
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-nyeuRqz9.js} +1 -1
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-BFOlvrJC.js} +1 -1
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
- package/dist/dashboard/assets/assert-CqTLw5e7.js +1 -0
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-C83EqLB0.js} +1 -1
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-D1xn7pIe.js} +1 -1
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-DU3Lq5Lm.js} +1 -1
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-Dw1U_drK.js} +1 -1
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-CEL5Sg_J.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-BjFR5UHy.js} +1 -1
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-HvBW0AjI.js} +1 -1
- package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
- package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-CECVELyy.js} +1 -1
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-DYYofQ7W.js} +1 -1
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-DLW4i8zK.js} +1 -1
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/{index-BU64Lqvp.js → index-1-fCqe64.js} +2 -2
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-DGvU2oLK.js} +1 -1
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/{input-BCVkdR9d.js → input-iNnO1X2K.js} +1 -1
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-0q8UGypb.js} +1 -1
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-C0w2bQjJ.js} +1 -1
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/{live-Cae01iu8.js → live-M5aJ_xCa.js} +1 -1
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/{network-CRib7H_2.js → network-BKeQGT3K.js} +1 -1
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-CivAk3da.js} +1 -1
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-CpzvI5ep.js} +1 -1
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
- package/dist/dashboard/assets/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-BFQX2VBc.js} +1 -1
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/{select-ChoFWyKz.js → select-CXbck0jG.js} +2 -2
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-C_XvMHh9.js} +1 -1
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-DggRiCxE.js} +1 -1
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-ClFzfbYp.js} +1 -1
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/{switch-Db70BMix.js → switch-CTIFxKqS.js} +1 -1
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-BClG3yWj.js} +1 -1
- package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +54 -53
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/package.json +1 -1
- package/src/boot/app.ts +10 -13
- package/src/boot/dependencies.ts +1 -1
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +20 -0
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +40 -28
- package/src/console/auth/account-reset.ts +7 -9
- package/src/console/backup/contracts.ts +4 -0
- package/src/console/backup/export-endpoint.ts +7 -5
- package/src/console/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/domain-registration.ts +0 -2
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +7 -13
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/contracts.ts +2 -2
- package/src/console/providers/catalog/discover-constants.ts +6 -0
- package/src/console/providers/catalog/routes.ts +1 -0
- package/src/console/providers/catalog/store.ts +21 -16
- package/src/console/quota/account-quota-global.ts +14 -24
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +6 -18
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/operations.ts +31 -46
- package/src/console/shared/errors.ts +33 -6
- package/src/main.ts +2 -3
- package/src/network/pool/byte-accounting.ts +3 -2
- package/src/network/pool/selector.ts +182 -39
- package/src/network/pool-cooldown-store.ts +89 -14
- package/src/network/pool-health.ts +49 -61
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/ssrf.ts +6 -6
- package/src/observability/event-log.ts +7 -361
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +15 -25
- package/src/observability/telemetry-reconciliation.ts +9 -37
- package/src/observability/telemetry-row.ts +125 -0
- package/src/persistence/db-mode.ts +4 -1
- package/src/persistence/dialect.ts +18 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +8 -0
- package/src/persistence/ttl-cache.ts +20 -2
- package/src/protocol/codecs.ts +1 -1
- package/src/protocol/dsml.ts +34 -86
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/request/chat.ts +10 -10
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +3 -4
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/providers/adapters/compatible-adapter.ts +71 -12
- package/src/providers/authentication/oauth-client.ts +1 -1
- package/src/providers/authentication/oauth-device-flow.ts +98 -8
- package/src/providers/authentication/oauth-refresh-service.ts +30 -49
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +61 -36
- package/src/providers/discovery/probe-phases.ts +14 -4
- package/src/providers/discovery/probing-service.ts +15 -2
- package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
- package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +5 -1
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +173 -17
- package/src/providers/integrations/buddy/buddy-identity.ts +19 -6
- package/src/providers/integrations/buddy/capabilities.ts +2 -2
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +3 -0
- package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +37 -22
- package/src/providers/integrations/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline.ts +2 -2
- package/src/providers/integrations/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
- package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
- package/src/providers/integrations/cursor/cursor-request.ts +26 -12
- package/src/providers/integrations/cursor/cursor-shared.ts +7 -4
- package/src/providers/integrations/cursor/cursor.ts +89 -40
- package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
- package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
- package/src/providers/integrations/factory/factory-oauth.ts +17 -44
- package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
- package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
- package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
- package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
- package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
- package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
- package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
- package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
- package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
- package/src/providers/integrations/freebuff/freebuff.ts +682 -0
- package/src/providers/integrations/grok/grok-oauth.ts +48 -45
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
- package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +9 -9
- package/src/providers/integrations/hermes/index.ts +2 -0
- package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
- package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
- package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- package/src/providers/integrations/ollamacloud/index.ts +2 -0
- package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
- package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-protocol.ts +0 -3
- package/src/providers/integrations/perch/perch-quota.ts +2 -16
- package/src/providers/integrations/perch/perch.ts +3 -13
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +18 -59
- package/src/providers/integrations/qoder/qoder-adapter.ts +1 -12
- package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
- package/src/providers/integrations/qoder/qoder-catalog.ts +20 -5
- package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
- package/src/providers/integrations/qoder/qoder-checkin.ts +4 -4
- package/src/providers/integrations/qoder/qoder-grpc.ts +0 -1
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -107
- package/src/providers/integrations/qoder/qoder.ts +0 -1
- package/src/providers/integrations/trae/trae-protocol.ts +9 -5
- package/src/providers/integrations/trae/trae-quota.ts +6 -28
- package/src/providers/integrations/trae/trae-realm.ts +38 -0
- package/src/providers/integrations/trae/trae.ts +7 -24
- package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
- package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
- package/src/providers/integrations/zro/zro.ts +204 -0
- package/src/providers/operations/account-error-classification.ts +199 -307
- package/src/providers/operations/account-health-service.ts +82 -1
- package/src/providers/operations/account-refresh-status.ts +22 -37
- package/src/providers/operations/account-tier.ts +15 -16
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +5 -51
- package/src/providers/operations/cooldown-rules.ts +11 -16
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +102 -32
- package/src/providers/operations/provider-rules/github.ts +48 -0
- package/src/providers/operations/provider-rules/grok.ts +33 -0
- package/src/providers/operations/provider-rules/inception.ts +49 -0
- package/src/providers/operations/provider-rules/index.ts +32 -0
- package/src/providers/operations/provider-rules/perch.ts +37 -0
- package/src/providers/operations/provider-rules/qoder.ts +70 -0
- package/src/providers/operations/provider-rules/trae.ts +81 -0
- package/src/providers/provider-contracts.ts +4 -2
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +40 -10
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- package/src/providers/quota/quota-limit-windows.ts +16 -5
- package/src/providers/quota/quota-support.ts +1 -1
- package/src/providers/quota/quota-window-parser.ts +90 -18
- package/src/providers/quota/refresh.ts +75 -18
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/transport/dispatch/attempt-finalize.ts +14 -13
- package/src/transport/dispatch/attempt-loop.ts +71 -17
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +8 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/streaming/finalize.ts +9 -6
- package/src/transport/dispatch/streaming/stream.ts +8 -36
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +63 -0
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +16 -30
- package/src/transport/guards/body-policy.ts +7 -1
- package/src/transport/guards/error-lifecycle.ts +13 -1
- package/src/transport/guards/request-context.ts +22 -14
- package/src/transport/request/preparer.ts +81 -71
- package/src/transport/routing/bounded-lru.ts +14 -0
- package/src/transport/routing/drain-reserve.ts +129 -0
- package/src/transport/routing/plan-gate.ts +7 -1
- package/src/transport/routing/round-robin.ts +60 -0
- package/src/transport/routing/route-catalog.ts +123 -111
- package/src/transport/routing/route-model.ts +51 -9
- package/src/transport/routing/router.ts +393 -32
- package/src/transport/surface/adapters.ts +11 -2
- package/src/transport/surface/chat/adapter.ts +2 -2
- package/src/transport/surface/chat/encode.ts +47 -4
- package/src/transport/surface/chat/parse.ts +2 -12
- package/src/transport/surface/completion.ts +44 -33
- package/src/transport/surface/content-parts.ts +21 -0
- package/src/transport/surface/messages/adapter.ts +7 -1
- package/src/transport/surface/messages/encode.ts +11 -3
- package/src/transport/surface/messages/parse.ts +2 -1
- package/src/transport/surface/messages/stream.ts +33 -17
- package/src/transport/surface/responses/encode.ts +22 -5
- package/src/transport/surface/responses/parse.ts +3 -11
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- package/src/providers/operations/model-discovery-cache.ts +0 -34
- package/src/providers/operations/probe-fetch.ts +0 -15
- package/src/providers/operations/provider-version-cache.ts +0 -30
- package/src/providers/provider-capabilities.ts +0 -33
- package/src/providers/provider-credential-envelope.ts +0 -43
- /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-DIXLjojg.js} +0 -0
- /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-DIXLjojg.js.gz} +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic streaming prefix-buffer parser for in-band tool-call markup.
|
|
3
|
+
*
|
|
4
|
+
* Three grammars (DSML blocks, OMP `<tool_calls:8hex>` envelopes, Qwen
|
|
5
|
+
* `<tool_call>` JSON blocks) share one stateful loop: append chunks to a
|
|
6
|
+
* pending buffer, scan for the earliest opener, settle visible text up to a
|
|
7
|
+
* safe end that holds back a possible trailing opener fragment, parse a
|
|
8
|
+
* complete block when its closer appears, fail open past a block-size cap, and
|
|
9
|
+
* flush the tail. The grammar-specific parts are supplied as configuration:
|
|
10
|
+
* opener strings, an opener classifier (some grammars must validate beyond the
|
|
11
|
+
* bare prefix, e.g. OMP's hex marker), closer/parse functions, an unclosed
|
|
12
|
+
* block policy (hold vs fail-open pass-through), a flush policy (pass through
|
|
13
|
+
* vs discard an unterminated envelope), and a call-id factory. Each parser
|
|
14
|
+
* instance keeps its own buffer; nothing is shared.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Result of one push/flush pass: text that settled plus recovered calls. */
|
|
18
|
+
export interface MarkupStreamParseResult<Call extends { readonly id: string }> {
|
|
19
|
+
readonly text: string;
|
|
20
|
+
readonly calls: readonly Call[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Raw call shape produced by a grammar's block parser, without its id. */
|
|
24
|
+
export type MarkupRawCall<Call extends { readonly id: string }> = Omit<Call, "id">;
|
|
25
|
+
|
|
26
|
+
/** Builds a complete call (id included) from a parsed raw call. */
|
|
27
|
+
export type MarkupCallFactory<Call extends { readonly id: string }> = (
|
|
28
|
+
call: MarkupRawCall<Call>,
|
|
29
|
+
index: number,
|
|
30
|
+
seq: number,
|
|
31
|
+
) => Call;
|
|
32
|
+
|
|
33
|
+
/** Classification of one opener candidate occurrence. */
|
|
34
|
+
export type MarkupOpenerClassification<Call extends { readonly id: string }> =
|
|
35
|
+
| {
|
|
36
|
+
/**
|
|
37
|
+
* The candidate may be the start of a real opener (e.g. a partial hex
|
|
38
|
+
* marker): hold everything from the candidate until a later chunk
|
|
39
|
+
* completes or disproves it.
|
|
40
|
+
*/
|
|
41
|
+
readonly kind: "hold";
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
/**
|
|
45
|
+
* The candidate is definitely not an opener: emit it as literal text
|
|
46
|
+
* (one character) and rescan after it.
|
|
47
|
+
*/
|
|
48
|
+
readonly kind: "literal";
|
|
49
|
+
}
|
|
50
|
+
| {
|
|
51
|
+
readonly kind: "found";
|
|
52
|
+
/** Tag that closes the block opened at this position. */
|
|
53
|
+
readonly closer: string;
|
|
54
|
+
/**
|
|
55
|
+
* Parses one complete block (opener through closer). `undefined` means
|
|
56
|
+
* the block is not valid markup and is passed through as text verbatim.
|
|
57
|
+
*/
|
|
58
|
+
readonly parse: (block: string) => readonly MarkupRawCall<Call>[] | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Call factory for the calls recovered from this block; defaults to the
|
|
61
|
+
* parser-level `callId`. Some grammars derive ids from block-local state
|
|
62
|
+
* (e.g. OMP embeds the envelope marker).
|
|
63
|
+
*/
|
|
64
|
+
readonly callId?: MarkupCallFactory<Call>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export interface MarkupStreamParserOptions<Call extends { readonly id: string }> {
|
|
68
|
+
/**
|
|
69
|
+
* Opener frontier. The earliest occurrence wins; trailing fragments that
|
|
70
|
+
* could start one are held back from settled text.
|
|
71
|
+
*/
|
|
72
|
+
readonly openers: readonly string[];
|
|
73
|
+
/** Classifies an opener candidate found at a scan position. */
|
|
74
|
+
readonly classifyOpener: (
|
|
75
|
+
pending: string,
|
|
76
|
+
index: number,
|
|
77
|
+
opener: string,
|
|
78
|
+
) => MarkupOpenerClassification<Call>;
|
|
79
|
+
/**
|
|
80
|
+
* Policy when a block's closer has not arrived yet. Default `"hold"`: keep
|
|
81
|
+
* the whole pending buffer from the opener until a later chunk completes or
|
|
82
|
+
* disproves the block. Returning `{ kind: "passThrough" }` fails open:
|
|
83
|
+
* everything from the opener is emitted as text immediately (the DSML
|
|
84
|
+
* block-size cap).
|
|
85
|
+
*/
|
|
86
|
+
readonly onUnclosed?: (
|
|
87
|
+
pending: string,
|
|
88
|
+
openIndex: number,
|
|
89
|
+
) => "hold" | { readonly kind: "passThrough" };
|
|
90
|
+
/**
|
|
91
|
+
* Tail policy at flush. Returns the text to emit for whatever remains in the
|
|
92
|
+
* buffer (default: everything, i.e. pass through). A grammar that discards
|
|
93
|
+
* unterminated envelopes returns `""` when the tail looks like an opener.
|
|
94
|
+
*/
|
|
95
|
+
readonly flush?: (pending: string) => string;
|
|
96
|
+
/** Call factory for recovered calls, unless a block overrides it. */
|
|
97
|
+
readonly callId?: MarkupCallFactory<Call>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class MarkupStreamParser<Call extends { readonly id: string }> {
|
|
101
|
+
readonly #options: MarkupStreamParserOptions<Call>;
|
|
102
|
+
#pending = "";
|
|
103
|
+
#callSequence = 0;
|
|
104
|
+
|
|
105
|
+
constructor(options: MarkupStreamParserOptions<Call>) {
|
|
106
|
+
this.#options = options;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Feeds one chunk and returns only settled text and complete calls. */
|
|
110
|
+
push(chunk: string): MarkupStreamParseResult<Call> {
|
|
111
|
+
this.#pending += chunk;
|
|
112
|
+
let cursor = 0;
|
|
113
|
+
let visible = "";
|
|
114
|
+
const calls: Call[] = [];
|
|
115
|
+
for (;;) {
|
|
116
|
+
let openIndex = -1;
|
|
117
|
+
let opener = "";
|
|
118
|
+
for (const candidate of this.#options.openers) {
|
|
119
|
+
const index = this.#pending.indexOf(candidate, cursor);
|
|
120
|
+
if (index >= 0 && (openIndex < 0 || index < openIndex)) {
|
|
121
|
+
openIndex = index;
|
|
122
|
+
opener = candidate;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (openIndex < 0) {
|
|
126
|
+
const safeEnd = this.#settledEnd(cursor);
|
|
127
|
+
visible += this.#pending.slice(cursor, safeEnd);
|
|
128
|
+
cursor = safeEnd;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
visible += this.#pending.slice(cursor, openIndex);
|
|
132
|
+
const classified = this.#options.classifyOpener(this.#pending, openIndex, opener);
|
|
133
|
+
if (classified.kind === "literal") {
|
|
134
|
+
visible += "<";
|
|
135
|
+
cursor = openIndex + 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (classified.kind === "hold") {
|
|
139
|
+
cursor = openIndex;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
const closeIndex = this.#pending.indexOf(classified.closer, openIndex + opener.length);
|
|
143
|
+
if (closeIndex < 0) {
|
|
144
|
+
const decision = this.#options.onUnclosed?.(this.#pending, openIndex) ?? "hold";
|
|
145
|
+
if (decision === "hold") {
|
|
146
|
+
cursor = openIndex;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
visible += this.#pending.slice(openIndex);
|
|
150
|
+
cursor = this.#pending.length;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
const end = closeIndex + classified.closer.length;
|
|
154
|
+
const block = this.#pending.slice(openIndex, end);
|
|
155
|
+
const parsed = classified.parse(block);
|
|
156
|
+
if (parsed === undefined) {
|
|
157
|
+
visible += block;
|
|
158
|
+
} else {
|
|
159
|
+
const callFor = classified.callId ?? this.#options.callId;
|
|
160
|
+
if (callFor === undefined) {
|
|
161
|
+
throw new Error("MarkupStreamParser: no call factory configured");
|
|
162
|
+
}
|
|
163
|
+
parsed.forEach((call, index) => {
|
|
164
|
+
calls.push(callFor(call, index, this.#callSequence++));
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
cursor = end;
|
|
168
|
+
}
|
|
169
|
+
this.#pending = this.#pending.slice(cursor);
|
|
170
|
+
return { text: visible, calls };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Settles trailing text per the configured flush policy. */
|
|
174
|
+
flush(): MarkupStreamParseResult<Call> {
|
|
175
|
+
const text = this.#options.flush?.(this.#pending) ?? this.#pending;
|
|
176
|
+
this.#pending = "";
|
|
177
|
+
return { text, calls: [] };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Holds back a trailing fragment that could be the start of an opener. */
|
|
181
|
+
#settledEnd(start: number): number {
|
|
182
|
+
const remaining = this.#pending.slice(start);
|
|
183
|
+
const maxOpener = this.#options.openers.reduce(
|
|
184
|
+
(max, open) => Math.max(max, open.length - 1),
|
|
185
|
+
0,
|
|
186
|
+
);
|
|
187
|
+
for (let length = Math.min(maxOpener, remaining.length); length > 0; length -= 1) {
|
|
188
|
+
const suffix = remaining.slice(-length);
|
|
189
|
+
if (this.#options.openers.some((open) => open.startsWith(suffix))) {
|
|
190
|
+
return this.#pending.length - length;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return this.#pending.length;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -28,3 +28,22 @@ export function reasoningTextOf(part: ReasoningTextSource): string | undefined {
|
|
|
28
28
|
if (typeof summary === "string" && summary.length > 0) return summary;
|
|
29
29
|
return undefined;
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The one reader for reasoning text replayed to upstream providers.
|
|
34
|
+
*
|
|
35
|
+
* Request builders echo the previous turn's reasoning back to a provider, and
|
|
36
|
+
* they deliberately prefer `summary` (the digest) ahead of `payload` (the
|
|
37
|
+
* stream) — the opposite of `reasoningTextOf`, which is for display. Returning
|
|
38
|
+
* `undefined` when neither field is a string lets each call site keep its exact
|
|
39
|
+
* fallback ("" for the chat/messages/gemini builders, the raw payload for
|
|
40
|
+
* Qoder's echo), so upstream-visible bytes do not change. Opaque parts are not
|
|
41
|
+
* special-cased here: the replay readers never consulted `opaque` either.
|
|
42
|
+
*/
|
|
43
|
+
export function reasoningTextForReplay(part: ReasoningTextSource): string | undefined {
|
|
44
|
+
const summary = part.summary;
|
|
45
|
+
if (typeof summary === "string") return summary;
|
|
46
|
+
const payload = part.payload;
|
|
47
|
+
if (typeof payload === "string") return payload;
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
@@ -16,6 +16,7 @@ import { clampThinkingLevelForModel } from "../../protocol/translation/dialect/m
|
|
|
16
16
|
import { isClaudeBillingHeaderText, resolveImageSource } from "../codecs";
|
|
17
17
|
import { resolvePromptCacheKey } from "../../providers/operations/session-resolution";
|
|
18
18
|
import { reasoningEffortFromIntent } from "../../protocol/reasoning";
|
|
19
|
+
import { reasoningTextForReplay } from "../reasoning-text";
|
|
19
20
|
import { parseChatResponseToEvents, decodeChatSseStream } from "../response/chat";
|
|
20
21
|
|
|
21
22
|
/** Fallback when no resolved policy is supplied — today's exact behavior. */
|
|
@@ -220,6 +221,13 @@ export function canonicalToChatPayload(
|
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
223
|
for (const m of request.messages) {
|
|
224
|
+
// Reasoning parts and their presence are computed once per message and
|
|
225
|
+
// shared by every branch below; the presence gate is on the *existence* of
|
|
226
|
+
// a reasoning part, not on non-empty text (see the comment at the first use).
|
|
227
|
+
const reasoningParts = m.content.filter(
|
|
228
|
+
(c): c is Extract<typeof c, { kind: "reasoning" }> => c.kind === "reasoning",
|
|
229
|
+
);
|
|
230
|
+
const hasReasoning = reasoningParts.length > 0;
|
|
223
231
|
if (m.role === "assistant" && m.content.some((c) => c.kind === "toolCall")) {
|
|
224
232
|
const toolCalls = m.content
|
|
225
233
|
.filter((c): c is Extract<typeof c, { kind: "toolCall" }> => c.kind === "toolCall")
|
|
@@ -251,16 +259,12 @@ export function canonicalToChatPayload(
|
|
|
251
259
|
// thinking mode" (CodeBuddy 400). Chat Completions has no
|
|
252
260
|
// reasoning content part, so it travels in the provider-native
|
|
253
261
|
// `reasoning_content` side channel, same as the branches below.
|
|
254
|
-
const reasoningContent =
|
|
255
|
-
.filter((c): c is Extract<typeof c, { kind: "reasoning" }> => c.kind === "reasoning")
|
|
256
|
-
.map((c) => c.summary ?? (typeof c.payload === "string" ? c.payload : ""))
|
|
257
|
-
.join("\n");
|
|
262
|
+
const reasoningContent = reasoningParts.map((c) => reasoningTextForReplay(c) ?? "").join("\n");
|
|
258
263
|
// Gated on the *presence* of a reasoning part, not on non-empty text:
|
|
259
264
|
// with `display: "omitted"` the provider emits a thinking block whose
|
|
260
265
|
// text is empty but whose signature marks the turn as a thinking turn,
|
|
261
266
|
// and the upstream still demands the field back. Omitting it there
|
|
262
267
|
// reproduces the same 400, so an empty string is the correct value.
|
|
263
|
-
const hasReasoning = m.content.some((c) => c.kind === "reasoning");
|
|
264
268
|
messages.push({
|
|
265
269
|
role: "assistant",
|
|
266
270
|
content: effectiveContent,
|
|
@@ -295,13 +299,9 @@ export function canonicalToChatPayload(
|
|
|
295
299
|
content: p.is_error === true ? `[tool_error] ${resultContent}` : resultContent,
|
|
296
300
|
});
|
|
297
301
|
}
|
|
298
|
-
const reasoningContent =
|
|
299
|
-
.filter((c): c is Extract<typeof c, { kind: "reasoning" }> => c.kind === "reasoning")
|
|
300
|
-
.map((c) => c.summary ?? (typeof c.payload === "string" ? c.payload : ""))
|
|
301
|
-
.join("\n");
|
|
302
|
+
const reasoningContent = reasoningParts.map((c) => reasoningTextForReplay(c) ?? "").join("\n");
|
|
302
303
|
// Presence, not length: an `omitted`-display thinking block carries an
|
|
303
304
|
// empty text with a signature, and the field is still required back.
|
|
304
|
-
const hasReasoning = m.content.some((c) => c.kind === "reasoning");
|
|
305
305
|
const hasRichContent = m.content.some(
|
|
306
306
|
(c) => c.kind === "image" || c.kind === "file" || c.kind === "audio" || c.kind === "document",
|
|
307
307
|
);
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type RewriteWalk,
|
|
16
16
|
} from "../codecs";
|
|
17
17
|
import { geminiThinkingOutputFloor } from "../../protocol/reasoning";
|
|
18
|
+
import { reasoningTextForReplay } from "../reasoning-text";
|
|
18
19
|
import { base64DecodedByteLength, clampImage } from "../image-clamp";
|
|
19
20
|
|
|
20
21
|
export function geminiModelUrl(base: string, model: string, action: string): string {
|
|
@@ -151,8 +152,13 @@ function toGeminiPart(
|
|
|
151
152
|
): Record<string, unknown>[] {
|
|
152
153
|
if (part.kind === "text") return [{ text: part.text }];
|
|
153
154
|
if (part.kind === "reasoning") {
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
// A `thinking` part must carry a non-empty signature, or Gemini-family
|
|
156
|
+
// upstreams reject the whole request with `400 ... thinking.signature:
|
|
157
|
+
// Field required`. Drop unsigned reasoning blocks (missing *or* empty
|
|
158
|
+
// signature) so the conversation replays cleanly.
|
|
159
|
+
if (typeof part.signature !== "string" || part.signature.length === 0) return [];
|
|
160
|
+
const summary = reasoningTextForReplay(part) ?? "";
|
|
161
|
+
return [{ text: summary, thought: true, thoughtSignature: part.signature }];
|
|
156
162
|
}
|
|
157
163
|
if (part.kind === "image") {
|
|
158
164
|
// Resolution goes through the shared `resolveImageSource` primitive rather
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
splitDataUrl,
|
|
28
28
|
finiteNumber,
|
|
29
29
|
} from "../codecs";
|
|
30
|
+
import { reasoningTextForReplay } from "../reasoning-text";
|
|
30
31
|
import { log } from "../../observability/logger";
|
|
31
32
|
import { base64DecodedByteLength, clampImage } from "../image-clamp";
|
|
32
33
|
import { LEVEL_TO_BUDGET } from "../../protocol/translation/thinking";
|
|
@@ -374,7 +375,7 @@ function partToClaudeBlock(
|
|
|
374
375
|
!part.opaque &&
|
|
375
376
|
(typeof part.signature !== "string" || part.signature.trim().length === 0)
|
|
376
377
|
) {
|
|
377
|
-
const text =
|
|
378
|
+
const text = reasoningTextForReplay(part) ?? "";
|
|
378
379
|
return text.length > 0 ? { type: "text", text: toWellFormedString(text) } : undefined;
|
|
379
380
|
}
|
|
380
381
|
if (part.opaque) {
|
|
@@ -384,9 +385,7 @@ function partToClaudeBlock(
|
|
|
384
385
|
}
|
|
385
386
|
return {
|
|
386
387
|
type: "thinking",
|
|
387
|
-
thinking: toWellFormedString(
|
|
388
|
-
part.summary ?? (stringValue(part.payload) ? part.payload : ""),
|
|
389
|
-
),
|
|
388
|
+
thinking: toWellFormedString(reasoningTextForReplay(part) ?? ""),
|
|
390
389
|
...(part.signature === undefined
|
|
391
390
|
? {}
|
|
392
391
|
: { signature: toWellFormedString(part.signature) }),
|
|
@@ -9,7 +9,7 @@ import { gatewayErrorFromStreamError } from "../stream-error-frames";
|
|
|
9
9
|
import { mergeResponsesUsage } from "./responses";
|
|
10
10
|
import { usageFromProvider, readReasoningText } from "../../providers/provider-usage";
|
|
11
11
|
import { createToolEmitLedger } from "../tool-identity";
|
|
12
|
-
import { ThinkSplitter, stripThinkingTags } from "../think-split";
|
|
12
|
+
import { ThinkingTagStripper, ThinkSplitter, stripThinkingTags } from "../think-split";
|
|
13
13
|
import { DsmlStreamParser, parseDsmlToolCalls } from "../dsml";
|
|
14
14
|
import {
|
|
15
15
|
OmpToolCallMarkupParser,
|
|
@@ -267,10 +267,13 @@ export async function* decodeChatSseStream(
|
|
|
267
267
|
// Single emission ledger: one entry per logical call.
|
|
268
268
|
const ledger = createToolEmitLedger();
|
|
269
269
|
const think = new ThinkSplitter();
|
|
270
|
-
// Separate
|
|
270
|
+
// Separate stripper for the reasoning channel: `reasoning_content` is its own
|
|
271
271
|
// stream, and interleaving it into the answer splitter would let a tag in one
|
|
272
|
-
// channel swallow the other's text.
|
|
273
|
-
|
|
272
|
+
// channel swallow the other's text. The reasoning channel is thought in its
|
|
273
|
+
// entirety — untagged prose included — so it strips paired tags statefully
|
|
274
|
+
// while preserving source order instead of splitting into text/reasoning
|
|
275
|
+
// buckets that a `${reasoning}${text}` join would reorder.
|
|
276
|
+
const reasoningThink = new ThinkingTagStripper();
|
|
274
277
|
const declaredToolNames = declaredToolNamesForRequest(request);
|
|
275
278
|
const callableToolNames = callableToolNamesForRequest(request);
|
|
276
279
|
const contentMarkup =
|
|
@@ -364,21 +367,13 @@ export async function* decodeChatSseStream(
|
|
|
364
367
|
typeof reasoningRaw === "string" ? reasoningMarkup?.push(reasoningRaw) : undefined;
|
|
365
368
|
// Strip stray `<think>` tags the upstream double-wrapped inside its own
|
|
366
369
|
// reasoning stream (buddy's hy4-preview-f leaks them per-delta). This
|
|
367
|
-
// is the
|
|
370
|
+
// is the stripper's job, not `stripThinkingTags`'s: a stateless strip
|
|
368
371
|
// cannot see a tag split across two deltas, so `"<thi"` was emitted
|
|
369
372
|
// verbatim and only the following `</think>` removed.
|
|
370
|
-
const
|
|
373
|
+
const reasoningText =
|
|
371
374
|
typeof reasoningRaw === "string"
|
|
372
375
|
? reasoningThink.push(reasoningMarkupResult?.text ?? reasoningRaw)
|
|
373
376
|
: undefined;
|
|
374
|
-
// Both sides are reasoning. The splitter routes text outside a
|
|
375
|
-
// `<think>` span to its `.text` side, but this field is the reasoning
|
|
376
|
-
// stream in its entirety: untagged prose here is still thought, not
|
|
377
|
-
// answer. Taking only `.reasoning` dropped every un-tagged delta.
|
|
378
|
-
const reasoningText =
|
|
379
|
-
reasoningSplit === undefined
|
|
380
|
-
? undefined
|
|
381
|
-
: `${reasoningSplit.reasoning}${reasoningSplit.text}`;
|
|
382
377
|
if (typeof reasoningText === "string" && reasoningText.length > 0) {
|
|
383
378
|
yield {
|
|
384
379
|
type: "content_delta",
|
|
@@ -538,9 +533,7 @@ export async function* decodeChatSseStream(
|
|
|
538
533
|
}
|
|
539
534
|
const reasoningMarkupTail = reasoningMarkup?.flush();
|
|
540
535
|
if (reasoningMarkupTail?.text) {
|
|
541
|
-
const
|
|
542
|
-
// Both sides are reasoning: see the per-delta branch above.
|
|
543
|
-
const reasoningText = `${split.reasoning}${split.text}`;
|
|
536
|
+
const reasoningText = reasoningThink.push(reasoningMarkupTail.text);
|
|
544
537
|
if (reasoningText.length > 0) {
|
|
545
538
|
yield {
|
|
546
539
|
type: "content_delta",
|
|
@@ -549,10 +542,9 @@ export async function* decodeChatSseStream(
|
|
|
549
542
|
} as CanonicalEvent;
|
|
550
543
|
}
|
|
551
544
|
}
|
|
552
|
-
// Release anything the reasoning
|
|
545
|
+
// Release anything the reasoning stripper held for a tag that never
|
|
553
546
|
// completed, before `think.flush()` and the terminal.
|
|
554
|
-
const
|
|
555
|
-
const reasoningTail = `${reasoningRest.reasoning}${reasoningRest.text}`;
|
|
547
|
+
const reasoningTail = reasoningThink.flush();
|
|
556
548
|
if (reasoningTail.length > 0) {
|
|
557
549
|
yield {
|
|
558
550
|
type: "content_delta",
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* interpreted. Calls are returned only when their names were declared on the
|
|
8
8
|
* request; an undeclared name is never promoted into executable tool input.
|
|
9
9
|
*/
|
|
10
|
+
import { MarkupStreamParser } from "../markup-stream-parser";
|
|
10
11
|
export interface OmpTextToolCall {
|
|
11
12
|
readonly id: string;
|
|
12
13
|
readonly name: string;
|
|
@@ -29,77 +30,49 @@ const TOOL_NAME_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,63}$/;
|
|
|
29
30
|
* The parser holds a possible envelope across SSE deltas, so a split opening
|
|
30
31
|
* tag, argument tag, or closing tag is never emitted as visible prose. At
|
|
31
32
|
* end-of-stream an unterminated envelope is dropped, not executed or leaked.
|
|
33
|
+
*
|
|
34
|
+
* The stateful prefix-buffer loop is the shared {@link MarkupStreamParser}
|
|
35
|
+
* scaffold; this class supplies the OMP grammar as a thin configuration.
|
|
32
36
|
*/
|
|
33
37
|
export class OmpToolCallMarkupParser {
|
|
34
|
-
readonly #
|
|
35
|
-
#pending = "";
|
|
36
|
-
#callSequence = 0;
|
|
38
|
+
readonly #parser: MarkupStreamParser<OmpTextToolCall>;
|
|
37
39
|
|
|
38
40
|
constructor(allowedNames: ReadonlySet<string>) {
|
|
39
|
-
this.#
|
|
41
|
+
this.#parser = new MarkupStreamParser<OmpTextToolCall>({
|
|
42
|
+
openers: [OPEN_PREFIX],
|
|
43
|
+
classifyOpener: (pending, openIndex) => {
|
|
44
|
+
const remainder = pending.slice(openIndex);
|
|
45
|
+
const opening = OPEN_PATTERN.exec(remainder);
|
|
46
|
+
if (opening === null || opening[1] === undefined) {
|
|
47
|
+
if (couldBeOpening(remainder)) return { kind: "hold" };
|
|
48
|
+
return { kind: "literal" };
|
|
49
|
+
}
|
|
50
|
+
const marker = opening[1];
|
|
51
|
+
const closeTag = `</tool_calls:${marker}>`;
|
|
52
|
+
return {
|
|
53
|
+
kind: "found",
|
|
54
|
+
closer: closeTag,
|
|
55
|
+
parse: (block) => {
|
|
56
|
+
const parsed = parseEnvelope(block, marker);
|
|
57
|
+
if (parsed === undefined) return undefined;
|
|
58
|
+
if (parsed.some((call) => !allowedNames.has(call.name))) {
|
|
59
|
+
// A syntactically valid call to an undeclared tool is model
|
|
60
|
+
// text, not authority to invoke a tool the client did not offer.
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
return parsed;
|
|
64
|
+
},
|
|
65
|
+
callId: (call, _index, seq) => ({ ...call, id: `call_omp_${marker}_${seq}` }),
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
flush: (pending) =>
|
|
69
|
+
couldBeOpening(pending) || OPEN_PATTERN.test(pending) ? "" : pending,
|
|
70
|
+
});
|
|
40
71
|
}
|
|
41
72
|
|
|
42
73
|
/** Feeds one content or reasoning delta and returns only settled text. */
|
|
43
74
|
push(chunk: string): OmpTextToolCallResult {
|
|
44
|
-
this.#
|
|
45
|
-
let cursor = 0;
|
|
46
|
-
let text = "";
|
|
47
|
-
const calls: OmpTextToolCall[] = [];
|
|
48
|
-
|
|
49
|
-
for (;;) {
|
|
50
|
-
const openIndex = this.#pending.indexOf(OPEN_PREFIX, cursor);
|
|
51
|
-
if (openIndex < 0) {
|
|
52
|
-
const safeEnd = this.#settledEnd(cursor);
|
|
53
|
-
text += this.#pending.slice(cursor, safeEnd);
|
|
54
|
-
cursor = safeEnd;
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
text += this.#pending.slice(cursor, openIndex);
|
|
59
|
-
const remainder = this.#pending.slice(openIndex);
|
|
60
|
-
const opening = OPEN_PATTERN.exec(remainder);
|
|
61
|
-
if (opening === null || opening[1] === undefined) {
|
|
62
|
-
if (this.#couldBeOpening(remainder)) {
|
|
63
|
-
cursor = openIndex;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
text += "<";
|
|
67
|
-
cursor = openIndex + 1;
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const marker = opening[1];
|
|
72
|
-
const openTag = opening[0];
|
|
73
|
-
const closeTag = `</tool_calls:${marker}>`;
|
|
74
|
-
const closeIndex = this.#pending.indexOf(closeTag, openIndex + openTag.length);
|
|
75
|
-
if (closeIndex < 0) {
|
|
76
|
-
cursor = openIndex;
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const envelopeEnd = closeIndex + closeTag.length;
|
|
81
|
-
const block = this.#pending.slice(openIndex, envelopeEnd);
|
|
82
|
-
const parsed = parseEnvelope(block, marker);
|
|
83
|
-
if (parsed === undefined) {
|
|
84
|
-
text += block;
|
|
85
|
-
} else if (parsed.some((call) => !this.#allowedNames.has(call.name))) {
|
|
86
|
-
// A syntactically valid call to an undeclared tool is model text, not
|
|
87
|
-
// authority to invoke a tool the client did not offer.
|
|
88
|
-
text += block;
|
|
89
|
-
} else {
|
|
90
|
-
for (const call of parsed) {
|
|
91
|
-
calls.push({
|
|
92
|
-
id: `call_omp_${marker}_${this.#callSequence++}`,
|
|
93
|
-
name: call.name,
|
|
94
|
-
arguments: call.arguments,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
cursor = envelopeEnd;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
this.#pending = this.#pending.slice(cursor);
|
|
102
|
-
return { text, calls };
|
|
75
|
+
return this.#parser.push(chunk);
|
|
103
76
|
}
|
|
104
77
|
|
|
105
78
|
/**
|
|
@@ -107,31 +80,15 @@ export class OmpToolCallMarkupParser {
|
|
|
107
80
|
* an unfinished envelope is deliberately discarded rather than leaked.
|
|
108
81
|
*/
|
|
109
82
|
flush(): OmpTextToolCallResult {
|
|
110
|
-
|
|
111
|
-
this.#pending = "";
|
|
112
|
-
if (this.#couldBeOpening(pending) || OPEN_PATTERN.test(pending)) {
|
|
113
|
-
return { text: "", calls: [] };
|
|
114
|
-
}
|
|
115
|
-
return { text: pending, calls: [] };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
#settledEnd(start: number): number {
|
|
119
|
-
const remaining = this.#pending.slice(start);
|
|
120
|
-
const maxPrefixLength = Math.min(OPEN_PREFIX.length - 1, remaining.length);
|
|
121
|
-
for (let length = maxPrefixLength; length > 0; length -= 1) {
|
|
122
|
-
if (OPEN_PREFIX.startsWith(remaining.slice(-length))) {
|
|
123
|
-
return this.#pending.length - length;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return this.#pending.length;
|
|
83
|
+
return this.#parser.flush();
|
|
127
84
|
}
|
|
85
|
+
}
|
|
128
86
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
87
|
+
function couldBeOpening(value: string): boolean {
|
|
88
|
+
if (value.length < OPEN_PREFIX.length) return OPEN_PREFIX.startsWith(value);
|
|
89
|
+
if (!value.startsWith(OPEN_PREFIX)) return false;
|
|
90
|
+
const suffix = value.slice(OPEN_PREFIX.length);
|
|
91
|
+
return /^[0-9a-f]{0,8}$/.test(suffix) || /^[0-9a-f]{8}$/.test(suffix) && !value.endsWith(">");
|
|
135
92
|
}
|
|
136
93
|
|
|
137
94
|
/**
|
|
@@ -274,7 +274,16 @@ export function parseResponsesResponseToEvents(
|
|
|
274
274
|
canonicalTerminal({
|
|
275
275
|
sequenceNumber: events.length + 1,
|
|
276
276
|
responseId: id,
|
|
277
|
-
|
|
277
|
+
// `incomplete` maps to `aborted` (not `complete`): the surface encoder
|
|
278
|
+
// turns `aborted` into `response.incomplete`, while `complete` would
|
|
279
|
+
// emit `response.completed` for a response the upstream declared
|
|
280
|
+
// unfinished (max output tokens / content filter).
|
|
281
|
+
state:
|
|
282
|
+
status === "cancelled" || status === "incomplete"
|
|
283
|
+
? "aborted"
|
|
284
|
+
: status === "failed"
|
|
285
|
+
? "failed"
|
|
286
|
+
: "complete",
|
|
278
287
|
stopReason: respJsonStopReason,
|
|
279
288
|
providerStopReason: respJsonProviderStopReason,
|
|
280
289
|
usage: usageFromProvider(usage),
|
|
@@ -740,8 +749,15 @@ export async function* decodeResponsesSseStream(
|
|
|
740
749
|
const truncated = status === undefined;
|
|
741
750
|
yield canonicalTerminal({
|
|
742
751
|
sequenceNumber: seq++,
|
|
752
|
+
// `incomplete` maps to `aborted` (not `complete`): the surface encoder
|
|
753
|
+
// turns `aborted` into `response.incomplete`, while `complete` would
|
|
754
|
+
// emit `response.completed` for a stream the upstream declared unfinished.
|
|
743
755
|
state:
|
|
744
|
-
status === "cancelled"
|
|
756
|
+
status === "cancelled" || status === "incomplete"
|
|
757
|
+
? "aborted"
|
|
758
|
+
: status === "failed" || truncated
|
|
759
|
+
? "failed"
|
|
760
|
+
: "complete",
|
|
745
761
|
stopReason: responsesStopReason,
|
|
746
762
|
providerStopReason: responsesProviderStopReason,
|
|
747
763
|
usage: usageFromProvider(rawUsage),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CanonicalStopReason } from "./canonical-model";
|
|
1
|
+
import type { CanonicalStopReason, CanonicalTerminalEvent } from "./canonical-model";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Canonical stop reason mapped onto each surface's wire vocabulary.
|
|
@@ -56,6 +56,28 @@ export function chatFinishReason(reason: CanonicalStopReason | undefined): strin
|
|
|
56
56
|
return canonicalStopReasonToWire("chat", reason, "stop");
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Completion's finish_reason, or `null` when the terminal recorded none.
|
|
61
|
+
*
|
|
62
|
+
* Completion inherits chat's wire vocabulary (`length`, `content_filter`,
|
|
63
|
+
* `stop`) but keeps its own convention: a terminal that is not complete
|
|
64
|
+
* (failed/aborted) already surfaced its failure elsewhere, so it yields
|
|
65
|
+
* `null` rather than a stop word, and a complete terminal flattens every
|
|
66
|
+
* reason other than `length`/`content_filter` to `stop`. The sync adapter and
|
|
67
|
+
* the streaming encoder both derive through this function so the two halves
|
|
68
|
+
* of the completion surface cannot drift.
|
|
69
|
+
*/
|
|
70
|
+
export function completionFinishReason(
|
|
71
|
+
reason: CanonicalStopReason | undefined,
|
|
72
|
+
state: CanonicalTerminalEvent["state"],
|
|
73
|
+
): string | null {
|
|
74
|
+
if (state !== "complete") return null;
|
|
75
|
+
if (reason === "length" || reason === "content_filter") {
|
|
76
|
+
return canonicalStopReasonToWire("chat", reason, "stop");
|
|
77
|
+
}
|
|
78
|
+
return "stop";
|
|
79
|
+
}
|
|
80
|
+
|
|
59
81
|
/** Messages' stop_reason, or `null` when the terminal recorded none. */
|
|
60
82
|
export function messagesStopReason(reason: CanonicalStopReason | undefined): string | null {
|
|
61
83
|
return reason === undefined ? null : canonicalStopReasonToWire("messages", reason, "end_turn");
|