cartethyia 2.7.9 → 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 +6 -5
- package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Dq530awD.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-DaZZ3cqP.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BlC_xdkX.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-7xT5yg2E.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-6Teoq9Se.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CZuI0MAR.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-C1hNTlkr.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-BE40s3u5.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-CQ6bPUf_.js → ProviderIcon-Ddhm1InL.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js +1 -0
- 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-D1RV_2BX.js +1 -0
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-BzZAvYSX.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage--s-EfEIL.js} +1 -1
- package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-BjXFGSK4.js} +1 -1
- package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-CP8F8BN7.js} +1 -1
- package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BTlkq_Uk.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-BMcj1xZE.js +1 -0
- package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-DkRUuVfz.js} +1 -1
- package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-CDjW841Q.js} +1 -1
- package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
- package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-Bto5UlL9.js} +1 -1
- package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-DaH6g9hL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-BYKBCzjk.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-DL0Zftwv.js} +1 -1
- package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-Dpx_UXaS.js} +1 -1
- package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
- package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-BV7-1NoM.js} +1 -1
- package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-CGvdNU6r.js} +1 -1
- package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js +4 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-BS3DZlZj.js} +1 -1
- package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
- package/dist/dashboard/assets/{index-BU64Lqvp.js → index-SQnvvTWX.js} +2 -2
- package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-Dl-cuLK0.js} +1 -1
- package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
- package/dist/dashboard/assets/{input-BCVkdR9d.js → input-Bfx0EzRc.js} +1 -1
- package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-BxhbQ_JS.js} +1 -1
- package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-D49Y4ptn.js} +1 -1
- package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
- package/dist/dashboard/assets/{live-Cae01iu8.js → live-CbVTdX9V.js} +1 -1
- package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
- package/dist/dashboard/assets/{network-CRib7H_2.js → network-JGvDveof.js} +1 -1
- package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-Dw6kiew-.js} +1 -1
- package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-ecPvG_8g.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/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-8jZr-3WE.js} +1 -1
- package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
- package/dist/dashboard/assets/{select-ChoFWyKz.js → select-oO1FxMVb.js} +2 -2
- package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-CLr3kdIx.js} +1 -1
- package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-Y3pDteZr.js} +1 -1
- package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
- package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-CArX_Iak.js} +1 -1
- package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
- package/dist/dashboard/assets/{switch-Db70BMix.js → switch-DwlwmDO-.js} +1 -1
- package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BqdE0LuM.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 +54 -53
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
- package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
- package/package.json +1 -1
- package/src/boot/app.ts +11 -14
- package/src/boot/dependencies.ts +5 -7
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/providers.ts +6 -5
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +28 -8
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +40 -28
- package/src/console/auth/account-reset.ts +7 -9
- package/src/console/backup/contracts.ts +4 -0
- package/src/console/backup/export-endpoint.ts +7 -5
- package/src/console/backup/nine-router.ts +71 -10
- package/src/console/backup/service.ts +8 -1
- package/src/console/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/console-router.ts +1 -1
- package/src/console/domain-registration.ts +7 -4
- package/src/console/inferhub/client.ts +1 -1
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +7 -13
- package/src/console/observability/live.ts +2 -2
- package/src/console/observability/store.ts +0 -53
- 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 +3 -2
- package/src/console/providers/catalog/store.ts +1914 -1902
- 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-global.ts +14 -24
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +11 -23
- package/src/console/quota/account-test-egress.ts +2 -2
- package/src/console/quota/daily-growth-job.ts +1 -1
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/contracts.ts +1 -1
- package/src/console/routing/pools/operations.ts +32 -47
- 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/console/shared/errors.ts +33 -6
- package/src/main.ts +2 -3
- 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/pool/{byte-accounting.ts → accounting.ts} +3 -2
- 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} +95 -20
- 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 +184 -41
- package/src/network/ssrf.ts +7 -7
- package/src/network/{retry.ts → util/retry.ts} +1 -1
- package/src/network/{rate-limit-verdict.ts → verdict.ts} +1 -1
- package/src/observability/event-log.ts +7 -361
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +16 -26
- package/src/observability/telemetry-reconciliation.ts +9 -37
- package/src/observability/telemetry-row.ts +125 -0
- package/src/persistence/db-mode.ts +4 -1
- package/src/persistence/dialect.ts +18 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +17 -0
- package/src/persistence/ttl-cache.ts +21 -3
- package/src/protocol/codecs.ts +59 -2
- package/src/protocol/dsml.ts +34 -86
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/request/chat.ts +10 -10
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +34 -22
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/messages.ts +34 -7
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/protocol/translation/quirks.ts +15 -0
- package/src/protocol/upstream/send-claude-messages.ts +2 -2
- package/src/providers/adapters/compatible-adapter.ts +76 -12
- package/src/providers/authentication/oauth-client.ts +40 -6
- package/src/providers/authentication/oauth-device-flow.ts +105 -11
- package/src/providers/authentication/oauth-flow-store.ts +82 -1
- package/src/providers/authentication/oauth-refresh-service.ts +766 -758
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +137 -36
- package/src/providers/discovery/discovery-types.ts +7 -0
- package/src/providers/discovery/probe-phases.ts +20 -6
- package/src/providers/discovery/probing-service.ts +22 -2
- package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
- package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +16 -1
- package/src/providers/integrations/buddy/account.ts +969 -0
- package/src/providers/integrations/buddy/capabilities.ts +12 -12
- package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
- package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +176 -20
- package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +41 -26
- package/src/providers/integrations/buddy/headers.ts +311 -0
- 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/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline-oauth.ts +10 -5
- package/src/providers/integrations/cline/cline.ts +5 -6
- 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/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +1 -9
- package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
- 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-request.ts +26 -12
- package/src/providers/integrations/cursor/cursor-shared.ts +8 -5
- package/src/providers/integrations/cursor/cursor.ts +574 -110
- 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/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/devin/protocol/devin-proto.ts +1 -27
- package/src/providers/integrations/factory/factory-oauth.ts +33 -51
- 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 +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 +52 -49
- 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 +3 -3
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +42 -13
- package/src/providers/integrations/hermes/index.ts +2 -0
- 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-catalog.ts +17 -1
- package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
- package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
- package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- package/src/providers/integrations/muse/muse-oauth.ts +5 -5
- 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/openrouter/provider.ts +1 -1
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-protocol.ts +0 -6
- package/src/providers/integrations/perch/perch-quota.ts +2 -16
- package/src/providers/integrations/perch/perch.ts +6 -16
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +20 -125
- package/src/providers/integrations/qoder/qoder-auth.ts +109 -30
- package/src/providers/integrations/qoder/qoder-catalog.ts +21 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +32 -29
- package/src/providers/integrations/qoder/qoder-checkin.ts +5 -5
- 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 -26
- package/src/providers/integrations/sensenova/provider.ts +144 -0
- package/src/providers/integrations/trae/trae-protocol.ts +9 -5
- package/src/providers/integrations/trae/trae-quota.ts +21 -29
- package/src/providers/integrations/trae/trae-realm.ts +38 -0
- package/src/providers/integrations/trae/trae.ts +7 -24
- 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/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 +205 -0
- package/src/providers/operations/account-error-classification.ts +199 -307
- package/src/providers/operations/account-health-service.ts +82 -1
- package/src/providers/operations/account-refresh-status.ts +36 -37
- package/src/providers/operations/account-tier.ts +15 -16
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +100 -81
- package/src/providers/operations/cooldown-rules.ts +11 -16
- package/src/providers/operations/oauth-refresh-lead.ts +46 -22
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +118 -51
- 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/operations/session-resolution.ts +17 -1
- package/src/providers/provider-contracts.ts +6 -4
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +70 -10
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- package/src/providers/quota/quota-contracts.ts +2 -0
- 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 +100 -21
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/security/ip-boundary.ts +1 -1
- package/src/transport/dispatch/attempt-finalize.ts +15 -14
- package/src/transport/dispatch/attempt-loop.ts +73 -19
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/handler-deps.ts +1 -1
- package/src/transport/dispatch/leases.ts +1 -1
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +8 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/retry-policy.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +10 -7
- package/src/transport/dispatch/streaming/stream.ts +9 -37
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +63 -0
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +16 -30
- package/src/transport/guards/body-policy.ts +7 -1
- package/src/transport/guards/error-lifecycle.ts +13 -1
- package/src/transport/guards/request-context.ts +22 -14
- package/src/transport/request/preparer.ts +81 -71
- package/src/transport/routing/bounded-lru.ts +14 -0
- package/src/transport/routing/drain-reserve.ts +129 -0
- package/src/transport/routing/plan-gate.ts +7 -1
- package/src/transport/routing/round-robin.ts +60 -0
- package/src/transport/routing/route-catalog.ts +123 -111
- package/src/transport/routing/route-model.ts +51 -9
- package/src/transport/routing/router.ts +393 -32
- package/src/transport/shutdown-notice.ts +1 -1
- package/src/transport/surface/adapters.ts +11 -2
- package/src/transport/surface/chat/adapter.ts +2 -2
- package/src/transport/surface/chat/encode.ts +47 -4
- package/src/transport/surface/chat/parse.ts +2 -12
- package/src/transport/surface/completion.ts +44 -33
- package/src/transport/surface/content-parts.ts +21 -0
- package/src/transport/surface/messages/adapter.ts +7 -1
- package/src/transport/surface/messages/encode.ts +11 -3
- package/src/transport/surface/messages/parse.ts +2 -1
- package/src/transport/surface/messages/stream.ts +33 -17
- package/src/transport/surface/responses/encode.ts +22 -5
- package/src/transport/surface/responses/parse.ts +3 -11
- package/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-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/src/network/pool-health.ts +0 -210
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- 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-identity.ts +0 -300
- 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 -185
- package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
- package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -175
- 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 -871
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -588
- package/src/providers/operations/model-discovery-cache.ts +0 -34
- package/src/providers/operations/probe-fetch.ts +0 -15
- package/src/providers/operations/provider-version-cache.ts +0 -30
- package/src/providers/provider-capabilities.ts +0 -33
- package/src/providers/provider-credential-envelope.ts +0 -43
- /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-C0gTXEoN.js} +0 -0
- /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-C0gTXEoN.js.gz} +0 -0
- /package/src/network/{http2-bidi.ts → bidi.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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AccountErrorRule,
|
|
3
|
+
AccountErrorSignals,
|
|
4
|
+
} from "../account-error-classification";
|
|
5
|
+
import { unsupportedModelCooldownMs } from "../account-error-classification";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Qoder's quota verdicts (`110` billing-daily-count, pricing-url refusals)
|
|
9
|
+
* are per-model: one model's spend cap does not drain another. A day matches
|
|
10
|
+
* the daily billing window the provider names.
|
|
11
|
+
*/
|
|
12
|
+
export const QODER_QUOTA_COOLDOWN_MS = 24 * 60 * 60 * 1000;
|
|
13
|
+
|
|
14
|
+
function isQoder(signals: AccountErrorSignals): boolean {
|
|
15
|
+
return signals.providerId === "qoder" || signals.providerId === "qodercn";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Qoder's code-112 pricing refusal (`{"code":"112","message":"{\"pricingUrl\":...}"}`)
|
|
20
|
+
* is a "this model is not included in this account's plan, buy more" verdict
|
|
21
|
+
* for one pair — the account keeps serving its other models. The probe path
|
|
22
|
+
* does not always carry the code in its evidence (only the message does), so
|
|
23
|
+
* the match keys on the provider id plus the pricing-page wording as well.
|
|
24
|
+
* It must run before `quota-exhausted`, which would park the whole account.
|
|
25
|
+
*/
|
|
26
|
+
export const qoderPlanGatedRule: AccountErrorRule = {
|
|
27
|
+
name: "qoder-plan-gated",
|
|
28
|
+
match: (s) =>
|
|
29
|
+
isQoder(s) &&
|
|
30
|
+
(s.providerCode === "112" || s.lower.includes("pricingurl")),
|
|
31
|
+
decide: (s) => {
|
|
32
|
+
const cooldownMs = unsupportedModelCooldownMs(s.modelBlockHits);
|
|
33
|
+
return s.result(
|
|
34
|
+
"model_unreachable",
|
|
35
|
+
"cooldown",
|
|
36
|
+
cooldownMs,
|
|
37
|
+
new Date(Date.now() + cooldownMs),
|
|
38
|
+
s.reason("Model not included in this account's plan"),
|
|
39
|
+
s.origin === "upstream" && s.modelId !== undefined,
|
|
40
|
+
true,
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Qoder's quota verdicts — `110` "Billing daily count exceeded" — are per-model:
|
|
47
|
+
* one model's spend cap does not drain another. Scoping them to the model keeps
|
|
48
|
+
* the account serving its other models. Must run before `quota-exhausted`, which
|
|
49
|
+
* parks the whole account.
|
|
50
|
+
*/
|
|
51
|
+
export const qoderModelQuotaRule: AccountErrorRule = {
|
|
52
|
+
name: "qoder-model-quota",
|
|
53
|
+
match: (s) => isQoder(s) && s.quotaSignal,
|
|
54
|
+
decide: (s) => {
|
|
55
|
+
const cooldownMs = s.statedCooldownMs ?? QODER_QUOTA_COOLDOWN_MS;
|
|
56
|
+
return s.result(
|
|
57
|
+
"model_unreachable",
|
|
58
|
+
"cooldown",
|
|
59
|
+
cooldownMs,
|
|
60
|
+
new Date(Date.now() + cooldownMs),
|
|
61
|
+
s.reason("Qoder quota exhausted for this model"),
|
|
62
|
+
s.origin === "upstream" && s.modelId !== undefined,
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const qoderRules: readonly AccountErrorRule[] = [
|
|
68
|
+
qoderPlanGatedRule,
|
|
69
|
+
qoderModelQuotaRule,
|
|
70
|
+
];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AccountErrorRule,
|
|
3
|
+
AccountErrorSignals,
|
|
4
|
+
} from "../account-error-classification";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Trae answers `4013 "AI service is not available in the current region now."`
|
|
8
|
+
* when the upstream does not serve the account's region. A day because the
|
|
9
|
+
* verdict is durable: it reproduces on every request until the region opens,
|
|
10
|
+
* and the account must not be re-dialled every thirty seconds for the whole
|
|
11
|
+
* window.
|
|
12
|
+
*/
|
|
13
|
+
export const TRAE_REGION_COOLDOWN_MS = 24 * 60 * 60 * 1000;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Trae's free-tier quota (`4008` / "exceeded the quota") resets daily, so its
|
|
17
|
+
* exhaustion is a full-day model backoff — never the generic 1h fallback,
|
|
18
|
+
* which let the account re-enter rotation inside the window.
|
|
19
|
+
*/
|
|
20
|
+
export const TRAE_QUOTA_COOLDOWN_MS = 24 * 60 * 60 * 1000;
|
|
21
|
+
|
|
22
|
+
function isTrae(signals: AccountErrorSignals): boolean {
|
|
23
|
+
return signals.providerId === "trae" || signals.providerId === "traecn";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const traeRules: readonly AccountErrorRule[] = [
|
|
27
|
+
{
|
|
28
|
+
// Trae answers `4013 "AI service is not available in the current region
|
|
29
|
+
// now."` when the upstream does not serve the account's region. That is a
|
|
30
|
+
// durable verdict on the account, not a transient transport fault: it
|
|
31
|
+
// reproduces on every request until the region opens, so it must cool hard
|
|
32
|
+
// for a day rather than be re-dialled every thirty seconds. Must run
|
|
33
|
+
// before `transport-fault`, which would read the 502 as a short
|
|
34
|
+
// `server_error`.
|
|
35
|
+
name: "trae-region-unavailable",
|
|
36
|
+
match: (s) =>
|
|
37
|
+
isTrae(s) &&
|
|
38
|
+
(s.providerCode === "4013" || s.lower.includes("not available in the current region")),
|
|
39
|
+
decide: (s) => {
|
|
40
|
+
const cooldownMs = TRAE_REGION_COOLDOWN_MS;
|
|
41
|
+
return s.result(
|
|
42
|
+
"quota_exhausted",
|
|
43
|
+
"cooldown",
|
|
44
|
+
cooldownMs,
|
|
45
|
+
new Date(Date.now() + cooldownMs),
|
|
46
|
+
s.reason("Trae service unavailable in the current region"),
|
|
47
|
+
// Account-scoped provider evidence still has to reach the writer: a
|
|
48
|
+
// stream frame carries no credential detail, so `canMutate` is false
|
|
49
|
+
// and without this the account stays in rotation to reproduce the
|
|
50
|
+
// refusal for the whole window the region is closed for.
|
|
51
|
+
true,
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
// Trae's free-tier quota (`4008` / "exceeded the quota") resets daily.
|
|
57
|
+
// Treating it like the generic quota cooldown (1h account-wide fallback)
|
|
58
|
+
// let the account re-enter rotation inside the window and reproduce the
|
|
59
|
+
// refusal every few minutes — the "cooldown not holding" report. The
|
|
60
|
+
// verdict is a full-day backoff on *any* model (the account's quota is
|
|
61
|
+
// spent, so every model on it shares the wall). Scoped to the model so
|
|
62
|
+
// the account is not disabled wholesale; the 1-day window means a model
|
|
63
|
+
// stays parked for the day instead of flapping. Must run before
|
|
64
|
+
// `quota-exhausted`, which would apply the generic shorter fallback.
|
|
65
|
+
name: "trae-quota-exhausted",
|
|
66
|
+
match: (s) =>
|
|
67
|
+
isTrae(s) &&
|
|
68
|
+
(s.providerCode === "4008" || s.lower.includes("have exceeded the quota")),
|
|
69
|
+
decide: (s) => {
|
|
70
|
+
const cooldownMs = s.statedCooldownMs ?? TRAE_QUOTA_COOLDOWN_MS;
|
|
71
|
+
return s.result(
|
|
72
|
+
"model_unreachable",
|
|
73
|
+
"cooldown",
|
|
74
|
+
cooldownMs,
|
|
75
|
+
new Date(Date.now() + cooldownMs),
|
|
76
|
+
s.reason("Trae quota exhausted for this model"),
|
|
77
|
+
s.origin === "upstream" && s.modelId !== undefined,
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
];
|
|
@@ -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,10 +5,12 @@
|
|
|
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
|
-
export const BUNDLED_PROVIDER_IDS =
|
|
11
|
+
export const BUNDLED_PROVIDER_IDS: ReadonlySet<BundledProviderId> = new Set(
|
|
12
|
+
BUNDLED_PROVIDER_METADATA.map((definition) => definition.id),
|
|
13
|
+
);
|
|
12
14
|
|
|
13
15
|
/** Normalized builtin or validated custom provider identifier. */
|
|
14
16
|
declare const customProviderIdBrand: unique symbol;
|
|
@@ -132,7 +134,7 @@ export type ValidatedOutboundWebSocket = (
|
|
|
132
134
|
/**
|
|
133
135
|
* The dialer contract handed to a provider adapter.
|
|
134
136
|
*
|
|
135
|
-
* Declared in `network/
|
|
137
|
+
* Declared in `network/bidi.ts` — the transport owns the contract — and
|
|
136
138
|
* re-exported here so provider code keeps one import site.
|
|
137
139
|
*/
|
|
138
140
|
export type { ValidatedOutboundHttp2Bidi };
|
|
@@ -451,7 +453,7 @@ function normalizeProviderSlug(id: string): string {
|
|
|
451
453
|
/** Returns true for one of Cartethyia's reserved builtin IDs, case-insensitively. */
|
|
452
454
|
export function isBundledProviderId(value: string): value is BundledProviderId {
|
|
453
455
|
const normalized = value.trim().toLowerCase();
|
|
454
|
-
return (
|
|
456
|
+
return BUNDLED_PROVIDER_IDS.has(normalized as BundledProviderId);
|
|
455
457
|
}
|
|
456
458
|
|
|
457
459
|
/** Normalizes a provider identifier before persistence or registry lookup. */
|
|
@@ -1,10 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Provider credential envelope and resolution handling.
|
|
3
|
+
*
|
|
4
|
+
* Strips token envelopes so adapters never emit `Bearer Bearer`, and resolves
|
|
5
|
+
* ordered upstream credential alternatives for the control plane.
|
|
6
|
+
*/
|
|
3
7
|
|
|
4
8
|
import { GatewayError } from "../protocol/gateway-error";
|
|
5
|
-
import { unwrapProviderToken } from "./provider-credential-envelope";
|
|
6
9
|
import type { CredentialKind, ProviderId, ResolvedCredential } from "./provider-contracts";
|
|
7
10
|
|
|
11
|
+
/** Envelope labels a stored credential may carry. */
|
|
12
|
+
export type TokenEnvelope = "bearer" | "oauth" | "provider";
|
|
13
|
+
|
|
14
|
+
const ENVELOPE_PREFIXES: Record<TokenEnvelope, string> = {
|
|
15
|
+
bearer: "Bearer ",
|
|
16
|
+
oauth: "OAuth ",
|
|
17
|
+
provider: "provider:",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Removes one explicitly identified envelope so adapters never emit
|
|
22
|
+
* `Bearer Bearer`. The bearer branch strips case-insensitively with
|
|
23
|
+
* surrounding-whitespace normalization; oauth/provider envelopes keep their
|
|
24
|
+
* exact byte slices. Input without the identified envelope passes through
|
|
25
|
+
* untouched.
|
|
26
|
+
*/
|
|
27
|
+
export function unwrapProviderToken(
|
|
28
|
+
value: string | Uint8Array,
|
|
29
|
+
envelope?: TokenEnvelope,
|
|
30
|
+
): Uint8Array {
|
|
31
|
+
const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
|
|
32
|
+
const text = new TextDecoder().decode(bytes);
|
|
33
|
+
if (envelope === "bearer") {
|
|
34
|
+
const trimmed = text.trim();
|
|
35
|
+
const match = /^bearer\s+(.+)$/is.exec(trimmed);
|
|
36
|
+
return new TextEncoder().encode((match?.[1] ?? trimmed).trim());
|
|
37
|
+
}
|
|
38
|
+
const prefix = envelope === undefined ? undefined : ENVELOPE_PREFIXES[envelope];
|
|
39
|
+
if (prefix !== undefined && text.toLowerCase().startsWith(prefix.toLowerCase())) {
|
|
40
|
+
return new TextEncoder().encode(text.slice(prefix.length));
|
|
41
|
+
}
|
|
42
|
+
return new Uint8Array(bytes);
|
|
43
|
+
}
|
|
44
|
+
|
|
8
45
|
/** One control-plane credential alternative, ordered by operator preference. */
|
|
9
46
|
export interface CredentialAlternative {
|
|
10
47
|
readonly provider_id: ProviderId;
|
|
@@ -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" },
|
|
@@ -490,6 +520,31 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
|
|
|
490
520
|
// rows are served by `/v1/search`, never by the canonical chat pipeline.
|
|
491
521
|
{ id: "exa", displayName: "Exa", baseUrl: "https://mcp.exa.ai", serviceKinds: ["websearch"], requiresAccount: false, credentialHint: "No API key. Search goes through the public MCP." },
|
|
492
522
|
{ id: "parallel", displayName: "Parallel", baseUrl: "https://search.parallel.ai", serviceKinds: ["websearch"], requiresAccount: false, credentialUrl: "https://platform.parallel.ai/settings?tab=api-keys", credentialHint: "Optional. Leave empty for the public MCP, or paste an API key for the higher-limit search API." },
|
|
523
|
+
{
|
|
524
|
+
// Freebuff (Codebuff Desktop). OAuth code flow → bearer token; every
|
|
525
|
+
// dispatch carries the desktop identity group + Ed25519 device signature
|
|
526
|
+
// (see documentation/research/freebuff/). The chat surface is
|
|
527
|
+
// OpenAI-compatible on `/api/v1/chat/completions` but requires a stateful
|
|
528
|
+
// session admission + agent-runs preflight — handled by the custom adapter.
|
|
529
|
+
id: "freebuff",
|
|
530
|
+
displayName: "Freebuff",
|
|
531
|
+
baseUrl: "https://www.codebuff.com",
|
|
532
|
+
hasAdapterUserAgent: true,
|
|
533
|
+
credentialUrl: "https://www.codebuff.com/login",
|
|
534
|
+
credentialHint: "Sign in with your Freebuff account (Desktop/CLI code flow).",
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
// Zro (moonmath). Fast, private, multi-region inference for open models.
|
|
538
|
+
// Claude Code connects via the Messages wire with a plain bearer key; the
|
|
539
|
+
// provider's own Claude Code docs configure `ANTHROPIC_BASE_URL` +
|
|
540
|
+
// `ANTHROPIC_AUTH_TOKEN`. No quota endpoint exists — entitlement is the
|
|
541
|
+
// `/v1/models` list itself (a key only sees the models it may call).
|
|
542
|
+
id: "zro",
|
|
543
|
+
displayName: "Zro",
|
|
544
|
+
baseUrl: "https://zro.moonmath.ai",
|
|
545
|
+
credentialUrl: "https://zro.moonmath.ai/docs/key-controls",
|
|
546
|
+
credentialHint: "Create or copy an API key (sk-...) from the Zro key controls page (docs/key-controls).",
|
|
547
|
+
},
|
|
493
548
|
] as const;
|
|
494
549
|
|
|
495
550
|
/**
|
|
@@ -617,6 +672,11 @@ const ADAPTER_USER_AGENT_PROVIDER_IDS: ReadonlySet<string> = new Set(
|
|
|
617
672
|
BUNDLED_PROVIDER_METADATA.filter((provider) => provider.hasAdapterUserAgent).map((provider) => provider.id),
|
|
618
673
|
);
|
|
619
674
|
|
|
675
|
+
/** Bundled metadata keyed by provider id for O(1) lookups. */
|
|
676
|
+
const BUNDLED_PROVIDER_BY_ID: ReadonlyMap<BundledProviderId, BundledProviderMetadata> = new Map(
|
|
677
|
+
BUNDLED_PROVIDER_METADATA.map((definition) => [definition.id, definition]),
|
|
678
|
+
);
|
|
679
|
+
|
|
620
680
|
/** Whether the bundled adapter provides its own upstream User-Agent. */
|
|
621
681
|
export function providerHasAdapterUserAgent(providerId: string): boolean {
|
|
622
682
|
return ADAPTER_USER_AGENT_PROVIDER_IDS.has(providerId);
|
|
@@ -628,7 +688,7 @@ export function providerHasAdapterUserAgent(providerId: string): boolean {
|
|
|
628
688
|
* minted inside a device/browser flow with no dedicated key page).
|
|
629
689
|
*/
|
|
630
690
|
export function providerCredentialUrl(providerId: string): string | undefined {
|
|
631
|
-
return
|
|
691
|
+
return BUNDLED_PROVIDER_BY_ID.get(providerId as BundledProviderId)?.credentialUrl;
|
|
632
692
|
}
|
|
633
693
|
|
|
634
694
|
/**
|
|
@@ -636,18 +696,18 @@ export function providerCredentialUrl(providerId: string): string | undefined {
|
|
|
636
696
|
* `undefined` where the link alone is self-explanatory.
|
|
637
697
|
*/
|
|
638
698
|
export function providerCredentialHint(providerId: string): string | undefined {
|
|
639
|
-
return
|
|
699
|
+
return BUNDLED_PROVIDER_BY_ID.get(providerId as BundledProviderId)?.credentialHint;
|
|
640
700
|
}
|
|
641
701
|
|
|
642
702
|
/** Presentation-only limitation for the provider detail page. */
|
|
643
703
|
export function providerOperatorNotice(providerId: string): string | undefined {
|
|
644
|
-
return
|
|
704
|
+
return BUNDLED_PROVIDER_BY_ID.get(providerId as BundledProviderId)?.operatorNotice;
|
|
645
705
|
}
|
|
646
706
|
|
|
647
707
|
/** Canonical builtin display name; falls back to the provider id for unknowns. */
|
|
648
708
|
export function providerDisplayName(providerId: string): string {
|
|
649
709
|
return (
|
|
650
|
-
|
|
710
|
+
BUNDLED_PROVIDER_BY_ID.get(providerId as BundledProviderId)?.displayName ??
|
|
651
711
|
providerId
|
|
652
712
|
);
|
|
653
713
|
}
|
|
@@ -679,7 +739,7 @@ export function providerDisplayName(providerId: string): string {
|
|
|
679
739
|
* gateway guessing it will not.
|
|
680
740
|
*/
|
|
681
741
|
export function providerServiceKinds(providerId: string): readonly ServiceKind[] {
|
|
682
|
-
const declared =
|
|
742
|
+
const declared = BUNDLED_PROVIDER_BY_ID.get(providerId as BundledProviderId)
|
|
683
743
|
?.serviceKinds;
|
|
684
744
|
// Bundled providers are explicit: they say which surfaces they serve, and a
|
|
685
745
|
// bundled provider without the declaration serves LLM only.
|
|
@@ -708,7 +768,7 @@ export function customProviderServiceKinds(
|
|
|
708
768
|
|
|
709
769
|
/** Canonical builtin base URL used by adapters, discovery, and quota clients. */
|
|
710
770
|
export function providerBaseUrl(providerId: string): string {
|
|
711
|
-
const entry =
|
|
771
|
+
const entry = BUNDLED_PROVIDER_BY_ID.get(providerId as BundledProviderId);
|
|
712
772
|
if (!entry) throw new Error(`Unknown builtin provider "${providerId}"`);
|
|
713
773
|
return entry.baseUrl.replace(/\/+$/, "");
|
|
714
774
|
}
|
|
@@ -744,7 +804,7 @@ export const PROVIDER_COMPATIBILITY_PROFILES: Record<string, CompatibilityProfil
|
|
|
744
804
|
|
|
745
805
|
/** Default wire family for a builtin provider. */
|
|
746
806
|
export function providerDefaultWireFamily(providerId: string): WireFamily {
|
|
747
|
-
return
|
|
807
|
+
return BUNDLED_PROVIDER_BY_ID.get(providerId as BundledProviderId)?.wireFamilyDefault ?? "chat";
|
|
748
808
|
}
|
|
749
809
|
|
|
750
810
|
/**
|
|
@@ -754,12 +814,12 @@ export function providerDefaultWireFamily(providerId: string): WireFamily {
|
|
|
754
814
|
* OpenAI-compatible adapter.
|
|
755
815
|
*/
|
|
756
816
|
export function providerUsesBespokeWire(providerId: string): boolean {
|
|
757
|
-
return
|
|
817
|
+
return BUNDLED_PROVIDER_BY_ID.get(providerId as BundledProviderId)?.bespokeWire ?? false;
|
|
758
818
|
}
|
|
759
819
|
|
|
760
820
|
/** Whether a builtin provider requires a persisted account row. */
|
|
761
821
|
export function providerRequiresAccount(providerId: string): boolean {
|
|
762
|
-
return
|
|
822
|
+
return BUNDLED_PROVIDER_BY_ID.get(providerId as BundledProviderId)?.requiresAccount ?? true;
|
|
763
823
|
}
|
|
764
824
|
|
|
765
825
|
/**
|
|
@@ -769,7 +829,7 @@ export function providerRequiresAccount(providerId: string): boolean {
|
|
|
769
829
|
*/
|
|
770
830
|
export function providerJwtVerification(providerId: string): ProviderJwtVerification {
|
|
771
831
|
return (
|
|
772
|
-
|
|
832
|
+
BUNDLED_PROVIDER_BY_ID.get(providerId as BundledProviderId)?.jwtVerification ?? {}
|
|
773
833
|
);
|
|
774
834
|
}
|
|
775
835
|
|
|
@@ -16,7 +16,21 @@ import { resolveDialect } from "../protocol/translation/dialect/resolver";
|
|
|
16
16
|
import type { CanonicalRequest } from "../protocol/canonical-model";
|
|
17
17
|
import type { ProviderDispatchContext } from "./provider-contracts";
|
|
18
18
|
import { providerBaseUrl, providerRateLimitIsIpScoped } from "./provider-metadata";
|
|
19
|
-
import type { ProviderDispatchTarget, ProviderId } from "./provider-contracts";
|
|
19
|
+
import type { ProviderDispatchTarget, ProviderId, ValidatedOutboundFetch } from "./provider-contracts";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Probe fetch wrapper: merges `Request`-carried headers with per-call init
|
|
23
|
+
* headers before delegating to the validated fetch. Required wherever the
|
|
24
|
+
* fetch is handed to adapters as `outbound_fetch`, because adapters may call
|
|
25
|
+
* it with a `Request` whose headers would otherwise be dropped.
|
|
26
|
+
*/
|
|
27
|
+
export function createProbeFetch(fetcher: ValidatedOutboundFetch): ValidatedOutboundFetch {
|
|
28
|
+
return (input, init) => {
|
|
29
|
+
const headers = new Headers(input instanceof Request ? input.headers : undefined);
|
|
30
|
+
new Headers(init?.headers).forEach((value, name) => headers.set(name, value));
|
|
31
|
+
return fetcher(input, { ...init, headers });
|
|
32
|
+
};
|
|
33
|
+
}
|
|
20
34
|
|
|
21
35
|
|
|
22
36
|
/**
|
|
@@ -58,6 +58,36 @@ export interface ProviderAuthentication {
|
|
|
58
58
|
readonly refresher?: OAuthTokenRefresher;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/** Provider implementation fields composed with canonical metadata. */
|
|
62
|
+
export type ProviderModuleCapabilities = Partial<Omit<ProviderModule, "loadAdapter">> &
|
|
63
|
+
Pick<ProviderModule, "loadAdapter">;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates a lazy authentication loader from a dynamic import and client export.
|
|
67
|
+
* Most OAuth clients also refresh their own tokens.
|
|
68
|
+
*/
|
|
69
|
+
export function oauthCapability<M, K extends keyof M>(
|
|
70
|
+
loader: () => Promise<M>,
|
|
71
|
+
exportName: K,
|
|
72
|
+
options: { readonly withRefresher?: boolean } = {},
|
|
73
|
+
): () => Promise<ProviderAuthentication> {
|
|
74
|
+
return async () => {
|
|
75
|
+
const module = await loader();
|
|
76
|
+
const client = (module as Record<string, unknown>)[exportName as string] as OAuthLoginClient & { refresh: unknown };
|
|
77
|
+
return options.withRefresher
|
|
78
|
+
? ({ client, refresher: client } as ProviderAuthentication)
|
|
79
|
+
: ({ client } as ProviderAuthentication);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Creates a lazy quota collector loader from a dynamic import and export name. */
|
|
84
|
+
export function quotaCapability<M, K extends keyof M>(
|
|
85
|
+
loader: () => Promise<M>,
|
|
86
|
+
exportName: K,
|
|
87
|
+
): () => Promise<QuotaFetcher> {
|
|
88
|
+
return () => loader().then((module) => (module as Record<string, unknown>)[exportName as string] as QuotaFetcher);
|
|
89
|
+
}
|
|
90
|
+
|
|
61
91
|
/**
|
|
62
92
|
* Derives a registry registration from a fully populated provider module.
|
|
63
93
|
* Keeps the registration shape in one place so the default registry and any
|
|
@@ -95,12 +125,6 @@ function assertAdapterIdentity(expected: ProviderId, adapter: ProviderAdapter):
|
|
|
95
125
|
});
|
|
96
126
|
}
|
|
97
127
|
|
|
98
|
-
/** Result of loading the configured provider registrations. */
|
|
99
|
-
export interface ProviderRegistrySnapshot {
|
|
100
|
-
readonly revision: number;
|
|
101
|
-
readonly adapters: ReadonlyMap<ProviderId, ProviderAdapter>;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
128
|
/**
|
|
105
129
|
* Single provider-registration authority. Every registration loads on
|
|
106
130
|
* demand through its own loader; registrations that are not added are
|
|
@@ -116,7 +140,6 @@ export class ProviderRegistry {
|
|
|
116
140
|
#quotaLoading = new Map<ProviderId, Promise<QuotaFetcher>>();
|
|
117
141
|
#modelDiscovery = new Map<ProviderId, ProviderModelDiscovery>();
|
|
118
142
|
#modelDiscoveryLoading = new Map<ProviderId, Promise<ProviderModelDiscovery>>();
|
|
119
|
-
#revision = 0;
|
|
120
143
|
|
|
121
144
|
/** Adds one registration and rejects duplicate canonical IDs. */
|
|
122
145
|
register(registration: ProviderRegistration): void {
|
|
@@ -132,7 +155,6 @@ export class ProviderRegistry {
|
|
|
132
155
|
});
|
|
133
156
|
}
|
|
134
157
|
this.#registrations.set(providerId, registration);
|
|
135
|
-
this.#revision += 1;
|
|
136
158
|
}
|
|
137
159
|
/** Removes one registration and any cached adapter/auth/quota/discovery state. */
|
|
138
160
|
unregister(providerId: string): boolean {
|
|
@@ -142,7 +164,6 @@ export class ProviderRegistry {
|
|
|
142
164
|
this.#authentication.delete(normalized);
|
|
143
165
|
this.#quotaCollectors.delete(normalized);
|
|
144
166
|
this.#modelDiscovery.delete(normalized);
|
|
145
|
-
if (removed) this.#revision += 1;
|
|
146
167
|
return removed;
|
|
147
168
|
}
|
|
148
169
|
|
|
@@ -170,28 +191,25 @@ export class ProviderRegistry {
|
|
|
170
191
|
this.#authentication.delete(providerId);
|
|
171
192
|
this.#quotaCollectors.delete(providerId);
|
|
172
193
|
this.#modelDiscovery.delete(providerId);
|
|
173
|
-
this.#revision += 1;
|
|
174
194
|
return replaced;
|
|
175
195
|
}
|
|
176
196
|
|
|
177
|
-
/** Loads one registered provider, or `undefined` when it is not registered. */
|
|
178
|
-
async loadOne(providerId: string): Promise<ProviderAdapter | undefined> {
|
|
179
|
-
const normalized = parseProviderId(providerId);
|
|
180
|
-
const registration = this.#registrations.get(normalized);
|
|
181
|
-
if (registration === undefined) return undefined;
|
|
182
|
-
return this.#loadRegistration(normalized, registration);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
197
|
/**
|
|
186
198
|
* Resolves one adapter on demand, returning the cached instance when it has
|
|
187
|
-
* already loaded
|
|
188
|
-
*
|
|
189
|
-
*
|
|
199
|
+
* already loaded — unless `force` is set, which rebuilds the adapter from
|
|
200
|
+
* the registration (single-flight, and the fresh instance replaces the
|
|
201
|
+
* cache). This never evaluates a provider's module graph until that provider
|
|
202
|
+
* is actually dispatched to, so heavy bespoke adapters (protobuf-based
|
|
203
|
+
* providers) stay out of the startup path. `force` is used by the probe
|
|
204
|
+
* path, which wants the adapter rebuilt against the latest registration
|
|
205
|
+
* rather than a cached instance.
|
|
190
206
|
*/
|
|
191
|
-
async resolve(providerId: string): Promise<ProviderAdapter | undefined> {
|
|
207
|
+
async resolve(providerId: string, opts: { readonly force?: boolean } = {}): Promise<ProviderAdapter | undefined> {
|
|
192
208
|
const normalized = parseProviderId(providerId);
|
|
193
|
-
|
|
194
|
-
|
|
209
|
+
if (!opts.force) {
|
|
210
|
+
const cached = this.#loaded.get(normalized);
|
|
211
|
+
if (cached !== undefined) return cached;
|
|
212
|
+
}
|
|
195
213
|
const registration = this.#registrations.get(normalized);
|
|
196
214
|
if (registration === undefined) return undefined;
|
|
197
215
|
return this.#loadRegistration(normalized, registration);
|
|
@@ -306,7 +324,6 @@ export class ProviderRegistry {
|
|
|
306
324
|
metrics.proxy_provider_adapter_load_ms.observe(durationMs);
|
|
307
325
|
trackAdapterLoad(providerId, durationMs);
|
|
308
326
|
this.#loaded.set(providerId, adapter);
|
|
309
|
-
this.#revision += 1;
|
|
310
327
|
return adapter;
|
|
311
328
|
})().finally(() => {
|
|
312
329
|
this.#loading.delete(providerId);
|
|
@@ -315,14 +332,6 @@ export class ProviderRegistry {
|
|
|
315
332
|
return task;
|
|
316
333
|
}
|
|
317
334
|
|
|
318
|
-
/** Returns the immutable registry snapshot; disabled optional providers are absent. */
|
|
319
|
-
snapshot(): ProviderRegistrySnapshot {
|
|
320
|
-
return {
|
|
321
|
-
revision: this.#revision,
|
|
322
|
-
adapters: new Map(this.#loaded),
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
|
|
326
335
|
/** Returns registration metadata without loading optional code. */
|
|
327
336
|
registrations(): readonly ProviderRegistration[] {
|
|
328
337
|
return [...this.#registrations.values()];
|
|
@@ -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
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { ProviderQuotaWindow } from "./quota-contracts";
|
|
12
12
|
import { isoDate, number, percentWindow, record, text } from "./quota-contracts";
|
|
13
|
+
import { resolveWindowEmission } from "./quota-window-parser";
|
|
13
14
|
|
|
14
15
|
/** Where one provider's limit entries and their fields live. */
|
|
15
16
|
export interface LimitWindowShape {
|
|
@@ -77,14 +78,24 @@ export function parseLimitWindows(
|
|
|
77
78
|
const kind = text(readFirst(limit, shape.kindPaths)) ?? `window-${index + 1}`;
|
|
78
79
|
const percent = number(readFirst(limit, shape.percentPaths));
|
|
79
80
|
const reset = isoDate(readFirst(limit, shape.resetPaths));
|
|
80
|
-
|
|
81
|
+
const rawUsed = shape.usedPaths ? number(readFirst(limit, shape.usedPaths)) : null;
|
|
81
82
|
const limitValue = shape.limitPaths
|
|
82
83
|
? number(readFirst(limit, shape.limitPaths))
|
|
83
84
|
: null;
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
null
|
|
85
|
+
const { emit } = resolveWindowEmission({
|
|
86
|
+
explicitPercent: null,
|
|
87
|
+
usedPercent: percent,
|
|
88
|
+
remainingPercent: null,
|
|
89
|
+
used: rawUsed,
|
|
90
|
+
limit: limitValue,
|
|
91
|
+
reset,
|
|
92
|
+
deriveFromUsedLimit: false,
|
|
93
|
+
// The array walker has always emitted a reset-only window (percent null,
|
|
94
|
+
// reset present); only an entry with neither is dropped.
|
|
95
|
+
emitWithoutPercent: true,
|
|
96
|
+
});
|
|
97
|
+
if (!emit) continue;
|
|
98
|
+
const usedValue = rawUsed ?? shape.deriveUsed?.(limit, percent, limitValue) ?? null;
|
|
88
99
|
const label =
|
|
89
100
|
shape.labelFor?.(kind, limit) ?? shape.fallbackLabel?.(kind) ?? humanize(kind);
|
|
90
101
|
windows.push(percentWindow(kind, label, percent, reset, usedValue, limitValue, "independent"));
|