cartethyia 2.7.8 → 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 -3
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js +1 -0
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-CXsaOAO2.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js +1 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js +1 -0
- 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-Cv-2Ol_K.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-CgoPc5B9.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-D9bFQern.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js +1 -0
- 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-D_0PiZtg.js +1 -0
- 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-emlTuxz6.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-CctTga2C.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-febLU4r1.js} +1 -1
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-M6fvR5Wz.js} +1 -1
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js +56 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js +1 -0
- 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-CidQVoNo.js → assert-CqTLw5e7.js} +1 -1
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-C83EqLB0.js} +1 -1
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
- package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-D1xn7pIe.js} +1 -1
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/favicon.webp +0 -0
- package/dist/dashboard/assets/brand/login_register.webp +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
- package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/og_image.webp +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
- package/dist/dashboard/assets/{card-_HwAEqFF.js → card-DU3Lq5Lm.js} +1 -1
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js +1 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.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-BZhOlfz-.js → dialog-BjFR5UHy.js} +1 -1
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BADqIRYP.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-COaC-irN.js → entry-CECVELyy.js} +1 -1
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js +1 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-DLW4i8zK.js} +1 -1
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/{index-CgKmFngv.js → index-1-fCqe64.js} +2 -2
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2BirKry.js → inline-DGvU2oLK.js} +1 -1
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/{input-D-qV9dKY.js → input-iNnO1X2K.js} +1 -1
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-0q8UGypb.js} +1 -1
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-C0w2bQjJ.js} +1 -1
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/{live-DGizF06-.js → live-M5aJ_xCa.js} +1 -1
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
- package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
- package/dist/dashboard/assets/{network-C9UE-bOr.js → network-BKeQGT3K.js} +1 -1
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-CivAk3da.js} +1 -1
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-CpzvI5ep.js} +1 -1
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
- package/dist/dashboard/assets/providers/devin.svg +9 -0
- package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
- package/dist/dashboard/assets/providers/windsurf.svg +10 -0
- package/dist/dashboard/assets/providers/windsurf.svg.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/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
- package/dist/dashboard/assets/query-keys-IKLh0YFd.js.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-BFQX2VBc.js +1 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/{select-BCOcv8WK.js → select-CXbck0jG.js} +2 -2
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js +1 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DggRiCxE.js} +1 -1
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/{state-DXbGTx7N.js → state-ClFzfbYp.js} +1 -1
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/story/25817331.webp +0 -0
- package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
- package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
- package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
- package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
- package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
- package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
- package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
- package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-CTIFxKqS.js} +1 -1
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BClG3yWj.js} +1 -1
- package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.webp +0 -0
- package/dist/dashboard/console-backgrounds_initial.webp +0 -0
- package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +59 -56
- package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
- package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
- package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -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 +12 -4
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/cooldowns.ts +1 -1
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +20 -6
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +50 -31
- 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/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -2
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +93 -0
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -0
- package/src/console/providers/catalog/discover-constants.ts +6 -0
- package/src/console/providers/catalog/model-operations.ts +122 -10
- package/src/console/providers/catalog/provider-operations.ts +116 -8
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +65 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +249 -4
- package/src/console/quota/account-quota-global.ts +97 -19
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +6 -18
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/operations.ts +31 -46
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/shared/errors.ts +33 -6
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +3 -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/event-query.ts +18 -4
- 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 +30 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +28 -2
- package/src/persistence/ttl-cache.ts +20 -2
- package/src/protocol/codecs.ts +1 -1
- package/src/protocol/dsml.ts +35 -87
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +14 -14
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +4 -5
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/stream-error-frames.ts +13 -3
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/protocol/translation/quirks.ts +1 -1
- package/src/protocol/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +15 -4
- package/src/protocol/upstream-parsing.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +78 -17
- 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 +153 -59
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +117 -27
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +74 -33
- package/src/providers/discovery/probing-service.ts +293 -165
- 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 +121 -2
- package/src/providers/integrations/antigravity/antigravity.ts +13 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +191 -35
- package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
- package/src/providers/integrations/buddy/buddy-identity.ts +89 -6
- package/src/providers/integrations/buddy/buddy-media.ts +4 -5
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
- package/src/providers/integrations/buddy/capabilities.ts +6 -43
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +23 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +38 -27
- 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-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +745 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +35 -20
- package/src/providers/integrations/cursor/cursor-shared.ts +26 -5
- package/src/providers/integrations/cursor/cursor.ts +139 -70
- 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/cursor/protocol/tool-canonical.ts +26 -100
- package/src/providers/integrations/devin/devin-auth.ts +353 -0
- package/src/providers/integrations/devin/devin-catalog.ts +207 -0
- package/src/providers/integrations/devin/devin-http.ts +91 -0
- package/src/providers/integrations/devin/devin-quota.ts +214 -0
- package/src/providers/integrations/devin/devin-shared.ts +111 -0
- package/src/providers/integrations/devin/devin.ts +539 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +558 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory-oauth.ts +17 -44
- package/src/providers/integrations/factory/factory.ts +2 -2
- 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/freepi/freepi-catalog.ts +60 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
- package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
- package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
- package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
- package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
- package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
- package/src/providers/integrations/freepi/freepi.ts +292 -0
- package/src/providers/integrations/gemini.ts +2 -2
- 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/hermes-oauth.ts +94 -0
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +13 -21
- 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/fingerprint.ts +1 -1
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +208 -0
- package/src/providers/integrations/perch/perch-quota.ts +123 -0
- package/src/providers/integrations/perch/perch-realm.ts +82 -0
- package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
- package/src/providers/integrations/perch/perch.ts +305 -0
- package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
- package/src/providers/integrations/qoder/chat-proto.ts +328 -328
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +90 -40
- package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +26 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
- package/src/providers/integrations/qoder/qoder-checkin.ts +6 -6
- package/src/providers/integrations/qoder/qoder-grpc.ts +210 -7
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -108
- package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
- package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
- package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
- package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
- package/src/providers/integrations/qoder/qoder.ts +3 -7
- package/src/providers/integrations/trae/trae-catalog.ts +9 -14
- package/src/providers/integrations/trae/trae-headers.ts +36 -11
- package/src/providers/integrations/trae/trae-oauth.ts +14 -17
- package/src/providers/integrations/trae/trae-protocol.ts +12 -11
- package/src/providers/integrations/trae/trae-quota.ts +96 -36
- package/src/providers/integrations/trae/trae-realm.ts +74 -15
- package/src/providers/integrations/trae/trae.ts +99 -26
- 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 +235 -278
- package/src/providers/operations/account-health-service.ts +273 -26
- package/src/providers/operations/account-refresh-status.ts +269 -0
- package/src/providers/operations/account-tier.ts +66 -0
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +42 -150
- package/src/providers/operations/cooldown-rules.ts +56 -0
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- 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 +33 -2
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +99 -14
- package/src/providers/provider-model-definition.ts +36 -0
- 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 +96 -18
- package/src/security/abuse.ts +71 -0
- 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 +9 -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 +9 -36
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +67 -1
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +129 -37
- 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 +418 -33
- 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 +9 -2
- 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 +34 -6
- package/src/transport/surface/responses/parse.ts +3 -11
- package/src/workers/daily-checkin.ts +1 -1
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/scheduler.ts +22 -4
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js +0 -1
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
- package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
- package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.png +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/favicon.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
- package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
- package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
- package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
- package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
- package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
- package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
- package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
- package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
- package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
- package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
- package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-YSoQOUh6.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-fnCsxgNO.js +0 -1
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
- package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
- package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
- package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
- package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
- package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
- package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
- package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
- 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-CTxoqyYB.js → app-DIXLjojg.js} +0 -0
- /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-DIXLjojg.js.gz} +0 -0
|
@@ -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
|
+
}
|
|
@@ -34,7 +34,7 @@ export function applyOpenAIReasoning(payload: Record<string, unknown>, request:
|
|
|
34
34
|
payload["reasoning_effort"] = effort;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/** Applies the DeepSeek chat contract shared by CodeBuddy
|
|
37
|
+
/** Applies the DeepSeek chat contract shared by CodeBuddy. */
|
|
38
38
|
export function applyDeepSeekReasoning(payload: Record<string, unknown>): void {
|
|
39
39
|
const model = typeof payload["model"] === "string" ? payload["model"].trim().toLowerCase() : "";
|
|
40
40
|
if (!model.startsWith("deepseek")) return;
|
|
@@ -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")
|
|
@@ -248,19 +256,15 @@ export function canonicalToChatPayload(
|
|
|
248
256
|
// to be replayed, and the tool-call turn is exactly where a thinking
|
|
249
257
|
// model emits it — dropping it here made the next request fail with
|
|
250
258
|
// "the reasoning content from the previous turn must be passed back in
|
|
251
|
-
// thinking mode" (
|
|
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
|
);
|
|
@@ -365,7 +365,7 @@ export function canonicalToChatPayload(
|
|
|
365
365
|
// nothing left to say: emitting it as an empty-content `user` message
|
|
366
366
|
// right after the tool results breaks strict upstreams (a bare `user`
|
|
367
367
|
// turn between a result and the next assistant turn reads as a broken
|
|
368
|
-
// tool sequence —
|
|
368
|
+
// tool sequence — CodeBuddy `11148`). Fold the leftover
|
|
369
369
|
// reasoning into the last tool message's side channel when one was
|
|
370
370
|
// just emitted, and skip the turn otherwise; a turn that still
|
|
371
371
|
// carries text or rich parts emits below as usual.
|
|
@@ -382,7 +382,7 @@ export function canonicalToChatPayload(
|
|
|
382
382
|
// or a non-empty serialised payload. Pushing `{content:"",
|
|
383
383
|
// reasoning_content:""}` is the opposite of load-bearing — the
|
|
384
384
|
// upstream reads it as "thinking mode with the reasoning stripped"
|
|
385
|
-
// and rejects the whole request (`
|
|
385
|
+
// and rejects the whole request (`CodeBuddy 11151 "history
|
|
386
386
|
// not found"`). The canonical form that reaches this branch with no
|
|
387
387
|
// trace is an input `reasoning` item whose `summary` was absent and
|
|
388
388
|
// whose `payload` was `null` — a signature-less thinking block
|
|
@@ -570,7 +570,7 @@ export function canonicalToChatPayload(
|
|
|
570
570
|
// Resolved unconditionally, not only when the caller sent a value: the bound
|
|
571
571
|
// table now carries a `defaultMaxTokens` for this wire, and an omitted limit
|
|
572
572
|
// must reach the upstream as a real number. Upstreams that require the field
|
|
573
|
-
// reject a large prompt whose `max_tokens` is absent outright (
|
|
573
|
+
// reject a large prompt whose `max_tokens` is absent outright (CodeBuddy
|
|
574
574
|
// `400 model_param_invalid`, reproduced through the gateway).
|
|
575
575
|
const bounded = resolveWireMaxTokens("chat", { max_tokens: requestedMaxTokens }, {
|
|
576
576
|
...(options.maxOutputTokens === undefined ? {} : { maxOutputTokens: options.maxOutputTokens }),
|
|
@@ -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";
|
|
@@ -39,7 +40,7 @@ import { claudeResponseToEvents, parseClaudeSseStream } from "../response/messag
|
|
|
39
40
|
* Gateway default when the caller sent no max_tokens (Anthropic requires
|
|
40
41
|
* the field). Explicit and warned at the call site — never silent.
|
|
41
42
|
*/
|
|
42
|
-
const ANTHROPIC_DEFAULT_MAX_TOKENS = WIRE_TOKEN_BOUNDS["messages"]?.defaultMaxTokens ?? 4_096;
|
|
43
|
+
export const ANTHROPIC_DEFAULT_MAX_TOKENS = WIRE_TOKEN_BOUNDS["messages"]?.defaultMaxTokens ?? 4_096;
|
|
43
44
|
|
|
44
45
|
/** Anthropic Messages' documented base64-image media-type allowlist. */
|
|
45
46
|
const ANTHROPIC_IMAGE_MEDIA_TYPES = new Set([
|
|
@@ -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",
|
|
@@ -38,15 +38,20 @@ export function responseParts(parts: readonly Record<string, unknown>[]): {
|
|
|
38
38
|
readonly text: string;
|
|
39
39
|
readonly calls: readonly { readonly id: string; readonly name: string; readonly args: Record<string, unknown> }[];
|
|
40
40
|
readonly thought: string;
|
|
41
|
+
readonly thoughtSignature: string;
|
|
41
42
|
} {
|
|
42
43
|
const text: string[] = [];
|
|
43
44
|
const thought: string[] = [];
|
|
45
|
+
let thoughtSignature = "";
|
|
44
46
|
const calls: Array<{ id: string; name: string; args: Record<string, unknown> }> = [];
|
|
45
47
|
let callIndex = 0;
|
|
46
48
|
for (const part of parts) {
|
|
47
49
|
if (typeof part["text"] === "string") {
|
|
48
50
|
if (part["thought"] === true) {
|
|
49
51
|
thought.push(part["text"] as string);
|
|
52
|
+
if (thoughtSignature.length === 0 && typeof part["thoughtSignature"] === "string") {
|
|
53
|
+
thoughtSignature = part["thoughtSignature"] as string;
|
|
54
|
+
}
|
|
50
55
|
} else text.push(part["text"] as string);
|
|
51
56
|
}
|
|
52
57
|
if (isRecord(part["functionCall"]) && typeof (part["functionCall"] as Record<string, unknown>)["name"] === "string") {
|
|
@@ -58,7 +63,7 @@ export function responseParts(parts: readonly Record<string, unknown>[]): {
|
|
|
58
63
|
});
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
|
-
return { text: text.join(""), calls, thought: thought.join("") };
|
|
66
|
+
return { text: text.join(""), calls, thought: thought.join(""), thoughtSignature };
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
export function mapGeminiUsage(body: Record<string, unknown>): Record<string, unknown> {
|
|
@@ -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),
|
|
@@ -93,8 +93,19 @@ function isCompleteJsonValue(data: string): boolean {
|
|
|
93
93
|
// invited a retry of the identical request against a broken upstream. The
|
|
94
94
|
// `upstream` origin keeps the public envelope from prefixing it
|
|
95
95
|
// a gateway-branded message, which blamed the gateway for the provider's bytes.
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
// The message quotes the offending line (bounded) so the client and operator
|
|
97
|
+
// see the actual bytes the upstream sent rather than a generic sentence.
|
|
98
|
+
const MAX_SSE_SNIPPET = 160;
|
|
99
|
+
function snippet(line: string): string {
|
|
100
|
+
const collapsed = line.replace(/[\r\n]+/g, " ").trim();
|
|
101
|
+
if (collapsed.length === 0) return "<empty line>";
|
|
102
|
+
return collapsed.length > MAX_SSE_SNIPPET
|
|
103
|
+
? `${collapsed.slice(0, MAX_SSE_SNIPPET)}…`
|
|
104
|
+
: collapsed;
|
|
105
|
+
}
|
|
106
|
+
function oversized(what: string, line?: string): GatewayError {
|
|
107
|
+
const quote = line === undefined ? "" : `: ${snippet(line)}`;
|
|
108
|
+
return new GatewayError("platform_unavailable", 502, `SSE ${what} exceeds size bound${quote} (request {requestId}). The upstream sent more than we can hold — retry 🌙`, {}, "upstream");
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
export async function* decodeSseEvents(
|
|
@@ -161,7 +172,7 @@ export async function* decodeSseEvents(
|
|
|
161
172
|
const rest = last.subarray(newlineAt + 1);
|
|
162
173
|
if (rest.byteLength > 0) pending.unshift(rest);
|
|
163
174
|
const line = decoder.decode(lineBytes).replace(/\r$/, "");
|
|
164
|
-
if (line.length > maxLineBytes) throw oversized("line");
|
|
175
|
+
if (line.length > maxLineBytes) throw oversized("line", line);
|
|
165
176
|
return line;
|
|
166
177
|
}
|
|
167
178
|
function* dispatchEvent(): Iterable<SseEvent> {
|
|
@@ -188,7 +199,7 @@ export async function* decodeSseEvents(
|
|
|
188
199
|
const piece = line.slice(5).trimStart();
|
|
189
200
|
dataLines.push(piece);
|
|
190
201
|
eventBytes += piece.length;
|
|
191
|
-
if (eventBytes > maxEventBytes) throw oversized("event");
|
|
202
|
+
if (eventBytes > maxEventBytes) throw oversized("event", line);
|
|
192
203
|
return;
|
|
193
204
|
}
|
|
194
205
|
if (line.startsWith("id:") || line.startsWith("retry:")) return;
|
|
@@ -220,10 +231,10 @@ export async function* decodeSseEvents(
|
|
|
220
231
|
if (dataLines.length > 0) {
|
|
221
232
|
dataLines.push(line);
|
|
222
233
|
eventBytes += line.length;
|
|
223
|
-
if (eventBytes > maxEventBytes) throw oversized("event");
|
|
234
|
+
if (eventBytes > maxEventBytes) throw oversized("event", line);
|
|
224
235
|
return;
|
|
225
236
|
}
|
|
226
|
-
throw new GatewayError("platform_unavailable", 502,
|
|
237
|
+
throw new GatewayError("platform_unavailable", 502, `Malformed SSE line: ${snippet(line)} (request {requestId}). The upstream sent something we can't read — retry 🌙`, {}, "upstream");
|
|
227
238
|
}
|
|
228
239
|
|
|
229
240
|
try {
|
|
@@ -233,7 +244,10 @@ export async function* decodeSseEvents(
|
|
|
233
244
|
if (chunk.value && chunk.value.byteLength > 0) {
|
|
234
245
|
pending.push(chunk.value);
|
|
235
246
|
pendingBytes += chunk.value.byteLength;
|
|
236
|
-
if (!hasPendingNewline() && pendingBytes > maxLineBytes)
|
|
247
|
+
if (!hasPendingNewline() && pendingBytes > maxLineBytes) {
|
|
248
|
+
const partial = decoder.decode(new Uint8Array(pendingBytes)).slice(0, MAX_SSE_SNIPPET);
|
|
249
|
+
throw oversized("line", partial);
|
|
250
|
+
}
|
|
237
251
|
}
|
|
238
252
|
let line = takeLine();
|
|
239
253
|
while (line !== undefined) {
|
|
@@ -243,7 +257,10 @@ export async function* decodeSseEvents(
|
|
|
243
257
|
if (chunk.done) break;
|
|
244
258
|
}
|
|
245
259
|
if (pendingBytes > 0) {
|
|
246
|
-
if (pendingBytes > maxLineBytes)
|
|
260
|
+
if (pendingBytes > maxLineBytes) {
|
|
261
|
+
const partial = decoder.decode(new Uint8Array(pendingBytes)).slice(0, MAX_SSE_SNIPPET);
|
|
262
|
+
throw oversized("line", partial);
|
|
263
|
+
}
|
|
247
264
|
const total = new Uint8Array(pendingBytes);
|
|
248
265
|
let offset = 0;
|
|
249
266
|
for (const chunk of pending.splice(0, pending.length)) {
|
|
@@ -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");
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
* which is honest: the upstream failed and we do not know more.
|
|
18
18
|
*/
|
|
19
19
|
import { GatewayError } from "./gateway-error";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
classifyUpstreamError,
|
|
22
|
+
upstreamErrorIdentifier,
|
|
23
|
+
extractUpstreamMessageText,
|
|
24
|
+
} from "../transport/failure-policy";
|
|
21
25
|
import { isRecord } from "./codecs";
|
|
22
26
|
|
|
23
27
|
|
|
@@ -54,19 +58,25 @@ export function gatewayErrorFromStreamError(
|
|
|
54
58
|
}
|
|
55
59
|
if (!isRecord(error)) return undefined;
|
|
56
60
|
|
|
57
|
-
const message = extractUpstreamMessage(error);
|
|
58
61
|
const identifier = upstreamErrorIdentifier(error);
|
|
59
62
|
const explicitStatus = numericStatus(error["status"], error["statusCode"], error["status_code"]);
|
|
60
63
|
const classification = classifyUpstreamError(explicitStatus, identifier);
|
|
61
64
|
const raw = JSON.stringify(error, null, 2).slice(0, 16_384);
|
|
65
|
+
// Only genuine prose (`message`/`msg`/`error` fields) counts as upstream
|
|
66
|
+
// passthrough text. A `code`/identifier is a classification key, not prose —
|
|
67
|
+
// it still serves as the message fallback when no text exists, but it must
|
|
68
|
+
// not set `upstreamMessageAsIs`.
|
|
69
|
+
const prose = extractUpstreamMessageText(error);
|
|
70
|
+
const message = prose.slice(0, 500) || identifier || label;
|
|
62
71
|
return new GatewayError(
|
|
63
72
|
classification.code,
|
|
64
73
|
classification.status,
|
|
65
|
-
message
|
|
74
|
+
message,
|
|
66
75
|
{
|
|
67
76
|
...(explicitStatus === undefined ? {} : { providerStatus: explicitStatus }),
|
|
68
77
|
...(identifier ? { providerCode: identifier } : {}),
|
|
69
78
|
raw,
|
|
79
|
+
...(prose ? { upstreamMessageAsIs: true } : {}),
|
|
70
80
|
...(classification.credentialEvidence ? { credentialEvidence: true } : {}),
|
|
71
81
|
...(classification.rateLimitScope
|
|
72
82
|
? { rateLimitScope: classification.rateLimitScope }
|