cartethyia 2.8.0 → 2.8.1
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/dist/dashboard/assets/{BannedPage-Cc0vKpMG.js → BannedPage-Dq530awD.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-Bqj2aa1T.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-CXsaOAO2.js → CliToolsPage-DaZZ3cqP.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BeLpJ4ma.js → CombosPage-BlC_xdkX.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-DnV0uZ3m.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-CBxAjo63.js → CustomizationPage-7xT5yg2E.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-B5yEE0k6.js → HealthEventsModal-Clq8E9cW.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BWFLECmz.js → InjectSystemPromptPanel-6Teoq9Se.js} +1 -1
- package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-CgoPc5B9.js → LoginPage-CZuI0MAR.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-D9bFQern.js → ModelPicker-C1hNTlkr.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-B_p4tL8k.js → OverviewPage-0yfhSI1C.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-0yfhSI1C.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js.gz +0 -0
- package/dist/dashboard/assets/{ProviderIcon-D_0PiZtg.js → ProviderIcon-Ddhm1InL.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-CLI2bJTK.js → ProvidersPage-C3fYuu6i.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js +9 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-DA-a1iSN.js → QuotaPage-D1RV_2BX.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-CctTga2C.js → SettingsPage-BzZAvYSX.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-febLU4r1.js → SetupPage--s-EfEIL.js} +1 -1
- package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-M6fvR5Wz.js → StudioPage-BjXFGSK4.js} +1 -1
- package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-nyeuRqz9.js → UsagePage-CP8F8BN7.js} +1 -1
- package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BFOlvrJC.js → api-keys-2hc12Uz2.js} +1 -1
- package/dist/dashboard/assets/api-keys-2hc12Uz2.js.gz +0 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css +2 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css.gz +0 -0
- package/dist/dashboard/assets/{assert-CqTLw5e7.js → assert-BMcj1xZE.js} +1 -1
- package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
- package/dist/dashboard/assets/{badge-C83EqLB0.js → badge-DkRUuVfz.js} +1 -1
- package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
- package/dist/dashboard/assets/{banner-D1xn7pIe.js → banner-CDjW841Q.js} +1 -1
- package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
- package/dist/dashboard/assets/{card-DU3Lq5Lm.js → card-Bto5UlL9.js} +1 -1
- package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-Dw1U_drK.js → cli-tools-DaH6g9hL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CEL5Sg_J.js → clipboard-button-BYKBCzjk.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BjFR5UHy.js → dialog-DL0Zftwv.js} +1 -1
- package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-HvBW0AjI.js → drawer-Dpx_UXaS.js} +1 -1
- package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CECVELyy.js → entry-BV7-1NoM.js} +1 -1
- package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-DYYofQ7W.js → entry-CGvdNU6r.js} +1 -1
- package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BSu3gFlt.js → entry-DScKYvZj.js} +3 -3
- package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DLW4i8zK.js → formatters-BS3DZlZj.js} +1 -1
- package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
- package/dist/dashboard/assets/{index-1-fCqe64.js → index-SQnvvTWX.js} +2 -2
- package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DGvU2oLK.js → inline-Dl-cuLK0.js} +1 -1
- package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
- package/dist/dashboard/assets/{input-iNnO1X2K.js → input-Bfx0EzRc.js} +1 -1
- package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-0q8UGypb.js → layout-BxhbQ_JS.js} +1 -1
- package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-C0w2bQjJ.js → list-row-D49Y4ptn.js} +1 -1
- package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
- package/dist/dashboard/assets/{live-M5aJ_xCa.js → live-CbVTdX9V.js} +1 -1
- package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
- package/dist/dashboard/assets/{network-BKeQGT3K.js → network-JGvDveof.js} +1 -1
- package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-CivAk3da.js → numbers-Dw6kiew-.js} +1 -1
- package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-CpzvI5ep.js → pagination-iUoI8GeE.js} +1 -1
- package/dist/dashboard/assets/pagination-iUoI8GeE.js.gz +0 -0
- package/dist/dashboard/assets/providers/sensenova-intl.png +0 -0
- package/dist/dashboard/assets/providers/sensenova.png +0 -0
- package/dist/dashboard/assets/{routing-BFQX2VBc.js → routing-8jZr-3WE.js} +1 -1
- package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
- package/dist/dashboard/assets/{select-CXbck0jG.js → select-oO1FxMVb.js} +1 -1
- package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
- package/dist/dashboard/assets/{settings-C_XvMHh9.js → settings-CLr3kdIx.js} +1 -1
- package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DggRiCxE.js → stack-Y3pDteZr.js} +1 -1
- package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
- package/dist/dashboard/assets/{state-ClFzfbYp.js → state-CArX_Iak.js} +1 -1
- package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
- package/dist/dashboard/assets/{switch-CTIFxKqS.js → switch-DwlwmDO-.js} +1 -1
- package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BClG3yWj.js → useMutation-DJ9GYEgf.js} +1 -1
- package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +51 -51
- package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
- package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
- package/package.json +1 -1
- package/src/boot/app.ts +1 -1
- package/src/boot/dependencies.ts +4 -6
- package/src/config/providers.ts +6 -5
- package/src/config/spec.ts +8 -8
- package/src/console/backup/nine-router.ts +71 -10
- package/src/console/backup/service.ts +8 -1
- package/src/console/console-router.ts +1 -1
- package/src/console/domain-registration.ts +7 -2
- package/src/console/inferhub/client.ts +1 -1
- package/src/console/observability/live.ts +2 -2
- package/src/console/providers/catalog/contracts.ts +9 -3
- package/src/console/providers/catalog/model-operations.ts +2 -3
- package/src/console/providers/catalog/provider-operations.ts +1 -1
- package/src/console/providers/catalog/routes.ts +2 -2
- package/src/console/providers/catalog/store.ts +1914 -1907
- package/src/console/providers/detail/routes.ts +1 -0
- package/src/console/providers/detail/store.ts +4 -0
- package/src/console/providers/oauth/callback-listener.ts +30 -11
- package/src/console/providers/oauth/device-egress.ts +123 -0
- package/src/console/providers/oauth/routes.ts +123 -16
- package/src/console/quota/account-quota-tenant.ts +5 -5
- package/src/console/quota/account-test-egress.ts +2 -2
- package/src/console/quota/daily-growth-job.ts +1 -1
- package/src/console/routing/pools/contracts.ts +1 -1
- package/src/console/routing/pools/operations.ts +1 -1
- package/src/console/routing/pools/probe-verdict.ts +2 -2
- package/src/console/routing/pools/relay-deploy.ts +1 -1
- package/src/console/routing/pools/store.ts +4 -4
- package/src/network/client.ts +67 -0
- package/src/network/egress/bidi-session.ts +322 -0
- package/src/network/egress/dial.ts +201 -0
- package/src/network/egress/factory.ts +191 -0
- package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
- package/src/network/egress/tls-profile.ts +123 -0
- package/src/network/egress/ws.ts +175 -0
- package/src/network/{types.ts → pool/agent-config.ts} +1 -1
- package/src/network/pool/agent.ts +2 -2
- package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +7 -7
- package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
- package/src/network/pool/resolver.ts +10 -849
- package/src/network/pool/selector.ts +2 -2
- package/src/network/ssrf.ts +1 -1
- package/src/network/{retry.ts → util/retry.ts} +1 -1
- package/src/observability/telemetry-buffer.ts +1 -1
- package/src/persistence/schema.ts +9 -0
- package/src/persistence/ttl-cache.ts +1 -1
- package/src/protocol/codecs.ts +58 -1
- package/src/protocol/request/messages.ts +31 -18
- package/src/protocol/response/messages.ts +34 -7
- package/src/protocol/translation/quirks.ts +15 -0
- package/src/protocol/upstream/send-claude-messages.ts +2 -2
- package/src/providers/adapters/compatible-adapter.ts +5 -0
- package/src/providers/authentication/oauth-client.ts +40 -6
- package/src/providers/authentication/oauth-device-flow.ts +7 -3
- package/src/providers/authentication/oauth-flow-store.ts +82 -1
- package/src/providers/authentication/oauth-refresh-service.ts +766 -739
- package/src/providers/default-registry.ts +76 -0
- package/src/providers/discovery/discovery-types.ts +7 -0
- package/src/providers/discovery/probe-phases.ts +6 -2
- package/src/providers/discovery/probing-service.ts +7 -0
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +11 -0
- package/src/providers/integrations/buddy/account.ts +969 -0
- package/src/providers/integrations/buddy/capabilities.ts +10 -10
- package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
- package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +5 -5
- package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +4 -4
- package/src/providers/integrations/buddy/{buddy-identity.ts → headers.ts} +138 -140
- package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
- package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
- package/src/providers/integrations/cline/cline-oauth.ts +10 -5
- package/src/providers/integrations/cline/cline.ts +3 -4
- package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
- package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
- package/src/providers/integrations/clixad/provider.ts +145 -0
- package/src/providers/integrations/codex/codex-oauth.ts +8 -5
- package/src/providers/integrations/cursor/cursor-auth.ts +1 -1
- package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
- package/src/providers/integrations/cursor/cursor-shared.ts +1 -1
- package/src/providers/integrations/cursor/cursor.ts +485 -70
- package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
- package/src/providers/integrations/deepai/deepai-common.ts +145 -0
- package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
- package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
- package/src/providers/integrations/deepai/provider.ts +42 -0
- package/src/providers/integrations/devin/devin-auth.ts +7 -0
- package/src/providers/integrations/factory/factory-oauth.ts +16 -7
- package/src/providers/integrations/freebuff/freebuff.ts +2 -2
- package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
- package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
- package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
- package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
- package/src/providers/integrations/freepi/freepi-session.ts +158 -0
- package/src/providers/integrations/freepi/freepi.ts +80 -54
- package/src/providers/integrations/github/github-discovery.ts +30 -13
- package/src/providers/integrations/github/github-oauth.ts +217 -44
- package/src/providers/integrations/github/github-quota.ts +22 -5
- package/src/providers/integrations/github/github.ts +40 -32
- package/src/providers/integrations/grok/grok-oauth.ts +4 -4
- package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
- package/src/providers/integrations/hermes/hermes.ts +34 -5
- package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
- package/src/providers/integrations/kilo/kilo.ts +1 -1
- package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
- package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
- package/src/providers/integrations/muse/muse-oauth.ts +5 -5
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/openrouter/provider.ts +1 -1
- package/src/providers/integrations/perch/perch-protocol.ts +0 -3
- package/src/providers/integrations/perch/perch.ts +3 -3
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +1 -1
- package/src/providers/integrations/qoder/qoder-adapter.ts +19 -113
- package/src/providers/integrations/qoder/qoder-auth.ts +8 -1
- package/src/providers/integrations/qoder/qoder-catalog.ts +1 -1
- package/src/providers/integrations/qoder/qoder-chat.ts +25 -8
- package/src/providers/integrations/qoder/qoder-checkin.ts +1 -1
- package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
- package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
- package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
- package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
- package/src/providers/integrations/qoder/qoder-types.ts +9 -0
- package/src/providers/integrations/qoder/qoder.ts +3 -25
- package/src/providers/integrations/sensenova/provider.ts +144 -0
- package/src/providers/integrations/trae/trae-quota.ts +15 -1
- package/src/providers/integrations/vercel/provider.ts +117 -0
- package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
- package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
- package/src/providers/integrations/zro/zro.ts +2 -1
- package/src/providers/operations/account-refresh-status.ts +14 -0
- package/src/providers/operations/client-versions.ts +95 -30
- package/src/providers/operations/oauth-refresh-lead.ts +46 -22
- package/src/providers/operations/provider-caches.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +22 -25
- package/src/providers/operations/session-resolution.ts +17 -1
- package/src/providers/provider-contracts.ts +2 -2
- package/src/providers/provider-metadata.ts +30 -0
- package/src/providers/quota/quota-contracts.ts +2 -0
- package/src/providers/quota/refresh.ts +25 -3
- package/src/security/ip-boundary.ts +1 -1
- package/src/transport/dispatch/attempt-finalize.ts +1 -1
- package/src/transport/dispatch/attempt-loop.ts +3 -3
- package/src/transport/dispatch/handler-deps.ts +1 -1
- package/src/transport/dispatch/leases.ts +1 -1
- package/src/transport/dispatch/retry-policy.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +2 -2
- package/src/transport/dispatch/streaming/stream.ts +1 -1
- package/src/transport/dispatch/upstream.ts +1 -1
- package/src/transport/shutdown-notice.ts +1 -1
- package/src/workers/buddy-growth-tasks.ts +8 -8
- package/src/workers/daily-checkin.ts +4 -4
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/zcode-gift-claim.ts +1 -1
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css +0 -2
- package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
- package/src/console/providers/catalog/discover-constants.ts +0 -6
- package/src/network/pool-health.ts +0 -198
- package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
- package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
- package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
- package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -188
- package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
- package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -179
- package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
- package/src/providers/integrations/qoder/chat-proto.ts +0 -328
- package/src/providers/integrations/qoder/qoder-grpc.ts +0 -870
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -481
- /package/dist/dashboard/assets/{app-DIXLjojg.js → app-C0gTXEoN.js} +0 -0
- /package/dist/dashboard/assets/{app-DIXLjojg.js.gz → app-C0gTXEoN.js.gz} +0 -0
- /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
- /package/src/network/pool/{byte-accounting.ts → accounting.ts} +0 -0
- /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
- /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
- /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
- /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
- /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
- /package/src/network/{rate-limit-verdict.ts → verdict.ts} +0 -0
|
@@ -19,16 +19,16 @@ import {
|
|
|
19
19
|
/**
|
|
20
20
|
* Newest Trae IDE version from the official changelog.
|
|
21
21
|
*
|
|
22
|
-
* Trae publishes no npm package and no release API, so the changelog
|
|
22
|
+
* Trae publishes no npm package and no release API, so the changelog was the
|
|
23
23
|
* only machine-readable source of the current client version. It matters
|
|
24
24
|
* upstream: intl `llm_raw_chat` answers `4015` unless the request carries the
|
|
25
25
|
* *current* client version, so a stale pin is an outage, not a cosmetic drift.
|
|
26
26
|
*
|
|
27
|
-
* The page
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* The page is now an SPA (2026-11-11): its HTML no longer embeds the release
|
|
28
|
+
* data, so this parser returns null and the pinned fallback carries the wire
|
|
29
|
+
* identity until a machine-readable feed exists again (see
|
|
30
|
+
* `VERSION_SOURCES.trae`). The parser is kept for when the page re-exposes
|
|
31
|
+
* its entries.
|
|
32
32
|
*/
|
|
33
33
|
async function traeVersion(response: Response): Promise<string | null> {
|
|
34
34
|
try {
|
|
@@ -179,11 +179,24 @@ const KIRO_SYSTEM_VERSION = "win32#10.0.22631";
|
|
|
179
179
|
/** Node version the observed Kiro client reports (Electron's bundled Node). */
|
|
180
180
|
const KIRO_NODE_VERSION = "24.18.0";
|
|
181
181
|
|
|
182
|
+
/** Clixad gateway's own CLI version: `cli_latest` on `/v1/models`. */
|
|
183
|
+
async function clixadVersion(response: Response): Promise<string | null> {
|
|
184
|
+
const body = await response.text().catch(() => "");
|
|
185
|
+
if (body.length === 0) return null;
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(body) as { cli_latest?: unknown };
|
|
188
|
+
const candidate = parsed.cli_latest;
|
|
189
|
+
return typeof candidate === "string" && isSemverish(candidate) ? candidate.trim() : null;
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
182
195
|
/** Ordered upstream sources and pinned fallbacks for every client version. */
|
|
183
196
|
export const VERSION_SOURCES = {
|
|
184
197
|
antigravity: {
|
|
185
198
|
key: "antigravity",
|
|
186
|
-
fallback: "2.
|
|
199
|
+
fallback: "2.31.0",
|
|
187
200
|
sources: [
|
|
188
201
|
{
|
|
189
202
|
url: "https://antigravity-hub-auto-updater-974169037036.us-central1.run.app/manifest/latest-arm64-mac.yml",
|
|
@@ -207,7 +220,7 @@ export const VERSION_SOURCES = {
|
|
|
207
220
|
// under `@opencode/cli`, and this feed is what its own installer reads.
|
|
208
221
|
// Fallback = the current latest release; raise it whenever the package
|
|
209
222
|
// moves so a cold process does not present a stale client build.
|
|
210
|
-
fallback: "2.2.
|
|
223
|
+
fallback: "2.2.7",
|
|
211
224
|
minVersion: "2.0.0",
|
|
212
225
|
sources: [{ url: "https://opencode.ai/update/api/latest/cli/npm" }],
|
|
213
226
|
},
|
|
@@ -247,7 +260,7 @@ export const VERSION_SOURCES = {
|
|
|
247
260
|
},
|
|
248
261
|
codex: {
|
|
249
262
|
key: "codex",
|
|
250
|
-
fallback: "0.167.
|
|
263
|
+
fallback: "0.167.1",
|
|
251
264
|
sources: [{ url: "https://registry.npmjs.org/@openai/codex/latest" }],
|
|
252
265
|
},
|
|
253
266
|
kimiCli: {
|
|
@@ -257,7 +270,7 @@ export const VERSION_SOURCES = {
|
|
|
257
270
|
},
|
|
258
271
|
kiro: {
|
|
259
272
|
key: "kiro",
|
|
260
|
-
fallback: "1.4.
|
|
273
|
+
fallback: "1.4.154",
|
|
261
274
|
sources: [
|
|
262
275
|
{
|
|
263
276
|
// Kiro's downloads page exposes no stable version string. Homebrew
|
|
@@ -270,7 +283,7 @@ export const VERSION_SOURCES = {
|
|
|
270
283
|
},
|
|
271
284
|
codebuddy: {
|
|
272
285
|
key: "codebuddy",
|
|
273
|
-
fallback: "2.
|
|
286
|
+
fallback: "2.183.0",
|
|
274
287
|
sources: [
|
|
275
288
|
{ url: "https://registry.npmjs.org/@tencent-ai/codebuddy-code/latest" },
|
|
276
289
|
{ url: "https://registry.npmmirror.com/@tencent-ai/codebuddy-code/latest" },
|
|
@@ -278,7 +291,7 @@ export const VERSION_SOURCES = {
|
|
|
278
291
|
},
|
|
279
292
|
codebuddyCn: {
|
|
280
293
|
key: "codebuddy-cn",
|
|
281
|
-
fallback: "4.12.
|
|
294
|
+
fallback: "4.12.4.43368774",
|
|
282
295
|
sources: [
|
|
283
296
|
{
|
|
284
297
|
// Official CN builds are listed by Homebrew's maintained cask feed;
|
|
@@ -293,7 +306,7 @@ export const VERSION_SOURCES = {
|
|
|
293
306
|
// Current `@anthropic-ai/claude-code` release, so the billing
|
|
294
307
|
// `cc_version=` suffix and the `claude-cli/` User-Agent stay on what
|
|
295
308
|
// upstream ships.
|
|
296
|
-
fallback: "2.1.
|
|
309
|
+
fallback: "2.1.322",
|
|
297
310
|
sources: [{ url: "https://registry.npmjs.org/@anthropic-ai/claude-code/latest" }],
|
|
298
311
|
},
|
|
299
312
|
claudeSdk: {
|
|
@@ -320,21 +333,22 @@ export const VERSION_SOURCES = {
|
|
|
320
333
|
},
|
|
321
334
|
kilo: {
|
|
322
335
|
key: "kilo-code",
|
|
323
|
-
fallback: "7.8.
|
|
336
|
+
fallback: "7.8.28",
|
|
324
337
|
sources: [{ url: "https://open-vsx.org/api/kilocode/kilo-code" }],
|
|
325
338
|
},
|
|
326
339
|
muse: {
|
|
327
340
|
key: "muse-code",
|
|
328
|
-
fallback: "1.4.
|
|
341
|
+
fallback: "1.4.15-R8339.1",
|
|
329
342
|
sources: [{ url: "https://api.meta.ai/muse-code/channels/muse-stable" }],
|
|
330
343
|
},
|
|
331
344
|
trae: {
|
|
332
345
|
key: "trae",
|
|
333
|
-
// Fallback = the
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
|
|
346
|
+
// Fallback = the current desktop release per the official winget manifest
|
|
347
|
+
// (ByteDance.Trae 3.6.13). The docs changelog renders as an SPA and no
|
|
348
|
+
// longer exposes its newest entries to the parser below, so it is not a
|
|
349
|
+
// reliable discovery source right now — raise this pin when the desktop
|
|
350
|
+
// build moves.
|
|
351
|
+
fallback: "3.6.13",
|
|
338
352
|
sources: [
|
|
339
353
|
{
|
|
340
354
|
url: "https://docs.trae.ai/ide/changelog?_lang=en",
|
|
@@ -345,24 +359,63 @@ export const VERSION_SOURCES = {
|
|
|
345
359
|
freepi: {
|
|
346
360
|
key: "freepi",
|
|
347
361
|
// The free-pi CLI version — the `x-client-version` the gateway reports.
|
|
348
|
-
// Fallback = the
|
|
349
|
-
|
|
362
|
+
// Fallback = the current npm latest (0.6.19 as of 2026-11-12); a cold
|
|
363
|
+
// process sends it until the monitor's first successful discovery.
|
|
364
|
+
fallback: "0.6.19",
|
|
350
365
|
sources: [{ url: "https://registry.npmjs.org/free-pi-cli/latest" }],
|
|
351
366
|
},
|
|
352
367
|
perch: {
|
|
353
368
|
key: "perch",
|
|
354
369
|
// The official Perch CLI, spawned by the adapter for hosted inference.
|
|
355
|
-
|
|
370
|
+
// Fallback = the current npm latest (2.4.128 as of 2026-11-11); a cold
|
|
371
|
+
// process sends it until the monitor's first successful discovery.
|
|
372
|
+
fallback: "2.4.128",
|
|
356
373
|
sources: [{ url: "https://registry.npmjs.org/perchai-cli/latest" }],
|
|
357
374
|
},
|
|
375
|
+
clixad: {
|
|
376
|
+
key: "clixad",
|
|
377
|
+
// The Clixad gateway publishes the CLI build it is paired with as
|
|
378
|
+
// `cli_latest` on `/v1/models` (unauthenticated). Fallback = the version
|
|
379
|
+
// the 0.0.1-beta.50 bundle shipped (verified live 2026-11-11); a cold
|
|
380
|
+
// process sends it until the monitor's first successful discovery.
|
|
381
|
+
fallback: "0.0.1-beta.50",
|
|
382
|
+
sources: [
|
|
383
|
+
{
|
|
384
|
+
url: "https://clixad.onrender.com/v1/models",
|
|
385
|
+
extract: clixadVersion,
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
},
|
|
389
|
+
freebuff: {
|
|
390
|
+
key: "freebuff",
|
|
391
|
+
// No machine-readable public feed exists for Freebuff Desktop. GitHub
|
|
392
|
+
// releases only publish Codecane (a separate CLI line) staging builds,
|
|
393
|
+
// npm `freebuff` is the CLI (a different product), and the site's
|
|
394
|
+
// download links are client-rendered with no stable asset path.
|
|
395
|
+
// Fallback = the version unpacked from the official Windows installer
|
|
396
|
+
// during the wire audit (2026-11-09, Freebuff Desktop 0.0.201). Raise it
|
|
397
|
+
// when the next installer bundle is audited.
|
|
398
|
+
fallback: "0.0.201",
|
|
399
|
+
sources: [],
|
|
400
|
+
},
|
|
401
|
+
deepai: {
|
|
402
|
+
key: "deepai",
|
|
403
|
+
// No machine-readable public feed exists for the DeepAI Code desktop app
|
|
404
|
+
// (macOS/Windows only, no npm/CLI line). Fallback = the version unpacked
|
|
405
|
+
// from the audited bundle (2026-11-11, DeepAI Code 0.3.35). Raise it
|
|
406
|
+
// when the next bundle is audited. Pinned-only by design for the same
|
|
407
|
+
// reason as `freebuff`.
|
|
408
|
+
fallback: "0.3.35",
|
|
409
|
+
sources: [],
|
|
410
|
+
},
|
|
358
411
|
cursor: {
|
|
359
412
|
key: "cursor",
|
|
360
|
-
// Fallback = the
|
|
361
|
-
// Discovery replaces it within one monitor tick; it
|
|
362
|
-
// process sends before the first successful fetch.
|
|
363
|
-
// `FALLBACK_CLIENT_VERSION` in the cursor adapter so the
|
|
364
|
-
// request header always send one identity.
|
|
365
|
-
fallback: "cli-3.29.
|
|
413
|
+
// Fallback = the newest stable build from the official download API
|
|
414
|
+
// (cli-3.29.15-86776fc). Discovery replaces it within one monitor tick; it
|
|
415
|
+
// is only what a cold process sends before the first successful fetch.
|
|
416
|
+
// Kept equal to `FALLBACK_CLIENT_VERSION` in the cursor adapter so the
|
|
417
|
+
// badge and the request header always send one identity.
|
|
418
|
+
fallback: "cli-3.29.15-86776fc",
|
|
366
419
|
sources: [
|
|
367
420
|
{
|
|
368
421
|
url: "https://cursor.com/api/download?platform=win32-x64-user&releaseTrack=stable",
|
|
@@ -376,7 +429,7 @@ export const VERSION_SOURCES = {
|
|
|
376
429
|
// comment this replaced claimed no semver existed and pinned a date tag
|
|
377
430
|
// (`v2026.9.24`) as the version — a tag, not a release, and not the
|
|
378
431
|
// identity the client sends.
|
|
379
|
-
fallback: "0.23.
|
|
432
|
+
fallback: "0.23.1",
|
|
380
433
|
sources: [
|
|
381
434
|
{
|
|
382
435
|
url: "https://api.github.com/repos/NousResearch/hermes-agent/releases/latest",
|
|
@@ -403,6 +456,9 @@ const resolvers = {
|
|
|
403
456
|
trae: createClientVersionResolver({ ...VERSION_SOURCES.trae, client: "trae" }),
|
|
404
457
|
freepi: createClientVersionResolver({ ...VERSION_SOURCES.freepi, client: "freepi" }),
|
|
405
458
|
perch: createClientVersionResolver({ ...VERSION_SOURCES.perch, client: "perch" }),
|
|
459
|
+
clixad: createClientVersionResolver({ ...VERSION_SOURCES.clixad, client: "clixad" }),
|
|
460
|
+
freebuff: createClientVersionResolver({ ...VERSION_SOURCES.freebuff, client: "freebuff" }),
|
|
461
|
+
deepai: createClientVersionResolver({ ...VERSION_SOURCES.deepai, client: "deepai" }),
|
|
406
462
|
cursor: createClientVersionResolver({ ...VERSION_SOURCES.cursor, client: "cursor" }),
|
|
407
463
|
opencode: createClientVersionResolver({ ...VERSION_SOURCES.opencode, client: "opencode" }),
|
|
408
464
|
commandcode: createClientVersionResolver({ ...VERSION_SOURCES.commandcode, client: "command-code" }),
|
|
@@ -424,6 +480,11 @@ const resolvers = {
|
|
|
424
480
|
} satisfies Record<keyof typeof VERSION_SOURCES, ClientVersionResolver>;
|
|
425
481
|
const PROVIDER_VERSION_RESOLVERS: Readonly<Record<string, ClientVersionResolver>> = {
|
|
426
482
|
antigravity: resolvers.antigravity,
|
|
483
|
+
freepi: resolvers.freepi,
|
|
484
|
+
perch: resolvers.perch,
|
|
485
|
+
clixad: resolvers.clixad,
|
|
486
|
+
freebuff: resolvers.freebuff,
|
|
487
|
+
deepai: resolvers.deepai,
|
|
427
488
|
grok: resolvers.grok,
|
|
428
489
|
xai: resolvers.grok,
|
|
429
490
|
opencode: resolvers.opencode,
|
|
@@ -537,6 +598,10 @@ export const getQoderVersion = accessor("qoder").get;
|
|
|
537
598
|
export const getHermesVersion = accessor("hermes").get;
|
|
538
599
|
export const getTraeVersion = accessor("trae").get;
|
|
539
600
|
export const getAntigravityVersion = accessor("antigravity").get;
|
|
601
|
+
export const getFreepiVersion = accessor("freepi").get;
|
|
602
|
+
export const getPerchVersion = accessor("perch").get;
|
|
603
|
+
export const getFreebuffVersion = accessor("freebuff").get;
|
|
604
|
+
export const getDeepAIVersion = accessor("deepai").get;
|
|
540
605
|
|
|
541
606
|
export const getOpenCodeVersion = accessor("opencode").get;
|
|
542
607
|
|
|
@@ -1,22 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-provider proactive OAuth refresh lead.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* where re-minting every few minutes is pointless traffic. And some providers
|
|
8
|
-
* (Google-backed) rate-limit the token endpoint aggressively, so refreshing
|
|
9
|
-
* only when genuinely close to expiry matters more than reacting fast.
|
|
4
|
+
* The lead is how long *before* expiry a token becomes eligible for the
|
|
5
|
+
* scheduled refresh sweep. It exists so the sweep re-mints a token before it
|
|
6
|
+
* dies — never to push refreshes any more often than that.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* Buckets are deliberately coarse. A single global "5 minutes before expiry"
|
|
9
|
+
* window is wrong: some providers issue short-lived tokens that a 5-minute
|
|
10
|
+
* lead can miss under a loaded sweep, while others issue very long-lived
|
|
11
|
+
* tokens where re-minting days early is pointless traffic. Three buckets cover
|
|
12
|
+
* both ends without a hand-tuned value per provider:
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* - daily (default): tokens that live hours to days — refresh once a day at
|
|
15
|
+
* most, or when the sweep sees the token enter its final 24h.
|
|
16
|
+
* - 3 days: multi-day tokens — refresh at most ~3 days before expiry.
|
|
17
|
+
* - 7 weeks: very long-lived tokens — refresh ~7 weeks before expiry, so a
|
|
18
|
+
* ~6-month token is touched roughly once a quarter.
|
|
19
|
+
*
|
|
20
|
+
* Short-lived tokens (below the daily bucket) keep tighter leads because the
|
|
21
|
+
* invariant is "never let the token expire": a lead larger than the token
|
|
22
|
+
* lifetime would make the token permanently due and the sweep would re-mint it
|
|
23
|
+
* on every tick, which is the wasteful behavior these buckets exist to remove.
|
|
24
|
+
*
|
|
25
|
+
* Real expiries (from the first-party clients these providers model):
|
|
26
|
+
* - Google-backed (Antigravity): access tokens live ~1h; 5 min lead.
|
|
27
|
+
* - Claude Code: CLI session tokens live ~8h; 4h lead keeps one work
|
|
28
|
+
* session on a single token.
|
|
29
|
+
* - xAI/Grok CLI and Kimi: short-lived tokens; 5 min lead.
|
|
30
|
+
* - ChatGPT/Codex: access tokens live ~5 days; 3-day lead re-mints ~2 days
|
|
31
|
+
* before expiry.
|
|
32
|
+
* - iFlow: very long-lived tokens; 7-week lead.
|
|
18
33
|
*/
|
|
19
|
-
export const DEFAULT_REFRESH_LEAD_MS =
|
|
34
|
+
export const DEFAULT_REFRESH_LEAD_MS = 24 * 60 * 60 * 1000;
|
|
35
|
+
|
|
36
|
+
/** 3-day lead for multi-day tokens. */
|
|
37
|
+
export const REFRESH_LEAD_3DAY_MS = 3 * 24 * 60 * 60 * 1000;
|
|
38
|
+
|
|
39
|
+
/** 7-week lead for very long-lived tokens. */
|
|
40
|
+
export const REFRESH_LEAD_7WEEK_MS = 7 * 7 * 24 * 60 * 60 * 1000;
|
|
20
41
|
|
|
21
42
|
/**
|
|
22
43
|
* Per-provider refresh lead in milliseconds. Providers absent here use
|
|
@@ -24,19 +45,22 @@ export const DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
|
24
45
|
* `provider_accounts.provider_id`.
|
|
25
46
|
*/
|
|
26
47
|
export const REFRESH_LEAD_MS: Readonly<Record<string, number>> = Object.freeze({
|
|
27
|
-
// Google-backed token endpoints throttle
|
|
28
|
-
//
|
|
48
|
+
// Google-backed token endpoints throttle, and the access token itself lives
|
|
49
|
+
// only ~1h — a tight lead (below the token lifetime) is what keeps the
|
|
50
|
+
// sweep from re-minting on every tick.
|
|
29
51
|
antigravity: 5 * 60 * 1000,
|
|
30
|
-
// Claude Code access tokens live ~8h
|
|
31
|
-
//
|
|
52
|
+
// Claude Code access tokens live ~8h; 4h keeps a full work session on one
|
|
53
|
+
// token without permanent due-state.
|
|
32
54
|
claude: 4 * 60 * 60 * 1000,
|
|
33
|
-
//
|
|
34
|
-
codex: 5 * 24 * 60 * 60 * 1000,
|
|
35
|
-
// xAI/Grok CLI and Kimi both re-mint on a 5-minute lead.
|
|
55
|
+
// xAI/Grok CLI and Kimi re-mint on a 5-minute lead (short-lived tokens).
|
|
36
56
|
grok: 5 * 60 * 1000,
|
|
37
57
|
kimi: 5 * 60 * 1000,
|
|
38
|
-
//
|
|
39
|
-
|
|
58
|
+
// ChatGPT/Codex access tokens live ~5 days; a 3-day lead re-mints ~2 days
|
|
59
|
+
// before expiry — one refresh per weekend, not per request.
|
|
60
|
+
codex: REFRESH_LEAD_3DAY_MS,
|
|
61
|
+
// iFlow tokens are very long-lived; a 7-week lead avoids pointless churn
|
|
62
|
+
// while still re-minting well before expiry.
|
|
63
|
+
iflow: REFRESH_LEAD_7WEEK_MS,
|
|
40
64
|
});
|
|
41
65
|
|
|
42
66
|
/** The refresh lead for `providerId`, falling back to the shared default. */
|
|
@@ -7,7 +7,7 @@ import { trackModelCatalogLoad } from "../../observability/performance-metrics";
|
|
|
7
7
|
import { resolveRedisClient } from "../../persistence/redis";
|
|
8
8
|
import { cachedRead } from "../../console/shared/read-through-cache";
|
|
9
9
|
import { TtlCacheFamily } from "../../persistence/ttl-cache";
|
|
10
|
-
import { retryWithBackoff } from "../../network/retry";
|
|
10
|
+
import { retryWithBackoff } from "../../network/util/retry";
|
|
11
11
|
|
|
12
12
|
/* -------------------------------------------------------------------------- */
|
|
13
13
|
/* 1. Model Catalog Cache */
|
|
@@ -161,6 +161,8 @@ export interface AccountWithOauthStateRow {
|
|
|
161
161
|
export interface LoadAccountWithOauthStateOptions {
|
|
162
162
|
/** Select `status` from providerAccounts. */
|
|
163
163
|
readonly includeStatus?: boolean;
|
|
164
|
+
/** Select the last health category from providerAccounts. */
|
|
165
|
+
readonly includeLastErrorCategory?: boolean;
|
|
164
166
|
/** Select `expiresAt` from providerOauthStates. */
|
|
165
167
|
readonly includeExpiry?: boolean;
|
|
166
168
|
/** Also compute the skew-adjusted OAuth due time (see `loadAccountWithFreshness`). */
|
|
@@ -174,6 +176,7 @@ export interface LoadedAccountWithOauthState {
|
|
|
174
176
|
readonly id: string;
|
|
175
177
|
readonly providerId: string;
|
|
176
178
|
readonly credentialKind: CredentialKind;
|
|
179
|
+
readonly lastErrorCategory?: string | null;
|
|
177
180
|
readonly credentialCiphertext: Buffer | null;
|
|
178
181
|
readonly refreshCiphertext: Buffer | null;
|
|
179
182
|
readonly authState: unknown;
|
|
@@ -209,6 +212,9 @@ export async function loadAccountWithOauthState(
|
|
|
209
212
|
clientSecretCiphertext: providerOauthStates.clientSecretCiphertext,
|
|
210
213
|
staticToken: providerAccounts.staticToken,
|
|
211
214
|
...(options.includeStatus === true ? { status: providerAccounts.status } : {}),
|
|
215
|
+
...(options.includeLastErrorCategory === true
|
|
216
|
+
? { lastErrorCategory: providerAccounts.lastErrorCategory }
|
|
217
|
+
: {}),
|
|
212
218
|
...(options.includeExpiry === true || options.includeDueAt === true
|
|
213
219
|
? { expiresAt: providerOauthStates.expiresAt }
|
|
214
220
|
: {}),
|
|
@@ -225,14 +231,12 @@ export async function loadAccountWithOauthState(
|
|
|
225
231
|
if (!row) return undefined;
|
|
226
232
|
if (options.includeDueAt !== true) return row;
|
|
227
233
|
const lead = options.skewMs ?? refreshLeadMs(row.providerId);
|
|
228
|
-
// A refresh-capable OAuth account with no stored expiry
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const unknownExpiryButRefreshable =
|
|
235
|
-
kindOauth && !row.staticToken && hasRefreshGrant && row.expiresAt == null;
|
|
234
|
+
// A refresh-capable OAuth account with no stored expiry has an unknown
|
|
235
|
+
// liveness, but the scheduled sweep is the only refresher: marking it due
|
|
236
|
+
// here would refresh on every load, and the dispatch path deliberately no
|
|
237
|
+
// longer refreshes inline. Unknown expiry therefore means "not due yet" —
|
|
238
|
+
// if the token actually died, the upstream's 401 triggers the dispatch
|
|
239
|
+
// retry refresh (see `attempt-loop.ts`).
|
|
236
240
|
// A static token is never "due": it is used as issued and there is no
|
|
237
241
|
// refresh to run, so the dispatch path must not treat it as stale.
|
|
238
242
|
const dueAt =
|
|
@@ -240,9 +244,7 @@ export async function loadAccountWithOauthState(
|
|
|
240
244
|
? undefined
|
|
241
245
|
: row.expiresAt != null
|
|
242
246
|
? row.expiresAt.getTime() - lead
|
|
243
|
-
:
|
|
244
|
-
? 0
|
|
245
|
-
: undefined;
|
|
247
|
+
: undefined;
|
|
246
248
|
return {
|
|
247
249
|
...row,
|
|
248
250
|
...(dueAt === undefined ? {} : { dueAt }),
|
|
@@ -293,7 +295,7 @@ export async function resolveCredentialForAccount(
|
|
|
293
295
|
db: CartethyiaDatabase,
|
|
294
296
|
providerId: string,
|
|
295
297
|
accountId: string,
|
|
296
|
-
|
|
298
|
+
_oauth?: ResolveCredentialOAuth,
|
|
297
299
|
): Promise<ResolvedCredential> {
|
|
298
300
|
// Cache hit: skip DB round-trip + AES-GCM decrypt.
|
|
299
301
|
// Don't cache OAuth credentials whose token is due for refresh soon —
|
|
@@ -338,19 +340,14 @@ export async function resolveCredentialForAccount(
|
|
|
338
340
|
);
|
|
339
341
|
}
|
|
340
342
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// worse than letting the server say so.
|
|
350
|
-
const refreshed = await oauth.refreshService.ensureFreshAccessToken(accountId, refresher);
|
|
351
|
-
if (refreshed) secret = refreshed;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
343
|
+
// No inline refresh on the request path. OAuth tokens are kept fresh by the
|
|
344
|
+
// scheduled refresh sweep (`oauth-refresh-sweep`, due by `refreshLeadMs`),
|
|
345
|
+
// and a token that still expired before its sweep turn is answered by the
|
|
346
|
+
// upstream's own 401, which the dispatch attempt-loop turns into a forced
|
|
347
|
+
// refresh (see `attempt-loop.ts`). Refreshing here blocked a client request
|
|
348
|
+
// on the token endpoint — a hung endpoint held the request for the full 30 s
|
|
349
|
+
// refresh timeout — and re-minted tokens on every expired request instead of
|
|
350
|
+
// once per schedule.
|
|
354
351
|
|
|
355
352
|
const authState = jsonbRecord(row.authState);
|
|
356
353
|
const alternative: CredentialAlternative = {
|
|
@@ -13,13 +13,29 @@ const SESSION_HEADERS = [
|
|
|
13
13
|
"session-id",
|
|
14
14
|
] as const;
|
|
15
15
|
|
|
16
|
+
/** Ceiling for a session id, mirroring the OpenCode free tier's 256-char cap. */
|
|
17
|
+
const MAX_INBOUND_SESSION_LENGTH = 256;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Normalize an inbound session id the same way the OpenCode CLI does: trim,
|
|
21
|
+
* reject empty strings, and cap pathological lengths so a defective header can
|
|
22
|
+
* never become a cache key or a sticky-routing key hundreds of bytes long.
|
|
23
|
+
* Returns undefined when the value is unusable.
|
|
24
|
+
*/
|
|
25
|
+
export function normalizeInboundSessionId(value: string | undefined): string | undefined {
|
|
26
|
+
if (typeof value !== "string") return undefined;
|
|
27
|
+
const trimmed = value.trim();
|
|
28
|
+
if (trimmed.length === 0 || trimmed.length > MAX_INBOUND_SESSION_LENGTH) return undefined;
|
|
29
|
+
return trimmed;
|
|
30
|
+
}
|
|
31
|
+
|
|
16
32
|
export function resolveInboundSessionId(
|
|
17
33
|
context?: ProviderDispatchContext,
|
|
18
34
|
request?: CanonicalRequest,
|
|
19
35
|
): string | undefined {
|
|
20
36
|
const headers = context?.request_headers;
|
|
21
37
|
for (const name of SESSION_HEADERS) {
|
|
22
|
-
const value = headers?.[name]
|
|
38
|
+
const value = normalizeInboundSessionId(headers?.[name]);
|
|
23
39
|
if (value) return value;
|
|
24
40
|
}
|
|
25
41
|
const conversationId = request?.conversation?.conversation_id;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { CanonicalEvent, CanonicalRequest, WireFamily } from "../protocol/canonical-model";
|
|
6
6
|
import { GatewayError } from "../protocol/gateway-error";
|
|
7
7
|
import { BUNDLED_PROVIDER_METADATA, type BundledProviderId } from "./provider-metadata";
|
|
8
|
-
import type { ValidatedOutboundHttp2Bidi } from "../network/
|
|
8
|
+
import type { ValidatedOutboundHttp2Bidi } from "../network/bidi";
|
|
9
9
|
|
|
10
10
|
/** Closed union of every shipped provider ID. */
|
|
11
11
|
export const BUNDLED_PROVIDER_IDS: ReadonlySet<BundledProviderId> = new Set(
|
|
@@ -134,7 +134,7 @@ export type ValidatedOutboundWebSocket = (
|
|
|
134
134
|
/**
|
|
135
135
|
* The dialer contract handed to a provider adapter.
|
|
136
136
|
*
|
|
137
|
-
* Declared in `network/
|
|
137
|
+
* Declared in `network/bidi.ts` — the transport owns the contract — and
|
|
138
138
|
* re-exported here so provider code keeps one import site.
|
|
139
139
|
*/
|
|
140
140
|
export type { ValidatedOutboundHttp2Bidi };
|
|
@@ -172,6 +172,14 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
|
|
|
172
172
|
{ id: "opencode-go", displayName: "OpenCode Go", baseUrl: "https://opencode.ai", credentialUrl: "https://opencode.ai/auth" },
|
|
173
173
|
{ id: "cerebras", displayName: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", credentialUrl: "https://cloud.cerebras.ai/platform" },
|
|
174
174
|
{ id: "openrouter", displayName: "OpenRouter", baseUrl: "https://openrouter.ai/api/v1", credentialUrl: "https://openrouter.ai/settings/keys" },
|
|
175
|
+
{
|
|
176
|
+
id: "vercel",
|
|
177
|
+
displayName: "Vercel AI Gateway",
|
|
178
|
+
baseUrl: "https://ai-gateway.vercel.sh/v1",
|
|
179
|
+
defaultSkipProxy: true,
|
|
180
|
+
credentialUrl: "https://vercel.com/dashboard/ai",
|
|
181
|
+
credentialHint: "Create an AI Gateway API key (`vercel ai-gateway api-keys create`) or paste an existing `vck_` key. The key is bearer-authenticated against the OpenAI-compatible /v1 surface.",
|
|
182
|
+
},
|
|
175
183
|
{ id: "mistral", displayName: "Mistral AI", baseUrl: "https://api.mistral.ai/v1", credentialUrl: "https://console.mistral.ai/api-keys" },
|
|
176
184
|
{ id: "fireworks", displayName: "Fireworks AI", baseUrl: "https://api.fireworks.ai/inference/v1", credentialUrl: "https://fireworks.ai/account/api-keys" },
|
|
177
185
|
{ id: "nvidia", displayName: "NVIDIA NIM", baseUrl: "https://integrate.api.nvidia.com/v1", credentialUrl: "https://build.nvidia.com/settings/api-keys" },
|
|
@@ -180,6 +188,22 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
|
|
|
180
188
|
// and the registry pins the Messages endpoint separately.
|
|
181
189
|
{ id: "deepseek", displayName: "DeepSeek", baseUrl: "https://api.deepseek.com", wireFamilyDefault: "messages", credentialUrl: "https://platform.deepseek.com/api_keys" },
|
|
182
190
|
{ id: "gmi", displayName: "GMI Cloud", baseUrl: "https://api.gmi-serving.com/v1", credentialUrl: "https://console.gmicloud.ai" },
|
|
191
|
+
{
|
|
192
|
+
id: "clixad",
|
|
193
|
+
displayName: "Clixad",
|
|
194
|
+
baseUrl: "https://clixad.onrender.com/v1",
|
|
195
|
+
requiresAccount: true,
|
|
196
|
+
credentialUrl: "https://clixad.io",
|
|
197
|
+
credentialHint: "Sign in with GitHub (device flow) or paste a Clixad token (adg_...). Model calls draw from the account balance and a daily free tier (120k credits/day).",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "deepai",
|
|
201
|
+
displayName: "DeepAI",
|
|
202
|
+
baseUrl: "https://api.deepai.org",
|
|
203
|
+
requiresAccount: true,
|
|
204
|
+
credentialUrl: "https://deepai.org",
|
|
205
|
+
credentialHint: "Sign in via the DeepAI desktop flow (loopback exchange → api_key) or paste a DeepAI api_key. Model calls are authenticated with the Api-Key header; the daily free allowance is 50 Theos and 11 models draw nothing from it.",
|
|
206
|
+
},
|
|
183
207
|
|
|
184
208
|
// OpenAI-compatible bearer aggregators. Each is a bare host already rooted at
|
|
185
209
|
// its own version prefix, so the `/v1`-prefixed wire paths stay pinned by the
|
|
@@ -198,6 +222,12 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
|
|
|
198
222
|
{ id: "synthetic", displayName: "Synthetic", baseUrl: "https://api.synthetic.new/v1", credentialUrl: "https://app.synthetic.new/api-keys" },
|
|
199
223
|
{ id: "stepfun", displayName: "StepFun", baseUrl: "https://api.stepfun.com/v1", credentialUrl: "https://platform.stepfun.com/console/keys" },
|
|
200
224
|
{ id: "sakana", displayName: "Sakana AI", baseUrl: "https://api.sakana.ai/v1", credentialUrl: "https://console.sakana.ai/api-keys" },
|
|
225
|
+
// SenseNova keys are per-site: China keys (token.sensenova.cn) and
|
|
226
|
+
// international keys (token.sensenova.ai) reject each other with 401
|
|
227
|
+
// "Forbidden", so the two deployments are separate providers with separate
|
|
228
|
+
// hosts and catalogs — the same split as CodeBuddy cb/cbcn.
|
|
229
|
+
{ id: "sensenova", displayName: "SenseNova", baseUrl: "https://token.sensenova.cn/v1", serviceKinds: ["llm", "imageGeneration"], credentialUrl: "https://token.sensenova.cn", credentialHint: "Paste a SenseNova API key created on platform.sensenova.cn. International accounts use SenseNova (Intl)." },
|
|
230
|
+
{ id: "sensenova-intl", displayName: "SenseNova (Intl)", baseUrl: "https://token.sensenova.ai/v1", serviceKinds: ["llm", "imageGeneration"], credentialUrl: "https://platform.sensenova.ai", credentialHint: "Paste a SenseNova API key created on platform.sensenova.ai. China accounts use SenseNova." },
|
|
201
231
|
{ id: "helmcode", displayName: "HelmCode", baseUrl: "https://api.helmcode.com/v1", credentialUrl: "https://helmcode.com/dashboard" },
|
|
202
232
|
{ id: "coralbricks", displayName: "CoralBricks", baseUrl: "https://api.coralbricks.com/v1", credentialUrl: "https://coralbricks.com/api-keys" },
|
|
203
233
|
{ id: "aiand", displayName: "ai&", baseUrl: "https://api.aiand.com/v1", credentialUrl: "https://console.aiand.com/api-keys" },
|
|
@@ -29,6 +29,8 @@ export interface ProviderQuotaResult {
|
|
|
29
29
|
readonly plan: string | null;
|
|
30
30
|
readonly windows: readonly ProviderQuotaWindow[];
|
|
31
31
|
readonly error: string | null;
|
|
32
|
+
/** The provider explicitly rejected the account credential; do not retry it automatically. */
|
|
33
|
+
readonly authInvalidated?: boolean;
|
|
32
34
|
/** Display identity the billing surface reported, when it reported one. */
|
|
33
35
|
readonly accountLabel?: string;
|
|
34
36
|
}
|
|
@@ -21,6 +21,7 @@ import { persistQuotaSnapshot } from "./snapshot";
|
|
|
21
21
|
import { applyAccountVerdict } from "../operations/account-health-service";
|
|
22
22
|
import { asJsonbRecord } from "../../shared/coerce";
|
|
23
23
|
import { KIRO_DEFAULT_LABELS } from "../integrations/kiro/kiro-oauth";
|
|
24
|
+
import { invalidateCredentialCache } from "../operations/provider-credential-service";
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* The stock Kiro fallback labels as a SQL literal list. Built once from the
|
|
@@ -34,7 +35,7 @@ const KIRO_DEFAULT_LABEL_IN = sql.raw(
|
|
|
34
35
|
);
|
|
35
36
|
|
|
36
37
|
/** Why an account quota refresh failed; `null` means the fetch succeeded. */
|
|
37
|
-
export type QuotaRefreshFailureCode = "missing_credential" | "quota_fetch_failed";
|
|
38
|
+
export type QuotaRefreshFailureCode = "missing_credential" | "quota_fetch_failed" | "auth_invalidated";
|
|
38
39
|
|
|
39
40
|
export interface QuotaRefreshOutcome {
|
|
40
41
|
readonly quota: ProviderQuotaResult;
|
|
@@ -150,9 +151,25 @@ function isTransientTimeout(error: string | null | undefined): boolean {
|
|
|
150
151
|
export async function recordAccountCheck(
|
|
151
152
|
db: CartethyiaDatabase,
|
|
152
153
|
accountId: string,
|
|
153
|
-
outcome: { ok: boolean; error?: string | null; category?: string | null },
|
|
154
|
+
outcome: { ok: boolean; error?: string | null; category?: string | null; authInvalidated?: boolean },
|
|
154
155
|
snapshotInvalidator?: { invalidate(): unknown },
|
|
155
156
|
): Promise<void> {
|
|
157
|
+
if (outcome.authInvalidated === true) {
|
|
158
|
+
const disabled = await applyAccountVerdict(db, {
|
|
159
|
+
accountId,
|
|
160
|
+
status: "disabled",
|
|
161
|
+
reason: outcome.error ?? "Provider rejected account authentication",
|
|
162
|
+
category: "auth_invalidated",
|
|
163
|
+
// No deadline: a credential rejected as invalid cannot heal by waiting,
|
|
164
|
+
// and the OAuth refresh sweep must not keep spending refresh attempts.
|
|
165
|
+
cooldownUntil: null,
|
|
166
|
+
});
|
|
167
|
+
if (disabled) {
|
|
168
|
+
invalidateCredentialCache(accountId);
|
|
169
|
+
await snapshotInvalidator?.invalidate();
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
156
173
|
if (outcome.ok) {
|
|
157
174
|
// A successful quota refresh is the freshest evidence about the account's
|
|
158
175
|
// health: heal a cooldown that the refresh shows is no longer justified, so
|
|
@@ -453,7 +470,11 @@ async function runQuotaRefresh(
|
|
|
453
470
|
error: error instanceof Error ? error.message : "Failed to refresh quota",
|
|
454
471
|
};
|
|
455
472
|
}
|
|
456
|
-
failure = quota.error === null
|
|
473
|
+
failure = quota.error === null
|
|
474
|
+
? null
|
|
475
|
+
: quota.authInvalidated === true
|
|
476
|
+
? "auth_invalidated"
|
|
477
|
+
: "quota_fetch_failed";
|
|
457
478
|
}
|
|
458
479
|
|
|
459
480
|
// Cache even a failed fetch: the error view ("credential expired", "quota
|
|
@@ -527,6 +548,7 @@ async function runQuotaRefresh(
|
|
|
527
548
|
{
|
|
528
549
|
ok: quota.error === null,
|
|
529
550
|
...(quota.error === null ? {} : { error: quota.error, category: failure ?? "quota_fetch_failed" }),
|
|
551
|
+
...(quota.authInvalidated === true ? { authInvalidated: true } : {}),
|
|
530
552
|
},
|
|
531
553
|
deps.snapshotInvalidator,
|
|
532
554
|
);
|
|
@@ -20,7 +20,7 @@ import { modelsDevCatalog } from "../../providers/discovery/models-dev-catalog";
|
|
|
20
20
|
import {
|
|
21
21
|
disablePoolForProxyHttpStatus,
|
|
22
22
|
recordPoolDispatchOutcome,
|
|
23
|
-
} from "../../network/pool
|
|
23
|
+
} from "../../network/pool/health";
|
|
24
24
|
import { ProxyConnectRefusalError } from "../../network/pool/agent";
|
|
25
25
|
import { captureDepthMaxBytes, providerCaptureMaxBytes } from "./capture-policy";
|
|
26
26
|
|