cartethyia 2.4.6 → 2.5.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/README.md +45 -13
- package/dist/dashboard/assets/BannedPage-ChqnTRpZ.js +1 -0
- package/dist/dashboard/assets/BannedPage-ChqnTRpZ.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-Bcy150Jo.js → CliToolDetailPage-Bb9CNuoh.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bb9CNuoh.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DyNXefph.js → CliToolsPage-BXUaRCQQ.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BXUaRCQQ.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CisNZOUt.js +1 -0
- package/dist/dashboard/assets/CombosPage-CisNZOUt.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CATpAEOz.js → ConsoleLogPage-BOpI_LOH.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-BOpI_LOH.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-D6jEU7nL.js → CustomizationPage-DAkdvJ4k.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-DAkdvJ4k.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-CZkpn0a7.js → HealthEventsModal-DeT-VXHd.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-DeT-VXHd.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BJlMVN4U.js → LoginPage-CytxSeGP.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CytxSeGP.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-CU7S1wJs.js +1 -0
- package/dist/dashboard/assets/OverviewPage-CU7S1wJs.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.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-DOXfuzfL.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-DOXfuzfL.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js +7 -0
- package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js +1 -0
- package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-BHW4Zt06.js → SettingsPage-8-eVadhc.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-8-eVadhc.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-VOYC_lIa.js → SetupPage-uEIXPyGO.js} +1 -1
- package/dist/dashboard/assets/SetupPage-uEIXPyGO.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DCY0TPTZ.js +1 -0
- package/dist/dashboard/assets/SortableList-DCY0TPTZ.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-BNIDLiU6.js → StudioPage-DqK5E26W.js} +4 -4
- package/dist/dashboard/assets/StudioPage-DqK5E26W.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-38JfiZ0H.js +56 -0
- package/dist/dashboard/assets/UsagePage-38JfiZ0H.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--yj84zk3.js +4 -0
- package/dist/dashboard/assets/entry--yj84zk3.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/{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-CcD1eSJ6.js} +2 -2
- package/dist/dashboard/assets/index-CcD1eSJ6.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-XPJ-dIw3.js +41 -0
- package/dist/dashboard/assets/select-XPJ-dIw3.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/tray.ts +47 -8
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// API-key authentication and authorization snapshots: token extraction, hashing, lookup, and admission identity.
|
|
2
2
|
|
|
3
3
|
import type { CartethyiaDatabase } from "../persistence/db-handle";
|
|
4
|
+
import { deleteShared, publishInvalidation, readShared, writeShared } from "../persistence/shared-cache";
|
|
4
5
|
import { DrizzleApiKeyStore } from "../persistence/stores/api-key-store";
|
|
5
6
|
import { GatewayError } from "../protocol/gateway-error";
|
|
6
7
|
import { hashSecret } from "./crypto";
|
|
@@ -39,6 +40,50 @@ const authCache = new Map<string, AuthCacheEntry>();
|
|
|
39
40
|
const authCacheByKeyId = new Map<string, Set<string>>();
|
|
40
41
|
/** Unknown/revoked token HMACs with their miss time; cleared on any key mutation. */
|
|
41
42
|
const authNegativeCache = new Map<string, number>();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Short circuit after a credential-store failure.
|
|
46
|
+
*
|
|
47
|
+
* An overloaded or down database fails every lookup, and each failure costs a
|
|
48
|
+
* connection attempt. Without this, a saturated backend received one query per
|
|
49
|
+
* inbound request — load the operator sees as traffic, on top of the load that
|
|
50
|
+
* caused the outage. For the window, lookups answer "unavailable" instead.
|
|
51
|
+
*
|
|
52
|
+
* Deliberately short: this is an auth path, and a brief outage must not lock
|
|
53
|
+
* out valid keys for long.
|
|
54
|
+
*/
|
|
55
|
+
const AUTH_FAILURE_COOLDOWN_MS = 1_000;
|
|
56
|
+
let authFailureUntil = 0;
|
|
57
|
+
|
|
58
|
+
/** Thrown when the credential store could not be reached at all. */
|
|
59
|
+
export class ApiKeyStoreUnavailableError extends Error {
|
|
60
|
+
constructor() {
|
|
61
|
+
super("The credential store is unavailable.");
|
|
62
|
+
this.name = "ApiKeyStoreUnavailableError";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Source for lifetime-usage deltas recorded but not yet flushed.
|
|
68
|
+
*
|
|
69
|
+
* Injected rather than imported: this module is on the auth path and must not
|
|
70
|
+
* depend on the persistence/write layer it would otherwise pull in. Set once at
|
|
71
|
+
* boot (`boot/dependencies.ts`); until then it reads as zero, which is the
|
|
72
|
+
* value the code has always used.
|
|
73
|
+
*/
|
|
74
|
+
let pendingUsage: (apiKeyId: string) => number = () => 0;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Supplies the in-flight usage source. See {@link pendingUsage}.
|
|
78
|
+
*
|
|
79
|
+
* The persisted `lifetime_tokens_consumed` is behind by up to one flush
|
|
80
|
+
* interval, and the flush coalesces by design — so it is behind by the *most*
|
|
81
|
+
* exactly when a key is busiest. Adding the pending delta costs nothing and
|
|
82
|
+
* removes the gap.
|
|
83
|
+
*/
|
|
84
|
+
export function setPendingUsageSource(source: (apiKeyId: string) => number): void {
|
|
85
|
+
pendingUsage = source;
|
|
86
|
+
}
|
|
42
87
|
/**
|
|
43
88
|
* Negative-result fast path. Unknown/revoked tokens previously hit the DB on
|
|
44
89
|
* every request; now a repeat miss within 1.5 s costs one HMAC + one map
|
|
@@ -97,12 +142,24 @@ export function invalidateApiKeyCache(keyId?: string): void {
|
|
|
97
142
|
if (keyId === undefined) {
|
|
98
143
|
authCache.clear();
|
|
99
144
|
authCacheByKeyId.clear();
|
|
145
|
+
void deleteShared("auth");
|
|
146
|
+
publishInvalidation({ namespace: "auth" });
|
|
100
147
|
return;
|
|
101
148
|
}
|
|
102
149
|
const hashes = authCacheByKeyId.get(keyId);
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
150
|
+
if (hashes) {
|
|
151
|
+
for (const hash of hashes) authCache.delete(hash);
|
|
152
|
+
authCacheByKeyId.delete(keyId);
|
|
153
|
+
}
|
|
154
|
+
// Peer processes hold their own copy, and `reusePort` means the console
|
|
155
|
+
// write may have landed on a different one entirely. Dropping only locally
|
|
156
|
+
// leaves a revoked key valid for up to a full TTL everywhere else.
|
|
157
|
+
//
|
|
158
|
+
// Delivered by broadcast rather than by deleting a shared entry: a mutation
|
|
159
|
+
// names a key id, and the shared layer is keyed by token hash, which the
|
|
160
|
+
// mutating request does not have. The notice is at-most-once, so the TTL
|
|
161
|
+
// stays the backstop for a peer that misses it.
|
|
162
|
+
publishInvalidation({ namespace: "auth", key: keyId });
|
|
106
163
|
}
|
|
107
164
|
|
|
108
165
|
/**
|
|
@@ -274,6 +331,19 @@ export function requestToken(headers: Headers | Record<string, string>): string
|
|
|
274
331
|
return token;
|
|
275
332
|
}
|
|
276
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Whether a key can possibly have children, and so whether summing them can
|
|
336
|
+
* return anything but zero.
|
|
337
|
+
*
|
|
338
|
+
* Children exist only for a `share` parent — the schema's own check constraint
|
|
339
|
+
* ties the mode to the parent-child shape (schema.ts:799-803), and no other
|
|
340
|
+
* mode mints recipients. Extracted and exported because the alternative is an
|
|
341
|
+
* untestable inline comparison guarding a database round-trip.
|
|
342
|
+
*/
|
|
343
|
+
export function canHaveChildKeys(key: { readonly keyMode?: string | null }): boolean {
|
|
344
|
+
return key.keyMode === "share";
|
|
345
|
+
}
|
|
346
|
+
|
|
277
347
|
/**
|
|
278
348
|
* Looks up the API key by its hashed bearer token. Returns `undefined` for
|
|
279
349
|
* an unknown or revoked key — callers must reject with 401, never fall back
|
|
@@ -292,8 +362,37 @@ export async function resolveApiKeyAuthorization(
|
|
|
292
362
|
}
|
|
293
363
|
const cached = authCache.get(hash);
|
|
294
364
|
if (cached && now - cached.at < AUTH_CACHE_TTL_MS) return cached.value;
|
|
365
|
+
// L2: a peer process may have resolved this token already. Reachable only
|
|
366
|
+
// after the L1 entry lapsed, so Redis is never consulted on a warm hit.
|
|
367
|
+
//
|
|
368
|
+
// Keyed by token hash, because that is what a lookup has — invalidation
|
|
369
|
+
// arrives as a key id and is delivered as a broadcast instead.
|
|
370
|
+
const shared = await readShared("auth", hash);
|
|
371
|
+
if (shared !== undefined) {
|
|
372
|
+
try {
|
|
373
|
+
const parsed = JSON.parse(shared) as ResolvedApiKey;
|
|
374
|
+
if (typeof parsed?.id === "string") {
|
|
375
|
+
indexAuthCache(hash, { at: now, value: parsed, keyId: parsed.id });
|
|
376
|
+
evictAuthCacheOldest();
|
|
377
|
+
return parsed;
|
|
378
|
+
}
|
|
379
|
+
} catch {
|
|
380
|
+
// A malformed or schema-drifted entry is ignored and re-resolved.
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Answered from the cooldown before any I/O: during a store outage the
|
|
384
|
+
// query is the expensive part, and it is also the part that fails.
|
|
385
|
+
if (now < authFailureUntil) throw new ApiKeyStoreUnavailableError();
|
|
295
386
|
const store = new DrizzleApiKeyStore(db);
|
|
296
|
-
|
|
387
|
+
let row: Awaited<ReturnType<typeof store.findActiveByHash>>;
|
|
388
|
+
try {
|
|
389
|
+
row = await store.findActiveByHash(hash);
|
|
390
|
+
} catch (error) {
|
|
391
|
+
// Any failure here is the store's, not the key's — a revoked key is a
|
|
392
|
+
// normal empty result, so this only runs on a real fault.
|
|
393
|
+
authFailureUntil = Date.now() + AUTH_FAILURE_COOLDOWN_MS;
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
297
396
|
if (!row) {
|
|
298
397
|
authNegativeCache.set(hash, now);
|
|
299
398
|
evictAuthNegativeOldest();
|
|
@@ -313,11 +412,19 @@ export async function resolveApiKeyAuthorization(
|
|
|
313
412
|
// the old row values forever. Reading the parent here makes denylist /
|
|
314
413
|
// one-time / recurring edits apply on the next cache miss.
|
|
315
414
|
const policy = parent ?? row;
|
|
415
|
+
// Only a `share` parent can have children, and children are the only thing
|
|
416
|
+
// the SUM below can find. Every other key ran an aggregate that was certain
|
|
417
|
+
// to return 0 — one extra Postgres round-trip on the auth path for nothing.
|
|
418
|
+
//
|
|
419
|
+
// `row.parentKeyId !== null` is the same test, kept as the mode check because
|
|
420
|
+
// the mode is what the share dialog and the schema's own check constraint
|
|
421
|
+
// key off (schema.ts:799-803), so the two cannot drift apart.
|
|
422
|
+
const canHaveChildren = canHaveChildKeys(parent ?? row);
|
|
316
423
|
const consumed =
|
|
317
|
-
(policy.id === row.id
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
(
|
|
424
|
+
((policy.id === row.id ? row.lifetimeTokensConsumed : policy.lifetimeTokensConsumed) ?? 0) +
|
|
425
|
+
(canHaveChildren ? await store.sumChildrenConsumed(policy.id) : 0) +
|
|
426
|
+
// In-flight deltas: what has been spent but not yet persisted.
|
|
427
|
+
pendingUsage(policy.id);
|
|
321
428
|
const scopes = Array.isArray(policy.scopes) ? (policy.scopes as AccessScope[]) : [];
|
|
322
429
|
const snapshot = createAuthorizationSnapshot({
|
|
323
430
|
api_key_id: row.id,
|
|
@@ -349,5 +456,16 @@ export async function resolveApiKeyAuthorization(
|
|
|
349
456
|
};
|
|
350
457
|
indexAuthCache(hash, { at: Date.now(), value, keyId: value.id });
|
|
351
458
|
evictAuthCacheOldest();
|
|
459
|
+
// Shared copy for peer processes, keyed by the token hash so a lookup can
|
|
460
|
+
// find it without resolving the key first. Invalidation travels by broadcast,
|
|
461
|
+
// since a console mutation names a key id and cannot enumerate the hashes
|
|
462
|
+
// that resolved to it in another process.
|
|
463
|
+
//
|
|
464
|
+
// The cached value deliberately carries `lifetime_tokens_consumed` as read.
|
|
465
|
+
// That figure moves constantly, and the module already documents the snapshot
|
|
466
|
+
// as advisory on that axis (spending is enforced by the admission counter),
|
|
467
|
+
// so a peer serving it stale for up to the TTL is the same tradeoff the
|
|
468
|
+
// in-process cache has always made.
|
|
469
|
+
void writeShared("auth", hash, JSON.stringify(value), AUTH_CACHE_TTL_MS);
|
|
352
470
|
return value;
|
|
353
471
|
}
|
|
@@ -64,6 +64,9 @@ export const NATIVE_SERVICES: readonly NativeServiceRoute[] = [
|
|
|
64
64
|
{ serviceKind: "websearch", path: "/v1/search" },
|
|
65
65
|
{ serviceKind: "imageGeneration", path: "/v1/images/generations" },
|
|
66
66
|
{ serviceKind: "videoGeneration", path: "/v1/videos/generations" },
|
|
67
|
+
{ serviceKind: "embedding", path: "/v1/embeddings" },
|
|
68
|
+
{ serviceKind: "audioTranscription", path: "/v1/audio/transcriptions" },
|
|
69
|
+
{ serviceKind: "audioSpeech", path: "/v1/audio/speech" },
|
|
67
70
|
] as const;
|
|
68
71
|
|
|
69
72
|
/** Paths served by a native service route, for the ingress body/canonical skip. */
|
|
@@ -39,6 +39,29 @@ export async function shouldCaptureProviderBody(
|
|
|
39
39
|
return payloadCaptureKeepsBodies(db, tenantId);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Same answer as {@link shouldCaptureProviderBody}, memoized on the request.
|
|
44
|
+
*
|
|
45
|
+
* The preference is a tenant setting, so it cannot change between attempts of
|
|
46
|
+
* one request. Resolving it per attempt meant a request with several attempts
|
|
47
|
+
* paid for it repeatedly, and the per-attempt answer could in principle differ
|
|
48
|
+
* from the one the terminal capture then acted on. Passing `state` lets every
|
|
49
|
+
* attempt of a request share one resolution.
|
|
50
|
+
*/
|
|
51
|
+
export async function shouldCaptureProviderBodyForRequest(
|
|
52
|
+
db: CartethyiaDatabase,
|
|
53
|
+
tenantId: string | null,
|
|
54
|
+
state: { payloadCapturePreference?: unknown },
|
|
55
|
+
): Promise<boolean> {
|
|
56
|
+
if (state.payloadCapturePreference === undefined) {
|
|
57
|
+
state.payloadCapturePreference = await resolvePayloadCapture(db, tenantId);
|
|
58
|
+
}
|
|
59
|
+
const resolved = state.payloadCapturePreference as
|
|
60
|
+
| { readonly ok: true; readonly mode: string }
|
|
61
|
+
| { readonly ok: false };
|
|
62
|
+
return resolved.ok === true && resolved.mode === "full";
|
|
63
|
+
}
|
|
64
|
+
|
|
42
65
|
/**
|
|
43
66
|
* Combined body cap for one capture depth. Fixed size tiers: every depth
|
|
44
67
|
* stores all four drawer panels (client request, translated provider
|
|
@@ -313,7 +336,15 @@ async function captureTerminalPayload(
|
|
|
313
336
|
return;
|
|
314
337
|
}
|
|
315
338
|
try {
|
|
316
|
-
|
|
339
|
+
// Resolved once per request and memoized on the request state. The
|
|
340
|
+
// per-attempt call in the dispatch paths and this terminal call were
|
|
341
|
+
// resolving the same tenant setting independently, so a request with
|
|
342
|
+
// several attempts paid for it repeatedly and the two call sites could
|
|
343
|
+
// disagree about whether capture is on at all.
|
|
344
|
+
if (state.payloadCapturePreference === undefined) {
|
|
345
|
+
state.payloadCapturePreference = await resolvePayloadCapture(db, tenantId);
|
|
346
|
+
}
|
|
347
|
+
const resolved = state.payloadCapturePreference;
|
|
317
348
|
if (!resolved.ok) {
|
|
318
349
|
note("skipped_preference_read_failed");
|
|
319
350
|
return;
|