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
|
@@ -152,6 +152,118 @@ function pairedClose(
|
|
|
152
152
|
return undefined;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Stateful strip of paired thinking tags that preserves source order.
|
|
157
|
+
*
|
|
158
|
+
* The reasoning channel (e.g. Chat `reasoning_content`) is one stream where
|
|
159
|
+
* every byte — tagged or not — is thought. The two-bucket split used by
|
|
160
|
+
* `ThinkSplitter` (`{ text, reasoning }`) cannot express interleaving, so a
|
|
161
|
+
* call site that joins `${reasoning}${text}` reorders the stream: untagged
|
|
162
|
+
* prose that preceded a `<think>` span lands after it. This stripper instead
|
|
163
|
+
* emits the whole settled content with paired tags removed, in source order,
|
|
164
|
+
* while keeping `ThinkSplitter`'s stateful guarantees: a tag split across two
|
|
165
|
+
* deltas is recognized, code spans are left alone, and an unclosed span fails
|
|
166
|
+
* open at the cap.
|
|
167
|
+
*/
|
|
168
|
+
export class ThinkingTagStripper {
|
|
169
|
+
#buffer = "";
|
|
170
|
+
#codeTicks = 0;
|
|
171
|
+
#codeFenced = false;
|
|
172
|
+
/** True once an opening tag has been seen and its close has not arrived yet. */
|
|
173
|
+
#inUnclosedSpan = false;
|
|
174
|
+
/** The opening tag currently held, restored verbatim on fail-open/flush. */
|
|
175
|
+
#openMarker = "";
|
|
176
|
+
|
|
177
|
+
/** Feeds a chunk; returns settled content with paired tags removed, in order. */
|
|
178
|
+
push(chunk: string): string {
|
|
179
|
+
this.#buffer += stripNul(chunk);
|
|
180
|
+
let out = "";
|
|
181
|
+
for (;;) {
|
|
182
|
+
if (this.#inUnclosedSpan) {
|
|
183
|
+
const closed = this.#findTag(CLOSE_TAGS);
|
|
184
|
+
if (closed === undefined) break;
|
|
185
|
+
out += this.#buffer.slice(0, closed.index);
|
|
186
|
+
this.#buffer = this.#buffer.slice(closed.index + closed.length);
|
|
187
|
+
this.#codeTicks = closed.codeTicks;
|
|
188
|
+
this.#codeFenced = closed.codeFenced;
|
|
189
|
+
this.#inUnclosedSpan = false;
|
|
190
|
+
this.#openMarker = "";
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const opened = this.#findTag(OPEN_TAGS);
|
|
194
|
+
if (opened === undefined) break;
|
|
195
|
+
out += this.#buffer.slice(0, opened.index);
|
|
196
|
+
this.#openMarker = this.#buffer.slice(opened.index, opened.index + opened.length);
|
|
197
|
+
this.#buffer = this.#buffer.slice(opened.index + opened.length);
|
|
198
|
+
this.#codeTicks = opened.codeTicks;
|
|
199
|
+
this.#codeFenced = opened.codeFenced;
|
|
200
|
+
this.#inUnclosedSpan = true;
|
|
201
|
+
}
|
|
202
|
+
if (this.#inUnclosedSpan) {
|
|
203
|
+
// No close yet: hold the whole span content — including any text after it
|
|
204
|
+
// — until a later close or `flush()`. Streaming it now would release the
|
|
205
|
+
// span content before its opening tag on fail-open.
|
|
206
|
+
if (this.#buffer.length > MAX_THINK_BLOCK_BYTES) {
|
|
207
|
+
out += this.#openMarker + this.#buffer;
|
|
208
|
+
this.#buffer = "";
|
|
209
|
+
this.#inUnclosedSpan = false;
|
|
210
|
+
this.#openMarker = "";
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
const held = danglingTagPrefix(this.#buffer);
|
|
215
|
+
const settled = held.length > 0 ? this.#buffer.slice(0, -held.length) : this.#buffer;
|
|
216
|
+
this.#buffer = held;
|
|
217
|
+
return out + settled;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Releases content held back for a tag that never completed. */
|
|
221
|
+
flush(): string {
|
|
222
|
+
const rest = this.#buffer;
|
|
223
|
+
this.#buffer = "";
|
|
224
|
+
this.#codeTicks = 0;
|
|
225
|
+
this.#codeFenced = false;
|
|
226
|
+
if (this.#inUnclosedSpan) {
|
|
227
|
+
// An open tag that never closed is not evidence of reasoning — restore it
|
|
228
|
+
// byte-for-byte and release the whole tail as-is.
|
|
229
|
+
const marker = this.#openMarker;
|
|
230
|
+
this.#inUnclosedSpan = false;
|
|
231
|
+
this.#openMarker = "";
|
|
232
|
+
return marker + rest;
|
|
233
|
+
}
|
|
234
|
+
return rest;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#findTag(tags: readonly string[]): TagMatch | undefined {
|
|
238
|
+
const buffer = this.#buffer;
|
|
239
|
+
let ticks = this.#codeTicks;
|
|
240
|
+
let fenced = this.#codeFenced;
|
|
241
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
242
|
+
const char = buffer[i];
|
|
243
|
+
if (char === "`") {
|
|
244
|
+
let run = 1;
|
|
245
|
+
while (buffer[i + run] === "`") run++;
|
|
246
|
+
if (ticks === 0) {
|
|
247
|
+
ticks = run;
|
|
248
|
+
fenced = run >= 3;
|
|
249
|
+
} else if (run === ticks) {
|
|
250
|
+
ticks = 0;
|
|
251
|
+
fenced = false;
|
|
252
|
+
}
|
|
253
|
+
i += run - 1;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (char === "\n" && ticks > 0 && !fenced) ticks = 0;
|
|
257
|
+
if (char !== "<" || ticks > 0) continue;
|
|
258
|
+
for (const tag of tags) {
|
|
259
|
+
if (!buffer.startsWith(tag, i)) continue;
|
|
260
|
+
return { index: i, length: tag.length, codeTicks: ticks, codeFenced: fenced };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
155
267
|
/** The longest suffix of `text` that is a proper prefix of any tag. */
|
|
156
268
|
function danglingTagPrefix(text: string): string {
|
|
157
269
|
const tags = [...OPEN_TAGS, ...CLOSE_TAGS];
|
|
@@ -12,77 +12,45 @@
|
|
|
12
12
|
* rest as text, mirroring the DSML/OMP scanners used for other families.
|
|
13
13
|
*/
|
|
14
14
|
import type { InbandScanner, InbandScanResult } from "./inband";
|
|
15
|
+
import { MarkupStreamParser } from "../../markup-stream-parser";
|
|
15
16
|
|
|
16
17
|
const OPEN_TAG = "<tool_call>";
|
|
17
18
|
const CLOSE_TAG = "</tool_call>";
|
|
18
19
|
|
|
19
20
|
export class QwenToolCallScanner implements InbandScanner {
|
|
20
|
-
readonly #
|
|
21
|
-
#pending = "";
|
|
22
|
-
#sequence = 0;
|
|
21
|
+
readonly #parser: MarkupStreamParser<InbandScanResult["calls"][number]>;
|
|
23
22
|
|
|
24
23
|
constructor(allowedNames: ReadonlySet<string>) {
|
|
25
|
-
this.#
|
|
24
|
+
this.#parser = new MarkupStreamParser<InbandScanResult["calls"][number]>({
|
|
25
|
+
openers: [OPEN_TAG],
|
|
26
|
+
classifyOpener: (_pending, _openIndex, opener) => {
|
|
27
|
+
if (opener !== OPEN_TAG) return { kind: "literal" };
|
|
28
|
+
return {
|
|
29
|
+
kind: "found",
|
|
30
|
+
closer: CLOSE_TAG,
|
|
31
|
+
parse: (block) => {
|
|
32
|
+
const inner = block.slice(OPEN_TAG.length, -CLOSE_TAG.length).trim();
|
|
33
|
+
const parsed = parseToolCall(inner);
|
|
34
|
+
if (parsed === undefined) return undefined;
|
|
35
|
+
if (!allowedNames.has(parsed.name)) {
|
|
36
|
+
// An undeclared tool is model text, not authority to invoke it.
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
return [parsed];
|
|
40
|
+
},
|
|
41
|
+
callId: (call, _index, seq) => ({ ...call, id: `call_qwen_${seq}` }),
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
flush: (pending) => (isPossibleOpener(pending) ? "" : pending),
|
|
45
|
+
});
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
push(chunk: string): InbandScanResult {
|
|
29
|
-
this.#
|
|
30
|
-
let cursor = 0;
|
|
31
|
-
let text = "";
|
|
32
|
-
const calls: Array<InbandScanResult["calls"][number]> = [];
|
|
33
|
-
for (;;) {
|
|
34
|
-
const open = this.#pending.indexOf(OPEN_TAG, cursor);
|
|
35
|
-
if (open < 0) {
|
|
36
|
-
// Hold a possible pending opener at the tail; settle everything before it.
|
|
37
|
-
text += this.#pending.slice(cursor, this.#settledEnd(this.#pending.length));
|
|
38
|
-
cursor = this.#settledEnd(this.#pending.length);
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
text += this.#pending.slice(cursor, open);
|
|
42
|
-
const bodyStart = open + OPEN_TAG.length;
|
|
43
|
-
const close = this.#pending.indexOf(CLOSE_TAG, bodyStart);
|
|
44
|
-
if (close < 0) {
|
|
45
|
-
// Unterminated block: hold from the opener on and settle what preceded it.
|
|
46
|
-
cursor = open;
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
const inner = this.#pending.slice(bodyStart, close).trim();
|
|
50
|
-
const parsed = parseToolCall(inner);
|
|
51
|
-
if (parsed !== undefined) {
|
|
52
|
-
if (this.#allowedNames.has(parsed.name)) {
|
|
53
|
-
calls.push({
|
|
54
|
-
id: `call_qwen_${this.#sequence++}`,
|
|
55
|
-
name: parsed.name,
|
|
56
|
-
arguments: parsed.arguments,
|
|
57
|
-
});
|
|
58
|
-
} else {
|
|
59
|
-
// An undeclared tool is model text, not authority to invoke it.
|
|
60
|
-
text += this.#pending.slice(open, close + CLOSE_TAG.length);
|
|
61
|
-
}
|
|
62
|
-
} else {
|
|
63
|
-
text += this.#pending.slice(open, close + CLOSE_TAG.length);
|
|
64
|
-
}
|
|
65
|
-
cursor = close + CLOSE_TAG.length;
|
|
66
|
-
}
|
|
67
|
-
this.#pending = this.#pending.slice(cursor);
|
|
68
|
-
return { text, calls };
|
|
49
|
+
return this.#parser.push(chunk);
|
|
69
50
|
}
|
|
70
51
|
|
|
71
52
|
flush(): InbandScanResult {
|
|
72
|
-
|
|
73
|
-
this.#pending = "";
|
|
74
|
-
if (isPossibleOpener(pending)) return { text: "", calls: [] };
|
|
75
|
-
return { text: pending, calls: [] };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
#settledEnd(end: number): number {
|
|
79
|
-
// Keep a trailing fragment that could be the start of an opener.
|
|
80
|
-
const tail = this.#pending.slice(0, end);
|
|
81
|
-
const maxPrefix = Math.min(OPEN_TAG.length - 1, tail.length);
|
|
82
|
-
for (let len = maxPrefix; len > 0; len -= 1) {
|
|
83
|
-
if (OPEN_TAG.startsWith(tail.slice(-len))) return end - len;
|
|
84
|
-
}
|
|
85
|
-
return end;
|
|
53
|
+
return this.#parser.flush();
|
|
86
54
|
}
|
|
87
55
|
}
|
|
88
56
|
|
|
@@ -49,7 +49,7 @@ interface WireTokenBound {
|
|
|
49
49
|
* The output limit assumed when the caller states none, for wires that need
|
|
50
50
|
* the field present.
|
|
51
51
|
*
|
|
52
|
-
* PROVEN, not assumed: a 3.16M-char prompt to
|
|
52
|
+
* PROVEN, not assumed: a 3.16M-char prompt to CodeBuddy
|
|
53
53
|
* (`bansos/deepseek-v4.1-flash`) answered `400 model_param_invalid` whenever
|
|
54
54
|
* `max_tokens` was absent, and answered 200 with the *same* body plus any
|
|
55
55
|
* explicit value — `1`, `16`, `1000`, `8000`, `64000`, `384000` all passed.
|
|
@@ -91,7 +91,7 @@ export function repairMissingToolResponses(
|
|
|
91
91
|
* fully answered by matching `tool` results is stripped of its calls (kept when
|
|
92
92
|
* it still carries content), and the dangling results go with it.
|
|
93
93
|
*
|
|
94
|
-
* This is the buddy-family policy (
|
|
94
|
+
* This is the buddy-family policy (CodeBuddy code `11148`): a partial
|
|
95
95
|
* batch `assistant[c1 c2] + tool[c1]` is a broken sequence the upstream rejects,
|
|
96
96
|
* and a synthesized placeholder cannot stand in for a real execution the model
|
|
97
97
|
* never saw. Contrast `repairMissingToolResponses`, which synthesizes an
|
|
@@ -188,7 +188,7 @@ export function repairRequestToolCalls(request: CanonicalRequest): CanonicalRequ
|
|
|
188
188
|
* True when a `toolResult` part is a `tool`-role turn whose `toolCall` is
|
|
189
189
|
* absent from the whole history, or when results for one batch of calls are
|
|
190
190
|
* not contiguous. Upstreams reject both with `tool_call_sequence_broken`
|
|
191
|
-
* (
|
|
191
|
+
* (CodeBuddy code `11148`), and a broken history stays broken: the
|
|
192
192
|
* client replays it on every later turn, so the whole conversation dies.
|
|
193
193
|
*/
|
|
194
194
|
function toolCallIdsIn(message: CanonicalMessage): string[] {
|
|
@@ -10,6 +10,7 @@ import { applyRouteUserAgent, type ProviderDispatchContext } from "../../provide
|
|
|
10
10
|
import { abortGatewayError, createUpstreamDeadlineLifecycle } from '../../providers/operations/upstream-deadline';
|
|
11
11
|
import { mapClaudeHttpError } from '../messages-errors';
|
|
12
12
|
import { isRecord } from '../codecs';
|
|
13
|
+
import { upstreamMessageAsIs, MAX_UPSTREAM_ERROR_BYTES } from '../../transport/failure-policy';
|
|
13
14
|
import { parseClaudeSseStream, claudeResponseToEvents } from '../response/messages';
|
|
14
15
|
|
|
15
16
|
export async function* sendClaudeMessagesRequest(
|
|
@@ -67,20 +68,30 @@ export async function* sendClaudeMessagesRequest(
|
|
|
67
68
|
try {
|
|
68
69
|
json = JSON.parse(body) as unknown;
|
|
69
70
|
} catch {
|
|
71
|
+
const fallback = "Claude response is not valid JSON (request {requestId}). The upstream came back empty-handed — retry 🌙";
|
|
72
|
+
const upstreamText = upstreamMessageAsIs(body, "");
|
|
70
73
|
throw new GatewayError(
|
|
71
74
|
"platform_unavailable",
|
|
72
75
|
502,
|
|
73
|
-
|
|
74
|
-
{
|
|
76
|
+
upstreamText.message || fallback,
|
|
77
|
+
{
|
|
78
|
+
raw: body.slice(0, MAX_UPSTREAM_ERROR_BYTES),
|
|
79
|
+
...(upstreamText.hadUpstreamText ? { upstreamMessageAsIs: true } : {}),
|
|
80
|
+
},
|
|
75
81
|
"upstream",
|
|
76
82
|
);
|
|
77
83
|
}
|
|
78
84
|
if (!isRecord(json)) {
|
|
85
|
+
const fallback = "Claude response must be an object (request {requestId}). The upstream came back empty-handed — retry 🌙";
|
|
86
|
+
const upstreamText = upstreamMessageAsIs(body, "");
|
|
79
87
|
throw new GatewayError(
|
|
80
88
|
"platform_unavailable",
|
|
81
89
|
502,
|
|
82
|
-
|
|
83
|
-
{
|
|
90
|
+
upstreamText.message || fallback,
|
|
91
|
+
{
|
|
92
|
+
raw: body.slice(0, MAX_UPSTREAM_ERROR_BYTES),
|
|
93
|
+
...(upstreamText.hadUpstreamText ? { upstreamMessageAsIs: true } : {}),
|
|
94
|
+
},
|
|
84
95
|
"upstream",
|
|
85
96
|
);
|
|
86
97
|
}
|
|
@@ -71,7 +71,7 @@ export function parseUpstreamBackoff(headers: {
|
|
|
71
71
|
*
|
|
72
72
|
* - **Relative** — "quota will reset in 3 hours", "try again in 30s".
|
|
73
73
|
* - **Absolute** — "your usage will reset at 2026-09-24 02:12:51 UTC+8"
|
|
74
|
-
* (
|
|
74
|
+
* (CodeBuddy `6004`). An absolute stamp is *not* a duration, and
|
|
75
75
|
* the relative-only pattern silently missed it, so the account fell back to
|
|
76
76
|
* the generic 15-minute default while the provider had actually parked it
|
|
77
77
|
* for the rest of the window — the account kept re-entering rotation and
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
isContextLengthFailure,
|
|
7
7
|
throwIfHtmlResponse,
|
|
8
8
|
mapUpstreamHttpError,
|
|
9
|
-
extractUpstreamMessage,
|
|
10
9
|
extractHtmlTitle,
|
|
11
10
|
upstreamErrorIdentifier,
|
|
11
|
+
upstreamMessageAsIs,
|
|
12
12
|
} from "../../transport/failure-policy";
|
|
13
13
|
import { applyRouteUserAgent, type ProviderDispatchTarget, type ProviderId, type ProviderDispatchContext } from "../provider-contracts";
|
|
14
14
|
import { BaseProviderAdapter } from "./base-adapter";
|
|
@@ -17,6 +17,7 @@ import { resolveDialect } from "../../protocol/translation/dialect/resolver";
|
|
|
17
17
|
import { resolveThinkingForModel } from "../../protocol/translation/dialect/thinking-ladder";
|
|
18
18
|
import type { DialectPolicy } from "../../protocol/translation/dialect/types";
|
|
19
19
|
import { postUpstreamJson } from "../../protocol/upstream/post-json";
|
|
20
|
+
import { gzipSync } from "node:zlib";
|
|
20
21
|
import { GATEWAY_USER_AGENT } from "../operations/gateway-user-agent";
|
|
21
22
|
import { resolvePromptCacheKey } from "../operations/session-resolution";
|
|
22
23
|
import {
|
|
@@ -28,6 +29,24 @@ import {
|
|
|
28
29
|
} from "../../protocol/codecs";
|
|
29
30
|
const TEXT_DECODER = new TextDecoder("utf-8", { fatal: false });
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Whether an upstream error body is the channel-check rejection the CodeBuddy
|
|
34
|
+
* console fallback recovers from. Ported from the donor gateway
|
|
35
|
+
* (`codebuddy2api/upstream_compat.is_channel_check_error`): it requires both
|
|
36
|
+
* a channel-check business code (`11128` / `11-128`) and one of the
|
|
37
|
+
* explanatory texts. A bare code match swept unrelated failures into a retry.
|
|
38
|
+
*/
|
|
39
|
+
export function isBuddyChannelCheckError(raw: string): boolean {
|
|
40
|
+
if (!/code["']?\s*:\s*["']?(?:11128|11-128)/i.test(raw)) return false;
|
|
41
|
+
const lower = raw.toLowerCase();
|
|
42
|
+
return (
|
|
43
|
+
lower.includes("渠道校验") || // 渠道校验
|
|
44
|
+
lower.includes("channel verification") ||
|
|
45
|
+
lower.includes("official client") ||
|
|
46
|
+
lower.includes("illegal api invocation from an unapproved channel")
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
31
50
|
/**
|
|
32
51
|
* Provider ids that understand the OpenAI-style `service_tier` control. Only
|
|
33
52
|
* these upstreams accept the field; every other OpenAI-compatible provider
|
|
@@ -111,8 +130,21 @@ export interface OpenAICompatibleAdapterConfig {
|
|
|
111
130
|
* stamps `Content-Encoding: zstd` — the Grok Build CLI does this via its
|
|
112
131
|
* sampler (`request_compression.rs`), and the proxy accepts it (verified
|
|
113
132
|
* live 2026-11-02). Falls back to plain JSON if the runtime lacks zstd.
|
|
133
|
+
* `"gzip"` compresses with `gzipSync` and stamps `Content-Encoding: gzip`
|
|
134
|
+
* — the CodeBuddy CLI contract (donor `codebuddy2api`): the CLI profiles
|
|
135
|
+
* always compress; the CN desktop profile compresses requests >= 1024 B.
|
|
136
|
+
* The caller must also send `Accept-Encoding: identity` (the compressed
|
|
137
|
+
* body and the uncompressed response are independent choices).
|
|
138
|
+
*/
|
|
139
|
+
readonly request_compression?: "json" | "zstd" | "gzip";
|
|
140
|
+
/**
|
|
141
|
+
* One retry against this path when the primary chat endpoint answers a
|
|
142
|
+
* channel-check 400 (`11128` / `11-128`). Matches the donor gateway's
|
|
143
|
+
* `/console/chat/completions` fallback for the international profile: the
|
|
144
|
+
* primary `v2` endpoint rejects requests that do not carry the channel
|
|
145
|
+
* marker, and a single console retry is the reference's recovery.
|
|
114
146
|
*/
|
|
115
|
-
readonly
|
|
147
|
+
readonly channel_fallback_path?: string;
|
|
116
148
|
/**
|
|
117
149
|
* Stamps `user-agent: Cartethyia/<version>` on every dispatch. Explicit
|
|
118
150
|
* opt-in per provider spec — never a default — so first-party cloaking
|
|
@@ -216,6 +248,13 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
|
|
|
216
248
|
// here — alongside the other request headers — so auth/proxy layers see the
|
|
217
249
|
// final shape before the transport runs.
|
|
218
250
|
if (this.config.request_compression === "zstd") headers["content-encoding"] = "zstd";
|
|
251
|
+
else if (this.config.request_compression === "gzip") {
|
|
252
|
+
headers["content-encoding"] = "gzip";
|
|
253
|
+
// The compressed request and the response encoding are independent; the
|
|
254
|
+
// reference requests the identity response encoding so the stream arrives
|
|
255
|
+
// uncompressed (matching `Accept-Encoding: identity` in the donor).
|
|
256
|
+
headers["accept-encoding"] = "identity";
|
|
257
|
+
}
|
|
219
258
|
Object.assign(headers, filterProviderCustomHeaders(context.credential.custom_headers));
|
|
220
259
|
applyRouteUserAgent(headers, context.credential, context.user_agent);
|
|
221
260
|
return headers;
|
|
@@ -330,18 +369,38 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
|
|
|
330
369
|
return u.toString();
|
|
331
370
|
})();
|
|
332
371
|
const outboundFetch = context.outbound_fetch ?? this.config.fetch ?? globalThis.fetch;
|
|
333
|
-
const
|
|
334
|
-
fetchUrl,
|
|
335
|
-
headers,
|
|
336
|
-
payload as Record<string, unknown>,
|
|
337
|
-
context,
|
|
338
|
-
outboundFetch,
|
|
339
|
-
// zstd body encoding: matches the Grok Build CLI's
|
|
340
|
-
// `request_compression` (zstd with a JSON fallback when unavailable).
|
|
372
|
+
const bodyEncoder =
|
|
341
373
|
this.config.request_compression === "zstd" && typeof Bun?.zstdCompressSync === "function"
|
|
342
|
-
? (p) => Bun.zstdCompressSync(Buffer.from(JSON.stringify(p))) as Uint8Array<ArrayBuffer>
|
|
343
|
-
:
|
|
344
|
-
|
|
374
|
+
? (p: Record<string, unknown>) => Bun.zstdCompressSync(Buffer.from(JSON.stringify(p))) as Uint8Array<ArrayBuffer>
|
|
375
|
+
: this.config.request_compression === "gzip"
|
|
376
|
+
? (p: Record<string, unknown>) => gzipSync(Buffer.from(JSON.stringify(p)), { level: 6 })
|
|
377
|
+
: undefined;
|
|
378
|
+
const channelFallback =
|
|
379
|
+
this.config.channel_fallback_path !== undefined &&
|
|
380
|
+
endpointPath !== this.config.channel_fallback_path
|
|
381
|
+
? joinUrl(this.config.resolveBaseUrl?.(context) ?? this.config.base_url, this.config.channel_fallback_path)
|
|
382
|
+
: undefined;
|
|
383
|
+
const upstream = await postUpstreamJson(fetchUrl, headers, payload as Record<string, unknown>, context, outboundFetch, bodyEncoder);
|
|
384
|
+
if (channelFallback !== undefined && upstream.res.status === 400) {
|
|
385
|
+
// Read the body only for a 400 that could still be retried. The channel
|
|
386
|
+
// check is the one refusal the console endpoint recovers from; a rejected
|
|
387
|
+
// body is consumed here so the fallback attempt carries a fresh stream.
|
|
388
|
+
const raw = await upstream.res.text().catch(() => "");
|
|
389
|
+
upstream.release();
|
|
390
|
+
if (isBuddyChannelCheckError(raw)) {
|
|
391
|
+
const retry = await postUpstreamJson(channelFallback, headers, payload as Record<string, unknown>, context, outboundFetch, bodyEncoder);
|
|
392
|
+
retry.release();
|
|
393
|
+
return retry.res;
|
|
394
|
+
}
|
|
395
|
+
// Not a channel check — reconstruct the 400 response so downstream error
|
|
396
|
+
// classification still sees the upstream's body. A `Response` body can be
|
|
397
|
+
// consumed once, so rebuild it from the captured text.
|
|
398
|
+
return new Response(raw, {
|
|
399
|
+
status: 400,
|
|
400
|
+
statusText: upstream.res.statusText,
|
|
401
|
+
headers: { "content-type": "application/json" },
|
|
402
|
+
});
|
|
403
|
+
}
|
|
345
404
|
upstream.release();
|
|
346
405
|
return upstream.res;
|
|
347
406
|
}
|
|
@@ -439,11 +498,12 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
|
|
|
439
498
|
}
|
|
440
499
|
json = parsed as Record<string, unknown>;
|
|
441
500
|
} catch {
|
|
442
|
-
const
|
|
501
|
+
const fallback = `Upstream returned a non-JSON response: ${extractHtmlTitle(raw) || raw.trim() || `HTTP ${res.status}`} (request {requestId}). The connection ghosted us — come back in a moment 👻`;
|
|
502
|
+
const upstreamText = upstreamMessageAsIs(raw, "");
|
|
443
503
|
throw new GatewayError(
|
|
444
504
|
"transport_unavailable",
|
|
445
505
|
502,
|
|
446
|
-
|
|
506
|
+
`${upstreamText.message || fallback}`.slice(0, 500),
|
|
447
507
|
{ upstreamStatus: res.status, contentType },
|
|
448
508
|
"upstream",
|
|
449
509
|
);
|
|
@@ -485,15 +545,16 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
|
|
|
485
545
|
const classification = isContextLengthFailure(json)
|
|
486
546
|
? { code: "context_length_exceeded" as const, status: 413, origin: "upstream" as const, credentialEvidence: false, rateLimitScope: undefined }
|
|
487
547
|
: classifyUpstreamError(providerStatus, identifier);
|
|
488
|
-
const
|
|
548
|
+
const upstreamText = upstreamMessageAsIs(json, "");
|
|
489
549
|
throw new GatewayError(
|
|
490
550
|
classification.code,
|
|
491
551
|
classification.status,
|
|
492
|
-
message,
|
|
552
|
+
upstreamText.message || "Upstream reported an error",
|
|
493
553
|
{
|
|
494
554
|
providerId: this.provider_id,
|
|
495
555
|
...(providerStatus === undefined ? {} : { providerStatus }),
|
|
496
556
|
...(identifier === undefined ? {} : { providerCode: identifier }),
|
|
557
|
+
...(upstreamText.hadUpstreamText ? { upstreamMessageAsIs: true } : {}),
|
|
497
558
|
...(classification.credentialEvidence ? { credentialEvidence: true } : {}),
|
|
498
559
|
...(classification.rateLimitScope
|
|
499
560
|
? { rateLimitScope: classification.rateLimitScope }
|
|
@@ -23,7 +23,7 @@ import type { OAuthTokenRefreshResult, OAuthTokenRefresher } from "./oauth-refre
|
|
|
23
23
|
export type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
24
24
|
|
|
25
25
|
/** Normalized token shape returned by provider token endpoints. */
|
|
26
|
-
interface NormalizedTokenResponse {
|
|
26
|
+
export interface NormalizedTokenResponse {
|
|
27
27
|
readonly access: string;
|
|
28
28
|
readonly refresh: string;
|
|
29
29
|
readonly expiresAt: Date;
|
|
@@ -8,8 +8,9 @@ import type {
|
|
|
8
8
|
OAuthDeviceFlowContext,
|
|
9
9
|
OAuthDevicePollResult,
|
|
10
10
|
OAuthDeviceStartResult,
|
|
11
|
+
OAuthExchangeResult,
|
|
11
12
|
} from "./oauth-flow-store";
|
|
12
|
-
import { OAuthClient } from "./oauth-client";
|
|
13
|
+
import { OAuthClient, type NormalizedTokenResponse } from "./oauth-client";
|
|
13
14
|
|
|
14
15
|
/** Configuration for standard device-code start and poll requests. */
|
|
15
16
|
export interface DeviceFlowConfig {
|
|
@@ -67,6 +68,58 @@ export abstract class OAuthDeviceFlow extends OAuthClient {
|
|
|
67
68
|
return new URLSearchParams(params);
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Optional verdict computed from the parsed token body before the status
|
|
73
|
+
* code is considered. Defaults to undefined (continue with standard
|
|
74
|
+
* handling). A provider whose authoritative endpoint states the poll state
|
|
75
|
+
* in the body on any status can return a backoff verdict here — e.g. Grok,
|
|
76
|
+
* which votes on `error` before the HTTP status.
|
|
77
|
+
*/
|
|
78
|
+
protected preVerdict?(_payload: unknown): OAuthDevicePollResult | undefined;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Post-success hook for the standard device-token poll.
|
|
82
|
+
*
|
|
83
|
+
* Receives the raw token response payload and the normalized parsed token.
|
|
84
|
+
* Returns the exchange result to complete with, or a verdict (typically
|
|
85
|
+
* `failed`) instead. The default reproduces the generic behavior exactly.
|
|
86
|
+
* Errors thrown by a hook are converted to a `failed` verdict by
|
|
87
|
+
* `pollGenericDeviceAuth`, keeping the poll contract (verdicts, never
|
|
88
|
+
* throws).
|
|
89
|
+
*/
|
|
90
|
+
protected afterDeviceTokenExchange?(
|
|
91
|
+
_payload: unknown,
|
|
92
|
+
_normalized: NormalizedTokenResponse,
|
|
93
|
+
): OAuthExchangeResult | OAuthDevicePollResult | Promise<OAuthExchangeResult | OAuthDevicePollResult>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Failure reason for a non-JSON token body. Defaults to the generic
|
|
97
|
+
* phrasing; providers whose wire carries a distinct message override it.
|
|
98
|
+
*/
|
|
99
|
+
protected unparsablePollReason(status: number): string {
|
|
100
|
+
return `device polling failed (${status})`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Failure reason for a non-OK token response. The default preserves the
|
|
105
|
+
* generic behavior: the response's `error` field names the reason on the
|
|
106
|
+
* RFC 8628 400/428 statuses, and every other non-OK status gets the generic
|
|
107
|
+
* phrasing (the body is not trusted there).
|
|
108
|
+
*/
|
|
109
|
+
protected pollFailureReason(
|
|
110
|
+
status: number,
|
|
111
|
+
errorKey: string | undefined,
|
|
112
|
+
_errorDescription: string | undefined,
|
|
113
|
+
): string {
|
|
114
|
+
if (status === 400 || status === 428) return errorKey || `device polling failed (${status})`;
|
|
115
|
+
return `device polling failed (${status})`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Failure reason when a token response omits `access_token`. */
|
|
119
|
+
protected missingTokenReason(_errorKey: string | undefined, _errorDescription: string | undefined): string {
|
|
120
|
+
return `${this.providerLabel} token response omitted access_token`;
|
|
121
|
+
}
|
|
122
|
+
|
|
70
123
|
/** Standard device-token poll. Returns pending while authorization is incomplete. */
|
|
71
124
|
protected async pollGenericDeviceAuth(
|
|
72
125
|
deviceAuthId: string,
|
|
@@ -88,20 +141,57 @@ export abstract class OAuthDeviceFlow extends OAuthClient {
|
|
|
88
141
|
} catch {
|
|
89
142
|
payload = undefined;
|
|
90
143
|
}
|
|
144
|
+
// A body that is not JSON is a failed poll regardless of status: verdicts
|
|
145
|
+
// must never throw, and the reason below lets providers keep their own
|
|
146
|
+
// phrasing (e.g. Grok's "returned invalid JSON").
|
|
147
|
+
if (payload === undefined) {
|
|
148
|
+
return { status: "failed", reason: this.unparsablePollReason(response.status) };
|
|
149
|
+
}
|
|
91
150
|
const root = record(payload) ?? {};
|
|
151
|
+
if (this.preVerdict) {
|
|
152
|
+
const verdict = this.preVerdict(payload);
|
|
153
|
+
if (verdict !== undefined) return verdict;
|
|
154
|
+
}
|
|
155
|
+
const errorKey = typeof root.error === "string" ? root.error : undefined;
|
|
156
|
+
const errorDescription = typeof root.error_description === "string" ? root.error_description : undefined;
|
|
92
157
|
if (response.status === 400 || response.status === 428) {
|
|
93
|
-
const
|
|
94
|
-
const verdict = devicePollBackoff(err, root.interval);
|
|
158
|
+
const verdict = devicePollBackoff(errorKey, root.interval);
|
|
95
159
|
if (verdict !== undefined) return verdict;
|
|
96
|
-
return {
|
|
160
|
+
return {
|
|
161
|
+
status: "failed",
|
|
162
|
+
reason: this.pollFailureReason(response.status, errorKey, errorDescription),
|
|
163
|
+
};
|
|
97
164
|
}
|
|
98
|
-
if (!response.ok
|
|
99
|
-
return { status: "failed", reason:
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
return { status: "failed", reason: this.pollFailureReason(response.status, errorKey, errorDescription) };
|
|
100
167
|
}
|
|
101
168
|
if (typeof root.access_token !== "string" || !root.access_token) {
|
|
102
|
-
return {
|
|
169
|
+
return {
|
|
170
|
+
status: "failed",
|
|
171
|
+
reason: this.missingTokenReason(errorKey, errorDescription),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
let normalized: NormalizedTokenResponse;
|
|
175
|
+
try {
|
|
176
|
+
normalized = this.parseTokenResponse(payload);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
status: "failed",
|
|
180
|
+
reason: error instanceof Error ? error.message : `${this.providerLabel} token response omitted access_token`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (this.afterDeviceTokenExchange) {
|
|
184
|
+
try {
|
|
185
|
+
const hooked = await this.afterDeviceTokenExchange(payload, normalized);
|
|
186
|
+
if ("status" in hooked) return hooked;
|
|
187
|
+
return { status: "complete", result: hooked };
|
|
188
|
+
} catch (error) {
|
|
189
|
+
return {
|
|
190
|
+
status: "failed",
|
|
191
|
+
reason: error instanceof Error ? error.message : `${this.providerLabel} authentication failed`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
103
194
|
}
|
|
104
|
-
const normalized = this.parseTokenResponse(payload);
|
|
105
195
|
return { status: "complete", result: this.toExchangeResult(normalized) };
|
|
106
196
|
}
|
|
107
197
|
}
|