cartethyia 2.4.6 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -13
- package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
- package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-Bcy150Jo.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DyNXefph.js → CliToolsPage-D3xqZbN-.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +1 -0
- package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CATpAEOz.js → ConsoleLogPage-u6W1UXSc.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-D6jEU7nL.js → CustomizationPage-CTTpCb25.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-CZkpn0a7.js → HealthEventsModal-C5XpWlvy.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BJlMVN4U.js → LoginPage-DHoB3Agn.js} +1 -1
- package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-Br3CXEau.js → ModelPicker-oQ-SDXp8.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +7 -0
- package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +1 -0
- package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-BHW4Zt06.js → SettingsPage-CvjZxHE4.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-VOYC_lIa.js → SetupPage-D3nza7LH.js} +1 -1
- package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-Dtt12WT1.js +1 -0
- package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-BNIDLiU6.js → StudioPage-CbK3hXvU.js} +4 -4
- package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +56 -0
- package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BETfaMYZ.js → api-keys-BmDbsior.js} +1 -1
- package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
- package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
- package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
- package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
- package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
- package/dist/dashboard/assets/{badge-BTyGk_DL.js → badge-DpXT4Ixt.js} +1 -1
- package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
- package/dist/dashboard/assets/{banner-bujTWYM4.js → banner-CSAAWLJv.js} +1 -1
- package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
- package/dist/dashboard/assets/{card-B0Bnx5CA.js → card-DEhRXXw4.js} +1 -1
- package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-DE2ug812.js → cli-tools-B0wBq-RL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-R_rArH8S.js → clipboard-button-DOo6EU8O.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BWIP_EvW.js → dialog-BvQeBMfP.js} +1 -1
- package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BKcgVugF.js → drawer-CMwPXwQB.js} +1 -1
- package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CcWkGR4a.js → entry-D4O2229X.js} +1 -1
- package/dist/dashboard/assets/{entry-CcWkGR4a.js.gz → entry-D4O2229X.js.gz} +0 -0
- package/dist/dashboard/assets/{entry-kTiTFrvh.js → entry-DKvr5Q9K.js} +1 -1
- package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
- package/dist/dashboard/assets/entry-FQCejoDY.js +4 -0
- package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-Booc5mNe.js → github-badge-zliLvBwr.js} +1 -1
- package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
- package/dist/dashboard/assets/{index-D70pChfn.js → index-CXm3FIyt.js} +2 -2
- package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
- package/dist/dashboard/assets/{inline-D1TCqM2D.js → inline-D1IlZZ8H.js} +1 -1
- package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
- package/dist/dashboard/assets/{input-B9oyU93y.js → input-BpVZdrbc.js} +1 -1
- package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BArzXirQ.js → layout-BioLW7gN.js} +1 -1
- package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BVZzaoHF.js → list-row-CwVcGAIc.js} +1 -1
- package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
- package/dist/dashboard/assets/{live-EmuIayqp.js → live-yfEJFF9T.js} +1 -1
- package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CSPQ9xB0.js → model-bans-CT-TxSNX.js} +1 -1
- package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
- package/dist/dashboard/assets/providers/cursor.webp +0 -0
- package/dist/dashboard/assets/providers/trae.webp +0 -0
- package/dist/dashboard/assets/{routing-BExgxxBU.js → routing-Cs6jl49L.js} +1 -1
- package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
- package/dist/dashboard/assets/select-C0Rer3gF.js +41 -0
- package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
- package/dist/dashboard/assets/{stack-Ca5HxI5i.js → stack-C9_uUjaA.js} +1 -1
- package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
- package/dist/dashboard/assets/{state-CzUPfLXo.js → state-DA0B95UQ.js} +1 -1
- package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DOUIky_i.js → switch-Cittxy-x.js} +1 -1
- package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
- package/dist/dashboard/assets/{toast-C76WrMhc.js → toast-CNt_jzO4.js} +1 -1
- package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-D87FZwH0.js → useMutation-BQKMcJ1H.js} +1 -1
- package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +51 -52
- package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
- package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
- package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
- package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
- package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
- package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
- package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
- package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
- package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
- package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
- package/package.json +3 -2
- package/scripts/cli/command/console-menu.ts +30 -11
- package/scripts/cli/command/start.ts +77 -13
- package/scripts/cli/command/update.ts +1 -1
- package/scripts/cli/gateway/instance-lock.ts +105 -0
- package/scripts/cli/gateway/spawn.ts +173 -101
- package/scripts/cli/ui/background.ts +110 -49
- package/src/boot/app.ts +20 -6
- package/src/boot/dependencies.ts +19 -0
- package/src/boot/elysia-typebox-bridge.ts +37 -0
- package/src/boot/lifecycle.ts +2 -0
- package/src/config/cooldowns.ts +138 -138
- package/src/config/providers.ts +5 -5
- package/src/config/runtime.ts +19 -3
- package/src/config/spec.ts +9 -0
- package/src/console/api-keys/contracts.ts +414 -391
- package/src/console/api-keys/routes.ts +880 -870
- package/src/console/auth/account-reset-store.ts +14 -0
- package/src/console/auth/account-reset.ts +3 -2
- package/src/console/auth/setup-service.ts +6 -3
- package/src/console/domain-registration.ts +9 -2
- package/src/console/observability/contracts.ts +475 -455
- package/src/console/observability/store.ts +1015 -952
- package/src/console/providers/catalog/contracts.ts +31 -0
- package/src/console/providers/catalog/provider-operations.ts +8 -0
- package/src/console/providers/catalog/store.ts +1532 -1525
- package/src/console/providers/oauth/callback-listener.ts +18 -3
- package/src/console/providers/oauth/routes.ts +38 -15
- package/src/console/quota/refresh.ts +19 -0
- package/src/console/updates/routes.ts +162 -4
- package/src/main.ts +3 -0
- package/src/network/http2-bidi.ts +85 -0
- package/src/network/outbound-fetch.ts +1 -26
- package/src/network/pool/agent.ts +478 -460
- package/src/network/pool/resolver.ts +1104 -544
- package/src/network/pool-health-machine.ts +12 -5
- package/src/observability/payload-capture.ts +344 -324
- package/src/persistence/db-pg.ts +9 -3
- package/src/persistence/db-sqlite.ts +497 -495
- package/src/persistence/health-event-flush.ts +137 -0
- package/src/persistence/migrate.ts +181 -129
- package/src/persistence/schema.ts +1360 -1295
- package/src/persistence/settings-revision.ts +25 -0
- package/src/persistence/shared-cache.ts +217 -0
- package/src/persistence/stores/share-store.ts +556 -528
- package/src/persistence/usage-flush.ts +61 -1
- package/src/protocol/canonical-model.ts +23 -1
- package/src/protocol/response/chat.ts +143 -10
- package/src/protocol/response/omp-tool-call-markup.ts +221 -0
- package/src/providers/default-registry.ts +173 -1
- package/src/providers/discovery/openai-model-discovery.ts +39 -3
- package/src/providers/discovery/openrouter-free-roster.ts +98 -0
- package/src/providers/discovery/probing-service.ts +1022 -1001
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +39 -0
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +18 -5
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
- package/src/providers/integrations/buddy/codebuddy/shared.ts +15 -6
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
- package/src/providers/integrations/buddy/workbuddy/shared.ts +3 -3
- package/src/providers/integrations/claude/claude.ts +606 -582
- package/src/providers/integrations/cline/cline-quota.ts +18 -16
- package/src/providers/integrations/cline/cline.ts +539 -468
- package/src/providers/integrations/commandcode/commandcode.ts +675 -541
- package/src/providers/integrations/commandcode/discovery.ts +116 -0
- package/src/providers/integrations/commandcode/quota.ts +160 -0
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
- package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
- package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
- package/src/providers/integrations/cursor/cursor-request.ts +471 -0
- package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
- package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
- package/src/providers/integrations/cursor/cursor.ts +960 -0
- package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
- package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
- package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
- package/src/providers/integrations/github/github.ts +4 -4
- package/src/providers/integrations/hermes.ts +79 -8
- package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
- package/src/providers/integrations/kilo/kilo.ts +153 -146
- package/src/providers/integrations/opencode/provider.ts +532 -538
- package/src/providers/integrations/openrouter/provider.ts +139 -281
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +310 -257
- package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
- package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
- package/src/providers/integrations/qoder/qoder.ts +865 -730
- package/src/providers/integrations/trae/trae-catalog.ts +190 -0
- package/src/providers/integrations/trae/trae-headers.ts +149 -0
- package/src/providers/integrations/trae/trae-oauth.ts +773 -0
- package/src/providers/integrations/trae/trae-protocol.ts +400 -0
- package/src/providers/integrations/trae/trae-realm.ts +220 -0
- package/src/providers/integrations/trae/trae.ts +458 -0
- package/src/providers/operations/account-health-service.ts +107 -10
- package/src/providers/operations/client-versions.ts +133 -6
- package/src/providers/operations/provider-catalog-seeder.ts +95 -86
- package/src/providers/operations/provider-credential-service.ts +46 -7
- package/src/providers/provider-contracts.ts +76 -0
- package/src/providers/provider-metadata.ts +78 -6
- package/src/providers/provider-model-definition.ts +373 -349
- package/src/security/api-key-auth.ts +126 -8
- package/src/transport/contracts.ts +3 -0
- package/src/transport/dispatch/attempt-finalize.ts +32 -1
- package/src/transport/dispatch/attempt-loop.ts +530 -515
- package/src/transport/dispatch/generation.ts +88 -21
- package/src/transport/dispatch/leases.ts +99 -2
- package/src/transport/dispatch/proxy-request.ts +60 -3
- package/src/transport/dispatch/streaming/finalize.ts +16 -1
- package/src/transport/dispatch/streaming/stream.ts +22 -2
- package/src/transport/dispatch/upstream.ts +19 -1
- package/src/transport/guards/auth-guard.ts +26 -1
- package/src/transport/guards/body-policy.ts +5 -4
- package/src/transport/request/preparer.ts +103 -0
- package/src/transport/request/state.ts +14 -0
- package/src/transport/routing/plan-gate.ts +45 -0
- package/src/transport/routing/route-catalog.ts +799 -777
- package/src/transport/routing/route-model.ts +52 -1
- package/src/transport/routing/router.ts +51 -0
- package/dist/dashboard/assets/BannedPage-QhvbJB9o.js +0 -1
- package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +0 -1
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js +0 -7
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +0 -1
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js +0 -1
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BAFaSDQV.js +0 -56
- package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
- package/dist/dashboard/assets/app-BLCvGdzb.css +0 -2
- package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
- package/dist/dashboard/assets/assert-7J6_LVD3.js +0 -1
- package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
- package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
- package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
- package/dist/dashboard/assets/card-B0Bnx5CA.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-R_rArH8S.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BWIP_EvW.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
- package/dist/dashboard/assets/entry-7dpgvpJ3.js +0 -4
- package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
- package/dist/dashboard/assets/entry-kTiTFrvh.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
- package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
- package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
- package/dist/dashboard/assets/input-B9oyU93y.js.gz +0 -0
- package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
- package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
- package/dist/dashboard/assets/live-EmuIayqp.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CSPQ9xB0.js.gz +0 -0
- package/dist/dashboard/assets/routing-BExgxxBU.js.gz +0 -0
- package/dist/dashboard/assets/select-C9mMoEpJ.js +0 -41
- package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
- package/dist/dashboard/assets/settings-C4_Gn_wq.js +0 -1
- package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
- package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
- package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOUIky_i.js.gz +0 -0
- package/dist/dashboard/assets/toast-C76WrMhc.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-D87FZwH0.js.gz +0 -0
- package/src/network/tls/bun-client-hello.ts +0 -112
- /package/dist/dashboard/assets/{app-Cjq7nrwu.js → app-C-Y4oZT0.js} +0 -0
- /package/dist/dashboard/assets/{app-Cjq7nrwu.js.gz → app-C-Y4oZT0.js.gz} +0 -0
|
@@ -25,11 +25,65 @@ async function qoderVersion(response: Response): Promise<string | null> {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Newest Trae IDE version from the official changelog.
|
|
30
|
+
*
|
|
31
|
+
* Trae publishes no npm package and no release API, so the changelog is the
|
|
32
|
+
* only machine-readable source of the current client version. It matters
|
|
33
|
+
* upstream: intl `llm_raw_chat` answers `4015` unless the request carries the
|
|
34
|
+
* *current* client version, so a stale pin is an outage, not a cosmetic drift.
|
|
35
|
+
*
|
|
36
|
+
* The page embeds its content as JSON, and its newest entry is the first dated
|
|
37
|
+
* heading — not the highest semver, because this vendor's hotfix line numbers
|
|
38
|
+
* past its feature line (3.5.104 shipped after 3.5.97). Taking `max(semver)`
|
|
39
|
+
* would report an old build as current, so this walks the document in order and
|
|
40
|
+
* returns the first version following a dated release heading.
|
|
41
|
+
*/
|
|
42
|
+
async function traeVersion(response: Response): Promise<string | null> {
|
|
43
|
+
try {
|
|
44
|
+
const text = await response.text();
|
|
45
|
+
const heading =
|
|
46
|
+
/"insert":"((?:January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{1,2},\s*\d{4}\s*\([^)]*\))\\n"/g;
|
|
47
|
+
for (const match of text.matchAll(heading)) {
|
|
48
|
+
const window = text.slice(match.index ?? 0, (match.index ?? 0) + 900);
|
|
49
|
+
const found = window.match(/TraeCode\s+v?(\d+\.\d+\.\d+)|v?(\d+\.\d+\.\d+)\s+is\s+released/i);
|
|
50
|
+
const version = found?.[1] ?? found?.[2];
|
|
51
|
+
if (version && isSemverish(version)) return version.trim();
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
28
59
|
async function clineClientVersion(response: Response): Promise<string | null> {
|
|
29
60
|
const data = (await response.json()) as { version?: unknown };
|
|
30
61
|
return isSemverish(data.version) ? data.version.trim() : null;
|
|
31
62
|
}
|
|
32
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Newest Cursor build from the official download API, as the IDE's own header
|
|
66
|
+
* spells it.
|
|
67
|
+
*
|
|
68
|
+
* Cursor's `x-cursor-client-version` is not the IDE semver on its own: the wire
|
|
69
|
+
* value is `cli-<version>-<sha7>` (observed live as `cli-2026.10.19-6bbf2c4`).
|
|
70
|
+
* The download API is the only source that returns both halves — `version` and
|
|
71
|
+
* `commitSha` — so they are joined here rather than guessed. A semver alone
|
|
72
|
+
* would be a different identity than the one the IDE sends.
|
|
73
|
+
*/
|
|
74
|
+
async function cursorVersion(response: Response): Promise<string | null> {
|
|
75
|
+
try {
|
|
76
|
+
const data = (await response.json()) as { version?: unknown; commitSha?: unknown };
|
|
77
|
+
const version = data.version;
|
|
78
|
+
if (!isSemverish(version)) return null;
|
|
79
|
+
const sha = typeof data.commitSha === "string" ? data.commitSha.trim() : "";
|
|
80
|
+
const short = sha.length >= 7 ? sha.slice(0, 7) : "";
|
|
81
|
+
return short.length > 0 ? `cli-${version.trim()}-${short}` : version.trim();
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
33
87
|
async function clineSdkVersion(response: Response): Promise<string | null> {
|
|
34
88
|
const data = (await response.json()) as {
|
|
35
89
|
version?: unknown;
|
|
@@ -177,7 +231,7 @@ export const VERSION_SOURCES = {
|
|
|
177
231
|
},
|
|
178
232
|
qoder: {
|
|
179
233
|
key: "qoder",
|
|
180
|
-
fallback: "1.1.
|
|
234
|
+
fallback: "1.1.74",
|
|
181
235
|
sources: [
|
|
182
236
|
{
|
|
183
237
|
url: "https://registry.npmjs.org/@qoder-ai/qodercli/latest",
|
|
@@ -222,6 +276,11 @@ export const VERSION_SOURCES = {
|
|
|
222
276
|
fallback: "0.0.95",
|
|
223
277
|
sources: [{ url: "https://registry.npmjs.org/@cline/sdk/latest", extract: clineSdkVersion }],
|
|
224
278
|
},
|
|
279
|
+
clineCli: {
|
|
280
|
+
key: "cline-cli",
|
|
281
|
+
fallback: "3.0.73",
|
|
282
|
+
sources: [{ url: "https://registry.npmjs.org/cline/latest", extract: clineSdkVersion }],
|
|
283
|
+
},
|
|
225
284
|
codex: {
|
|
226
285
|
key: "codex",
|
|
227
286
|
fallback: "0.163.0",
|
|
@@ -257,7 +316,7 @@ export const VERSION_SOURCES = {
|
|
|
257
316
|
},
|
|
258
317
|
workbuddyCli: {
|
|
259
318
|
key: "workbuddy",
|
|
260
|
-
fallback: "2.
|
|
319
|
+
fallback: "2.169.0",
|
|
261
320
|
sources: [
|
|
262
321
|
{ url: "https://registry.npmjs.org/@tencent-ai/codebuddy-code/latest" },
|
|
263
322
|
{ url: "https://registry.npmmirror.com/@tencent-ai/codebuddy-code/latest" },
|
|
@@ -284,7 +343,7 @@ export const VERSION_SOURCES = {
|
|
|
284
343
|
},
|
|
285
344
|
codebuddy: {
|
|
286
345
|
key: "codebuddy",
|
|
287
|
-
fallback: "2.
|
|
346
|
+
fallback: "2.169.0",
|
|
288
347
|
sources: [
|
|
289
348
|
{ url: "https://registry.npmjs.org/@tencent-ai/codebuddy-code/latest" },
|
|
290
349
|
{ url: "https://registry.npmmirror.com/@tencent-ai/codebuddy-code/latest" },
|
|
@@ -342,6 +401,46 @@ export const VERSION_SOURCES = {
|
|
|
342
401
|
fallback: "1.4.10-R6427.1",
|
|
343
402
|
sources: [{ url: "https://api.meta.ai/muse-code/channels/muse-stable" }],
|
|
344
403
|
},
|
|
404
|
+
trae: {
|
|
405
|
+
key: "trae",
|
|
406
|
+
// Fallback = the donor's last observed client version. Discovery replaces
|
|
407
|
+
// it within one monitor tick; it is only what a cold process sends before
|
|
408
|
+
// the first successful fetch.
|
|
409
|
+
fallback: "3.6.2",
|
|
410
|
+
sources: [
|
|
411
|
+
{
|
|
412
|
+
url: "https://docs.trae.ai/ide/changelog?_lang=en",
|
|
413
|
+
extract: traeVersion,
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
},
|
|
417
|
+
cursor: {
|
|
418
|
+
key: "cursor",
|
|
419
|
+
// Fallback = the last observed wire version. Discovery replaces it within
|
|
420
|
+
// one monitor tick; it is only what a cold process sends before the first
|
|
421
|
+
// successful fetch.
|
|
422
|
+
fallback: "cli-2026.10.19-6bbf2c4",
|
|
423
|
+
sources: [
|
|
424
|
+
{
|
|
425
|
+
url: "https://cursor.com/api/download?platform=win32-x64-user&releaseTrack=stable",
|
|
426
|
+
extract: cursorVersion,
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
},
|
|
430
|
+
hermes: {
|
|
431
|
+
key: "hermes",
|
|
432
|
+
// Hermes DOES publish a semver: the GitHub release tag is `v0.22.2`. The
|
|
433
|
+
// comment this replaced claimed no semver existed and pinned a date tag
|
|
434
|
+
// (`v2026.9.24`) as the version — a tag, not a release, and not the
|
|
435
|
+
// identity the client sends.
|
|
436
|
+
fallback: "0.22.2",
|
|
437
|
+
sources: [
|
|
438
|
+
{
|
|
439
|
+
url: "https://api.github.com/repos/NousResearch/hermes-agent/releases/latest",
|
|
440
|
+
extract: githubReleaseVersion,
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
},
|
|
345
444
|
zcode: {
|
|
346
445
|
key: "zcode",
|
|
347
446
|
fallback: "3.14.3",
|
|
@@ -357,11 +456,15 @@ export const VERSION_SOURCES = {
|
|
|
357
456
|
const resolvers = {
|
|
358
457
|
antigravity: createClientVersionResolver({ ...VERSION_SOURCES.antigravity, client: "antigravity" }),
|
|
359
458
|
qoder: createClientVersionResolver({ ...VERSION_SOURCES.qoder, client: "qoder" }),
|
|
459
|
+
hermes: createClientVersionResolver({ ...VERSION_SOURCES.hermes, client: "hermes" }),
|
|
460
|
+
trae: createClientVersionResolver({ ...VERSION_SOURCES.trae, client: "trae" }),
|
|
461
|
+
cursor: createClientVersionResolver({ ...VERSION_SOURCES.cursor, client: "cursor" }),
|
|
360
462
|
opencode: createClientVersionResolver({ ...VERSION_SOURCES.opencode, client: "opencode" }),
|
|
361
463
|
commandcode: createClientVersionResolver({ ...VERSION_SOURCES.commandcode, client: "command-code" }),
|
|
362
464
|
grok: createClientVersionResolver({ ...VERSION_SOURCES.grok, client: "grok" }),
|
|
363
465
|
clineClient: createClientVersionResolver({ ...VERSION_SOURCES.clineClient, client: "cline" }),
|
|
364
466
|
clineSdk: createClientVersionResolver({ ...VERSION_SOURCES.clineSdk, client: "cline-sdk" }),
|
|
467
|
+
clineCli: createClientVersionResolver({ ...VERSION_SOURCES.clineCli, client: "cline-cli" }),
|
|
365
468
|
codex: createClientVersionResolver({ ...VERSION_SOURCES.codex, client: "codex" }),
|
|
366
469
|
workbuddyClient: createClientVersionResolver({ ...VERSION_SOURCES.workbuddyClient, client: "workbuddy" }),
|
|
367
470
|
workbuddyCnClient: createClientVersionResolver({ ...VERSION_SOURCES.workbuddyCnClient, client: "workbuddy-cn" }),
|
|
@@ -384,9 +487,16 @@ const PROVIDER_VERSION_RESOLVERS: Readonly<Record<string, ClientVersionResolver>
|
|
|
384
487
|
opencode: resolvers.opencode,
|
|
385
488
|
"opencode-zen": resolvers.opencode,
|
|
386
489
|
"opencode-go": resolvers.opencode,
|
|
387
|
-
|
|
490
|
+
// The identity this provider is dispatched under is the CLI's, so the route
|
|
491
|
+
// identity reports the CLI version. `clineClient` (the VS Code extension
|
|
492
|
+
// manifest) is no longer the client this provider claims to be.
|
|
493
|
+
cline: resolvers.clineCli,
|
|
388
494
|
codex: resolvers.codex,
|
|
389
495
|
qoder: resolvers.qoder,
|
|
496
|
+
hermes: resolvers.hermes,
|
|
497
|
+
trae: resolvers.trae,
|
|
498
|
+
traecn: resolvers.trae,
|
|
499
|
+
cursor: resolvers.cursor,
|
|
390
500
|
commandcode: resolvers.commandcode,
|
|
391
501
|
workbuddy: resolvers.workbuddyClient,
|
|
392
502
|
workbuddycn: resolvers.workbuddyCnClient,
|
|
@@ -484,6 +594,8 @@ function accessor<K extends keyof typeof resolvers>(name: K): {
|
|
|
484
594
|
}
|
|
485
595
|
|
|
486
596
|
export const getQoderVersion = accessor("qoder").get;
|
|
597
|
+
export const getHermesVersion = accessor("hermes").get;
|
|
598
|
+
export const getTraeVersion = accessor("trae").get;
|
|
487
599
|
export const getAntigravityVersion = accessor("antigravity").get;
|
|
488
600
|
|
|
489
601
|
export const getOpenCodeVersion = accessor("opencode").get;
|
|
@@ -503,8 +615,10 @@ export function buildGrokAuthUserAgent(version = getGrokVersion()): string {
|
|
|
503
615
|
|
|
504
616
|
export const getClineClientVersion = accessor("clineClient").get;
|
|
505
617
|
export const getClineSdkVersion = accessor("clineSdk").get;
|
|
618
|
+
export const getClineCliVersion = accessor("clineCli").get;
|
|
506
619
|
export const _resetClineClientVersionCache = accessor("clineClient").reset;
|
|
507
620
|
export const _resetClineSdkVersionCache = accessor("clineSdk").reset;
|
|
621
|
+
export const _resetClineCliVersionCache = accessor("clineCli").reset;
|
|
508
622
|
|
|
509
623
|
export const getCodexVersion = accessor("codex").get;
|
|
510
624
|
|
|
@@ -513,9 +627,16 @@ export const getWorkBuddyCnClientVersion = accessor("workbuddyCnClient").get;
|
|
|
513
627
|
export const getWorkBuddyCliVersion = accessor("workbuddyCli").get;
|
|
514
628
|
|
|
515
629
|
export function buildWorkBuddyUserAgent(
|
|
630
|
+
identity = "WB",
|
|
516
631
|
clientVersion = getWorkBuddyClientVersion(),
|
|
517
632
|
cliVersion = getWorkBuddyCliVersion(),
|
|
518
633
|
): string {
|
|
634
|
+
// 1:1 with codebuddy2api. The CLI profile is `CLI/<v> CodeBuddy/<v>` and the
|
|
635
|
+
// WorkBuddy profile is `WorkBuddy/<wb> WorkBuddy AI/<wb> CLI/<cli>`, which
|
|
636
|
+
// sends NO SDK headers — two separate profiles, not one string with a switch.
|
|
637
|
+
// Whether SDK headers ride along is decided by `workbuddyHeaders`, not here, so
|
|
638
|
+
// this builder only decides the UA text.
|
|
639
|
+
if (identity === "CLI") return `CLI/${cliVersion} CodeBuddy/${cliVersion}`;
|
|
519
640
|
return `WorkBuddy/${clientVersion} WorkBuddy AI/${clientVersion} CLI/${cliVersion}`;
|
|
520
641
|
}
|
|
521
642
|
|
|
@@ -529,7 +650,7 @@ export const getCodeBuddyCnVersion = accessor("codebuddyCn").get;
|
|
|
529
650
|
* The CodeBuddy User-Agent.
|
|
530
651
|
*
|
|
531
652
|
* Both identities share the CLI *package* version, which is why `codebuddy`
|
|
532
|
-
* (the npm `@tencent-ai/codebuddy-code` pin, `2.
|
|
653
|
+
* (the npm `@tencent-ai/codebuddy-code` pin, `2.169.0` — the value both
|
|
533
654
|
* references use for CLI) is the version for each. `codebuddyCn` is the CN
|
|
534
655
|
* *desktop* build (`4.12.2.…`), and using it here produced
|
|
535
656
|
* `CLI/4.12.2.41207131 CodeBuddy/4.12.2.41207131` — a CLI claiming a desktop
|
|
@@ -538,8 +659,14 @@ export const getCodeBuddyCnVersion = accessor("codebuddyCn").get;
|
|
|
538
659
|
export function buildCodeBuddyUserAgent(
|
|
539
660
|
identity: "IDE" | "CLI",
|
|
540
661
|
version = getCodeBuddyVersion(),
|
|
662
|
+
cnVersion = getCodeBuddyCnVersion(),
|
|
541
663
|
): string {
|
|
542
|
-
|
|
664
|
+
// 1:1 with codebuddy2api's identity_headers(): the CLI profile uses ONE version
|
|
665
|
+
// (the npm CLI package pin) for both halves, and the IDE profile uses the other
|
|
666
|
+
// product's version. This previously always used `version`, so "CLI" produced
|
|
667
|
+
// `CLI/<ide> CodeBuddy/<ide>` — a CLI claiming the desktop build.
|
|
668
|
+
const resolved = identity === "CLI" ? cnVersion : version;
|
|
669
|
+
return `${identity}/${resolved} CodeBuddy/${resolved}`;
|
|
543
670
|
}
|
|
544
671
|
|
|
545
672
|
export const _resetCodeBuddyVersionCache = accessor("codebuddy").reset;
|
|
@@ -1,86 +1,95 @@
|
|
|
1
|
-
/** Durable materialization of bundled provider model metadata. */
|
|
2
|
-
import { and, eq, sql } from "drizzle-orm";
|
|
3
|
-
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
4
|
-
import { models } from "../../persistence/schema";
|
|
5
|
-
import type { ModelDefinition } from "../provider-model-definition";
|
|
6
|
-
|
|
7
|
-
/** Persists compiled provider model metadata and reconciles drifted capability flags. */
|
|
8
|
-
export async function seedBundledModels(
|
|
9
|
-
db: CartethyiaDatabase,
|
|
10
|
-
builtinModels: ReadonlyMap<string, readonly ModelDefinition[]>,
|
|
11
|
-
): Promise<void> {
|
|
12
|
-
for (const [providerId, definitions] of builtinModels) {
|
|
13
|
-
if (definitions.length === 0) continue;
|
|
14
|
-
// Drop builtin rows the current catalog no longer declares, including rows
|
|
15
|
-
// whose model id is still present but whose endpoint path drifted (e.g. a
|
|
16
|
-
// provider that moved from `/chat/completions` to `/v2/chat/completions`).
|
|
17
|
-
// The unique key is (provider, model, endpoint), so a changed endpoint
|
|
18
|
-
// would otherwise insert a second row and leave the stale route behind —
|
|
19
|
-
// the model list would then show duplicates and probes could hit the dead
|
|
20
|
-
// path. Composite `NOT IN` keeps exactly the (model, endpoint) pairs the
|
|
21
|
-
// catalog owns.
|
|
22
|
-
const pairs = definitions.map(
|
|
23
|
-
(definition) => sql`(${definition.modelId}, ${definition.endpointPath})`,
|
|
24
|
-
);
|
|
25
|
-
await db
|
|
26
|
-
.delete(models)
|
|
27
|
-
.where(
|
|
28
|
-
and(
|
|
29
|
-
eq(models.providerId, providerId),
|
|
30
|
-
eq(models.source, "builtin"),
|
|
31
|
-
sql`(${models.modelId}, ${models.endpointPath}) NOT IN (${sql.join(pairs, sql`, `)})`,
|
|
32
|
-
),
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
const now = new Date();
|
|
36
|
-
const seen = new Set<string>();
|
|
37
|
-
const rows = [...builtinModels.entries()].flatMap(([providerId, definitions]) =>
|
|
38
|
-
definitions.flatMap((definition) => {
|
|
39
|
-
const key = `${providerId}\u0000${definition.modelId}\u0000${definition.endpointPath}`;
|
|
40
|
-
if (seen.has(key)) return [];
|
|
41
|
-
seen.add(key);
|
|
42
|
-
return [
|
|
43
|
-
{
|
|
44
|
-
providerId,
|
|
45
|
-
modelId: definition.modelId,
|
|
46
|
-
wireFamily: definition.wireFamily,
|
|
47
|
-
serviceKind: definition.serviceKind ?? "llm",
|
|
48
|
-
endpointPath: definition.endpointPath,
|
|
49
|
-
contextLimit: definition.contextLimit,
|
|
50
|
-
outputLimit: definition.outputLimit,
|
|
51
|
-
modalities: definition.modalities,
|
|
52
|
-
reasoning: definition.reasoning,
|
|
53
|
-
toolCall: definition.toolCall,
|
|
54
|
-
cost: definition.cost,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
1
|
+
/** Durable materialization of bundled provider model metadata. */
|
|
2
|
+
import { and, eq, sql } from "drizzle-orm";
|
|
3
|
+
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
4
|
+
import { models } from "../../persistence/schema";
|
|
5
|
+
import type { ModelDefinition } from "../provider-model-definition";
|
|
6
|
+
|
|
7
|
+
/** Persists compiled provider model metadata and reconciles drifted capability flags. */
|
|
8
|
+
export async function seedBundledModels(
|
|
9
|
+
db: CartethyiaDatabase,
|
|
10
|
+
builtinModels: ReadonlyMap<string, readonly ModelDefinition[]>,
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
for (const [providerId, definitions] of builtinModels) {
|
|
13
|
+
if (definitions.length === 0) continue;
|
|
14
|
+
// Drop builtin rows the current catalog no longer declares, including rows
|
|
15
|
+
// whose model id is still present but whose endpoint path drifted (e.g. a
|
|
16
|
+
// provider that moved from `/chat/completions` to `/v2/chat/completions`).
|
|
17
|
+
// The unique key is (provider, model, endpoint), so a changed endpoint
|
|
18
|
+
// would otherwise insert a second row and leave the stale route behind —
|
|
19
|
+
// the model list would then show duplicates and probes could hit the dead
|
|
20
|
+
// path. Composite `NOT IN` keeps exactly the (model, endpoint) pairs the
|
|
21
|
+
// catalog owns.
|
|
22
|
+
const pairs = definitions.map(
|
|
23
|
+
(definition) => sql`(${definition.modelId}, ${definition.endpointPath})`,
|
|
24
|
+
);
|
|
25
|
+
await db
|
|
26
|
+
.delete(models)
|
|
27
|
+
.where(
|
|
28
|
+
and(
|
|
29
|
+
eq(models.providerId, providerId),
|
|
30
|
+
eq(models.source, "builtin"),
|
|
31
|
+
sql`(${models.modelId}, ${models.endpointPath}) NOT IN (${sql.join(pairs, sql`, `)})`,
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const now = new Date();
|
|
36
|
+
const seen = new Set<string>();
|
|
37
|
+
const rows = [...builtinModels.entries()].flatMap(([providerId, definitions]) =>
|
|
38
|
+
definitions.flatMap((definition) => {
|
|
39
|
+
const key = `${providerId}\u0000${definition.modelId}\u0000${definition.endpointPath}`;
|
|
40
|
+
if (seen.has(key)) return [];
|
|
41
|
+
seen.add(key);
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
providerId,
|
|
45
|
+
modelId: definition.modelId,
|
|
46
|
+
wireFamily: definition.wireFamily,
|
|
47
|
+
serviceKind: definition.serviceKind ?? "llm",
|
|
48
|
+
endpointPath: definition.endpointPath,
|
|
49
|
+
contextLimit: definition.contextLimit,
|
|
50
|
+
outputLimit: definition.outputLimit,
|
|
51
|
+
modalities: definition.modalities,
|
|
52
|
+
reasoning: definition.reasoning,
|
|
53
|
+
toolCall: definition.toolCall,
|
|
54
|
+
cost: definition.cost,
|
|
55
|
+
// A static row either knows it is refused on a free plan or says
|
|
56
|
+
// nothing. Never coerced to false: the column is three-valued, and
|
|
57
|
+
// "nobody established it" is not the same claim as "served on free".
|
|
58
|
+
proOnly: definition.proOnly ?? null,
|
|
59
|
+
// Same three-valued rule for the inverse: the provider's directory
|
|
60
|
+
// states the tier, so the flag is carried rather than derived.
|
|
61
|
+
freeTier: definition.freeTier ?? null,
|
|
62
|
+
source: "builtin",
|
|
63
|
+
sourceUpdatedAt: now,
|
|
64
|
+
enabled: true,
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
if (rows.length === 0) return;
|
|
70
|
+
// Reconcile static metadata on conflict rather than skipping: capability
|
|
71
|
+
// flags (toolCall/reasoning/webSearch), wire family, limits, and cost can
|
|
72
|
+
// drift on a pre-existing row (e.g. a discovery seeded `tool_call=false`
|
|
73
|
+
// before the static catalog was corrected). `source` is forced back to
|
|
74
|
+
// `builtin`: a fetch upsert may have flipped a builtin row to `discovered`,
|
|
75
|
+
// and the static catalog owns these ids. `enabled` is operator-owned and
|
|
76
|
+
// must never be reset here.
|
|
77
|
+
await db.insert(models).values(rows).onConflictDoUpdate({
|
|
78
|
+
target: [models.providerId, models.modelId, models.endpointPath],
|
|
79
|
+
set: {
|
|
80
|
+
wireFamily: sql`excluded.wire_family`,
|
|
81
|
+
serviceKind: sql`excluded.service_kind`,
|
|
82
|
+
endpointPath: sql`excluded.endpoint_path`,
|
|
83
|
+
contextLimit: sql`excluded.context_limit`,
|
|
84
|
+
outputLimit: sql`excluded.output_limit`,
|
|
85
|
+
modalities: sql`excluded.modalities`,
|
|
86
|
+
reasoning: sql`excluded.reasoning`,
|
|
87
|
+
toolCall: sql`excluded.tool_call`,
|
|
88
|
+
cost: sql`excluded.cost`,
|
|
89
|
+
proOnly: sql`excluded.pro_only`,
|
|
90
|
+
freeTier: sql`excluded.free_tier`,
|
|
91
|
+
source: sql`'builtin'`,
|
|
92
|
+
sourceUpdatedAt: sql`excluded.source_updated_at`,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -29,8 +29,31 @@ const CRED_CACHE_MAX_ENTRIES = 2_000;
|
|
|
29
29
|
interface CredCacheEntry {
|
|
30
30
|
readonly at: number;
|
|
31
31
|
readonly value: ResolvedCredential;
|
|
32
|
+
/**
|
|
33
|
+
* When this entry must stop being served, or `undefined` for "the default
|
|
34
|
+
* TTL applies".
|
|
35
|
+
*
|
|
36
|
+
* Per-entry rather than global because a credential's usable lifetime is a
|
|
37
|
+
* property of the credential: an OAuth token with 30 minutes left can be
|
|
38
|
+
* served for the full window, one with 20 seconds left for almost none of
|
|
39
|
+
* it. Storing the bound on the entry lets both share one cache.
|
|
40
|
+
*/
|
|
41
|
+
readonly expiresAt?: number;
|
|
32
42
|
}
|
|
33
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Margin kept between a cached OAuth token's expiry and the moment the cache
|
|
46
|
+
* stops serving it.
|
|
47
|
+
*
|
|
48
|
+
* The old rule refused to cache OAuth at all unless the token outlived the
|
|
49
|
+
* whole 5 s window, which meant every attempt paid a SELECT + AES-GCM decrypt
|
|
50
|
+
* for every OAuth account — the largest remaining per-request DB read. Caching
|
|
51
|
+
* against the token's own expiry is the same safety with a fraction of the
|
|
52
|
+
* reads. The margin covers clock skew between this process and the issuer, and
|
|
53
|
+
* the time a request already in flight may hold the value.
|
|
54
|
+
*/
|
|
55
|
+
const CRED_CACHE_EXPIRY_MARGIN_MS = 30_000;
|
|
56
|
+
|
|
34
57
|
const credCache = new Map<string, CredCacheEntry>();
|
|
35
58
|
|
|
36
59
|
/**
|
|
@@ -159,9 +182,14 @@ export async function resolveCredentialForAccount(
|
|
|
159
182
|
// the cached value would contain a soon-to-expire secret.
|
|
160
183
|
const now = Date.now();
|
|
161
184
|
const cached = credCache.get(credCacheKey(providerId, accountId));
|
|
162
|
-
if (cached
|
|
163
|
-
|
|
164
|
-
|
|
185
|
+
if (cached) {
|
|
186
|
+
// Two independent bounds: the cache window (rotation freshness) and, for a
|
|
187
|
+
// credential whose usable life was known, the moment that life ends. The
|
|
188
|
+
// shorter one wins, so a token about to expire is never served past it and
|
|
189
|
+
// no credential is served stale for longer than the window.
|
|
190
|
+
const withinWindow = now - cached.at < CRED_CACHE_TTL_MS;
|
|
191
|
+
const withinLife = cached.expiresAt === undefined || now < cached.expiresAt;
|
|
192
|
+
if (withinWindow && withinLife) return cached.value;
|
|
165
193
|
}
|
|
166
194
|
|
|
167
195
|
const parsedProviderId = parseProviderId(providerId);
|
|
@@ -208,11 +236,22 @@ export async function resolveCredentialForAccount(
|
|
|
208
236
|
...(authState === undefined ? {} : { auth_state: authState }),
|
|
209
237
|
};
|
|
210
238
|
const value = resolver.resolve(parsedProviderId, [alternative]).credential;
|
|
211
|
-
//
|
|
239
|
+
// Every credential is cached; an OAuth token with a known expiry simply gets
|
|
240
|
+
// a shorter bound. Refusing to cache OAuth was the safe-looking choice that
|
|
241
|
+
// cost a SELECT + decrypt per attempt, and it was stricter than necessary:
|
|
242
|
+
// the risk is serving an expired token, which a TTL tied to that token
|
|
243
|
+
// expiry prevents as completely as not caching does.
|
|
212
244
|
const isOAuth = row.credentialKind === "oauth";
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
245
|
+
const lifeBound =
|
|
246
|
+
isOAuth && dueAt !== undefined ? dueAt - CRED_CACHE_EXPIRY_MARGIN_MS : undefined;
|
|
247
|
+
// A token already inside the margin must not be cached at all — its bound
|
|
248
|
+
// would be in the past, so the entry would be dead on arrival.
|
|
249
|
+
if (lifeBound === undefined || lifeBound > now) {
|
|
250
|
+
credCache.set(credCacheKey(providerId, accountId), {
|
|
251
|
+
at: now,
|
|
252
|
+
value,
|
|
253
|
+
...(lifeBound === undefined ? {} : { expiresAt: lifeBound }),
|
|
254
|
+
});
|
|
216
255
|
evictCredCacheOldest();
|
|
217
256
|
}
|
|
218
257
|
return value;
|
|
@@ -5,6 +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/http2-bidi";
|
|
8
9
|
|
|
9
10
|
/** Closed union of every shipped provider ID. */
|
|
10
11
|
export const BUNDLED_PROVIDER_IDS = BUNDLED_PROVIDER_METADATA.map((definition) => definition.id) as readonly BundledProviderId[];
|
|
@@ -67,14 +68,76 @@ export type ValidatedOutboundWebSocket = (
|
|
|
67
68
|
signal: AbortSignal,
|
|
68
69
|
) => Promise<ProviderWebSocketSession>;
|
|
69
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Validated bidirectional HTTP/2 egress, bound to the same selected network
|
|
73
|
+
* pool as `outbound_fetch`.
|
|
74
|
+
*
|
|
75
|
+
* A Connect-RPC bidi Run is neither a `fetch` (the request body never ends)
|
|
76
|
+
* nor a WebSocket (different protocol), so it gets its own capability — and it
|
|
77
|
+
* goes through the same binding factory as every other egress, which is what
|
|
78
|
+
* keeps SSRF validation, DNS pinning, and pool byte attribution intact. A
|
|
79
|
+
* provider adapter receives this closure and never dials a socket itself.
|
|
80
|
+
*/
|
|
81
|
+
/**
|
|
82
|
+
* The dialer contract handed to a provider adapter.
|
|
83
|
+
*
|
|
84
|
+
* Declared in `network/http2-bidi.ts` — the transport owns the contract — and
|
|
85
|
+
* re-exported here so provider code keeps one import site.
|
|
86
|
+
*/
|
|
87
|
+
export type { ValidatedOutboundHttp2Bidi };
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A provider session that outlives the HTTP request that created it.
|
|
91
|
+
*
|
|
92
|
+
* Cursor's Run stays open between the client's separate HTTP requests: the
|
|
93
|
+
* first request returns a tool call and the next submits that tool's result on
|
|
94
|
+
* the *same* upstream Run. The account and network-pool slot that Run holds
|
|
95
|
+
* must therefore stay held too, while the request-scoped accounting for the
|
|
96
|
+
* first HTTP request is already settled. Ownership is transferred here so the
|
|
97
|
+
* release is idempotent and belongs to exactly one owner.
|
|
98
|
+
*/
|
|
99
|
+
export interface AttemptLeaseOwnership {
|
|
100
|
+
/**
|
|
101
|
+
* Hands the selected account/network-pool lease to a provider session that
|
|
102
|
+
* outlives this request. Returns the release to call exactly once when the
|
|
103
|
+
* session terminates; returns `undefined` when there was nothing to transfer
|
|
104
|
+
* (a credential-less or pool-less route), which is not an error.
|
|
105
|
+
*
|
|
106
|
+
* Idempotent: a second call returns `undefined` and releases nothing.
|
|
107
|
+
*/
|
|
108
|
+
transferToProviderSession(): (() => void) | undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Identity that authorizes a later request to continue a provider session the
|
|
113
|
+
* gateway is already holding.
|
|
114
|
+
*
|
|
115
|
+
* Cursor's tool loop spans two client HTTP requests on one upstream Run, so the
|
|
116
|
+
* Run — and the account lease it holds — outlives the request that opened it. A
|
|
117
|
+
* later request may only claim the Run by presenting the same tenant, API key,
|
|
118
|
+
* provider, and account, all recorded server-side when the Run was created. A
|
|
119
|
+
* call id alone is never sufficient: it is unguessable but it is still just a
|
|
120
|
+
* string a client could carry into another key's request.
|
|
121
|
+
*/
|
|
122
|
+
export interface ProviderContinuationScope {
|
|
123
|
+
readonly tenantId: string;
|
|
124
|
+
readonly apiKeyId: string;
|
|
125
|
+
readonly providerId: string;
|
|
126
|
+
readonly providerAccountId: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
70
129
|
/** Context passed from routing to a provider adapter. */
|
|
71
130
|
export interface ProviderDispatchContext {
|
|
72
131
|
readonly credential: ResolvedCredential;
|
|
73
132
|
readonly deadline: number;
|
|
74
133
|
readonly abort_signal: AbortSignal;
|
|
75
134
|
readonly outbound_fetch?: ValidatedOutboundFetch | undefined;
|
|
135
|
+
/** Validated image/attachment egress; deliberately excluded from payload capture. */
|
|
136
|
+
readonly outbound_attachment_fetch?: ValidatedOutboundFetch | undefined;
|
|
76
137
|
/** Validated WSS egress bound to the same selected network pool as HTTP. */
|
|
77
138
|
readonly outbound_websocket?: ValidatedOutboundWebSocket | undefined;
|
|
139
|
+
/** Validated bidirectional HTTP/2 egress bound to the same selected network pool. */
|
|
140
|
+
readonly outbound_http2?: ValidatedOutboundHttp2Bidi | undefined;
|
|
78
141
|
/** Built-in API-key route-selected fallback User-Agent; custom providers leave this unset. */
|
|
79
142
|
readonly user_agent?: string | undefined;
|
|
80
143
|
/** Gateway request id, stable across provider retries for one logical request. */
|
|
@@ -85,6 +148,19 @@ export interface ProviderDispatchContext {
|
|
|
85
148
|
readonly conversation_affinity?: string | undefined;
|
|
86
149
|
/** Opaque identity shared by retries of one logical gateway request. */
|
|
87
150
|
readonly request_identity?: object | undefined;
|
|
151
|
+
/**
|
|
152
|
+
* Ownership of this attempt's account/network-pool lease. Present only when
|
|
153
|
+
* the route holds a lease; a provider session that outlives the request
|
|
154
|
+
* (Cursor's Run) takes it over so the slot is not released while the Run is
|
|
155
|
+
* live, and not released twice when it ends.
|
|
156
|
+
*/
|
|
157
|
+
readonly lease_ownership?: AttemptLeaseOwnership | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* Identity of the caller, for a provider session that outlives this request.
|
|
160
|
+
* Absent when the route cannot scope a continuation, in which case a session
|
|
161
|
+
* must not be held open across requests at all.
|
|
162
|
+
*/
|
|
163
|
+
readonly continuation_scope?: ProviderContinuationScope | undefined;
|
|
88
164
|
}
|
|
89
165
|
|
|
90
166
|
const USER_AGENT_HEADER_NAME = /^user-agent$/i;
|