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,101 +1,173 @@
|
|
|
1
|
-
import { openSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
1
|
+
import { openSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
clearInstanceLock,
|
|
4
|
+
fingerprint,
|
|
5
|
+
pidAlive,
|
|
6
|
+
readInstanceLock,
|
|
7
|
+
writeInstanceLock,
|
|
8
|
+
} from "./instance-lock";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { PACKAGE_ROOT, resolvePackagePath } from "../../../src/config/package-paths";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Frees the port when what holds it is this install's own gateway.
|
|
14
|
+
*
|
|
15
|
+
* Returns `null` when the port is free (or was successfully taken), and a
|
|
16
|
+
* message to fail with otherwise. It never kills a process it cannot identify:
|
|
17
|
+
* a stranger's service on this port is reported, not drained.
|
|
18
|
+
*
|
|
19
|
+
* The probe is two-step on purpose. `/health` alone is answered by anything, so
|
|
20
|
+
* it only says "something is there"; the drain request is what distinguishes
|
|
21
|
+
* ours from someone else's, because it is token-gated — and a 401 means another
|
|
22
|
+
* Cartethyia install, which this one must not stop.
|
|
23
|
+
*/
|
|
24
|
+
async function claimPort(port: number, drainToken: string): Promise<string | null> {
|
|
25
|
+
// A lock naming this install's own gateway is the strongest evidence
|
|
26
|
+
// available, so prefer it over the network probe when it is live.
|
|
27
|
+
const lock = readInstanceLock();
|
|
28
|
+
const ours = lock !== null && lock.port === port && lock.tokenFingerprint === fingerprint(drainToken);
|
|
29
|
+
|
|
30
|
+
const response = await fetch(`http://127.0.0.1:${port}/admin/drain`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "x-drain-token": drainToken },
|
|
33
|
+
signal: AbortSignal.timeout(3_000),
|
|
34
|
+
}).catch(() => null);
|
|
35
|
+
|
|
36
|
+
if (response === null) {
|
|
37
|
+
// Nothing answered a drain request. If our own lock names a dead pid, the
|
|
38
|
+
// marker is stale and nothing is holding the port — clear it and continue.
|
|
39
|
+
if (ours && !pidAlive(lock.pid)) {
|
|
40
|
+
clearInstanceLock();
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return `Port ${port} is busy and nothing there answers a drain request. Stop whatever holds it, or set a different PORT.`;
|
|
44
|
+
}
|
|
45
|
+
if (response.status === 401) {
|
|
46
|
+
return `Another Cartethyia gateway is already running on port ${port} under a different install. Stop it from its own terminal or tray — this install will not stop a gateway it does not own.`;
|
|
47
|
+
}
|
|
48
|
+
if (response.status === 403) {
|
|
49
|
+
return `Port ${port} refused a drain from loopback. Stop what holds it, then start again.`;
|
|
50
|
+
}
|
|
51
|
+
if (response.status !== 202) {
|
|
52
|
+
return `Port ${port} is busy and refused to drain (HTTP ${response.status}). Stop it manually, then start again.`;
|
|
53
|
+
}
|
|
54
|
+
// Drain accepted. Wait for the port to actually free: the gateway flushes its
|
|
55
|
+
// 202 before closing sockets, so acceptance is not release.
|
|
56
|
+
const deadline = Date.now() + 30_000;
|
|
57
|
+
while (Date.now() < deadline) {
|
|
58
|
+
const stillUp = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
59
|
+
signal: AbortSignal.timeout(500),
|
|
60
|
+
}).catch(() => null);
|
|
61
|
+
if (stillUp === null) {
|
|
62
|
+
clearInstanceLock();
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
await Bun.sleep(200);
|
|
66
|
+
}
|
|
67
|
+
return `The gateway on port ${port} accepted a drain but did not release the port within 30 seconds. Stop it manually, then start again.`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Starts the gateway and resolves once it answers its own health probe.
|
|
72
|
+
*
|
|
73
|
+
* The child is attached by default. A detached child cannot be killed from an
|
|
74
|
+
* `exit` handler on Windows (measured: `kill()` runs, the gateway keeps
|
|
75
|
+
* serving), and a terminal's Ctrl-C only reaches children in the same process
|
|
76
|
+
* group. Staying attached means Ctrl-C and the `exit` backstop both stop the
|
|
77
|
+
* gateway. "Hide to tray" passes `detached: true` because that gateway must
|
|
78
|
+
* outlive this process.
|
|
79
|
+
*/
|
|
80
|
+
export async function startGateway(
|
|
81
|
+
port: number,
|
|
82
|
+
drainToken: string,
|
|
83
|
+
options: { readonly detached?: boolean } = {},
|
|
84
|
+
): Promise<Bun.Subprocess> {
|
|
85
|
+
// Starting a second gateway on the same port used to fail with "stopped
|
|
86
|
+
// before becoming ready (exit code 1)", which says nothing about the real
|
|
87
|
+
// cause. Probe the port first and say what is actually there.
|
|
88
|
+
const existing = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
89
|
+
signal: AbortSignal.timeout(1_000),
|
|
90
|
+
}).catch(() => null);
|
|
91
|
+
if (existing !== null) {
|
|
92
|
+
// Something already owns the port. Replace it only when it proves to be our
|
|
93
|
+
// own gateway — otherwise stop and say what is there. A gateway left behind
|
|
94
|
+
// by a closed terminal used to make every later start fail until the
|
|
95
|
+
// operator found and killed it by hand.
|
|
96
|
+
const claimed = await claimPort(port, drainToken);
|
|
97
|
+
if (claimed !== null) throw new Error(claimed);
|
|
98
|
+
}
|
|
99
|
+
// The gateway logs a startup phase line per stage. In menu mode those lines
|
|
100
|
+
// would scroll through the same terminal the menu is drawing on, so they go
|
|
101
|
+
// to a file instead and the operator sees only the menu.
|
|
102
|
+
const configDir = process.env.CARTETHYIA_CONFIG_DIR;
|
|
103
|
+
let logFd: number | null = null;
|
|
104
|
+
if (configDir) {
|
|
105
|
+
try {
|
|
106
|
+
logFd = openSync(join(configDir, "gateway.log"), "a");
|
|
107
|
+
} catch {
|
|
108
|
+
// A read-only config directory costs only the log, not startup.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const child = Bun.spawn(
|
|
112
|
+
[process.execPath, "run", resolvePackagePath("src", "main.ts")],
|
|
113
|
+
{
|
|
114
|
+
cwd: PACKAGE_ROOT,
|
|
115
|
+
env: { ...process.env, CARTETHYIA_DRAIN_TOKEN: drainToken },
|
|
116
|
+
// "ignore", not "inherit": the gateway does not read stdin, and in menu
|
|
117
|
+
// mode the menu is the only stdin consumer. With "inherit" the gateway
|
|
118
|
+
// held the fd and swallowed every keypress after the first, so the menu
|
|
119
|
+
// rendered but never received Enter.
|
|
120
|
+
stdin: "ignore",
|
|
121
|
+
stdout: logFd ?? "ignore",
|
|
122
|
+
// stderr goes to the same file: "inherit" ties the gateway to the
|
|
123
|
+
// terminal, so closing that terminal took the gateway down with it and
|
|
124
|
+
// "Hide to tray" left nothing running.
|
|
125
|
+
stderr: logFd ?? "ignore",
|
|
126
|
+
detached: options.detached === true,
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
const startupController = new AbortController();
|
|
130
|
+
const ready = (async (): Promise<void> => {
|
|
131
|
+
const deadline = Date.now() + 30_000;
|
|
132
|
+
while (!startupController.signal.aborted && Date.now() < deadline) {
|
|
133
|
+
try {
|
|
134
|
+
const response = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
135
|
+
signal: AbortSignal.timeout(500),
|
|
136
|
+
});
|
|
137
|
+
if (response.ok) {
|
|
138
|
+
const body: unknown = await response.json();
|
|
139
|
+
if (body !== null && typeof body === "object" && "status" in body && body.status === "ok") return;
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// The gateway is not accepting health requests yet.
|
|
143
|
+
}
|
|
144
|
+
await Bun.sleep(250);
|
|
145
|
+
}
|
|
146
|
+
if (startupController.signal.aborted) throw new Error("Cartethyia exited before becoming ready");
|
|
147
|
+
throw new Error("Cartethyia did not open its configured port within 30 seconds");
|
|
148
|
+
})();
|
|
149
|
+
const exitedEarly = child.exited.then((code) => {
|
|
150
|
+
startupController.abort();
|
|
151
|
+
return { status: "exit" as const, code };
|
|
152
|
+
});
|
|
153
|
+
const readyResult = ready.then(
|
|
154
|
+
() => ({ status: "ready" as const }),
|
|
155
|
+
(error: unknown) => ({ status: "failure" as const, error }),
|
|
156
|
+
);
|
|
157
|
+
try {
|
|
158
|
+
const result = await Promise.race([readyResult, exitedEarly]);
|
|
159
|
+
if (result.status === "exit") {
|
|
160
|
+
throw new Error(`Cartethyia stopped before becoming ready (exit code ${result.code})`);
|
|
161
|
+
}
|
|
162
|
+
if (result.status === "failure") throw result.error;
|
|
163
|
+
// Recorded only once the child is actually serving: a marker written before
|
|
164
|
+
// readiness would name a gateway that never came up, and the next start
|
|
165
|
+
// would treat a live port as ours when it is not.
|
|
166
|
+
writeInstanceLock(port, child.pid, drainToken);
|
|
167
|
+
return child;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
child.kill();
|
|
170
|
+
await child.exited;
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -1,49 +1,110 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* child
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*/
|
|
32
|
-
export function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { readFileSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { PACKAGE_ROOT, resolvePackagePath } from "../../../src/config/package-paths";
|
|
4
|
+
|
|
5
|
+
/** Opens a URL in the operator's default browser, ignoring a missing handler. */
|
|
6
|
+
export function openUrl(url: string): Promise<void> {
|
|
7
|
+
const command = process.platform === "win32"
|
|
8
|
+
? "explorer.exe"
|
|
9
|
+
: process.platform === "darwin"
|
|
10
|
+
? "open"
|
|
11
|
+
: "xdg-open";
|
|
12
|
+
const child = Bun.spawn([command, url], {
|
|
13
|
+
stdin: "ignore",
|
|
14
|
+
stdout: "ignore",
|
|
15
|
+
stderr: "ignore",
|
|
16
|
+
});
|
|
17
|
+
return child.exited.then(() => undefined);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Starts a detached process and resolves once it reports readiness.
|
|
22
|
+
*
|
|
23
|
+
* The child owns no stdio, so a signal file is the only channel that survives
|
|
24
|
+
* the parent. Without it the caller prints "running in the background" for a
|
|
25
|
+
* child that may already have died — which is exactly what a port clash looks
|
|
26
|
+
* like, because the child's own startup probe fails while the parent's gateway
|
|
27
|
+
* still holds the port.
|
|
28
|
+
*
|
|
29
|
+
* Resolves with the URL the child reported. Rejects with the child's message
|
|
30
|
+
* when it reports failure, or with a timeout if it never reports at all.
|
|
31
|
+
*/
|
|
32
|
+
export function launchBackgroundWithStatus(
|
|
33
|
+
args: readonly string[],
|
|
34
|
+
options: { readonly signalPath: string; readonly timeoutMs?: number } = { signalPath: "" },
|
|
35
|
+
): Promise<string> {
|
|
36
|
+
return new Promise<string>((resolve, reject) => {
|
|
37
|
+
const signalPath = options.signalPath;
|
|
38
|
+
const deadline = Date.now() + (options.timeoutMs ?? 60_000);
|
|
39
|
+
let settled = false;
|
|
40
|
+
const finish = (error: Error | null, url?: string): void => {
|
|
41
|
+
if (settled) return;
|
|
42
|
+
settled = true;
|
|
43
|
+
clearInterval(timer);
|
|
44
|
+
try {
|
|
45
|
+
unlinkSync(signalPath);
|
|
46
|
+
} catch {}
|
|
47
|
+
if (error) reject(error);
|
|
48
|
+
else resolve(url ?? "");
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const env = { ...process.env, CARTETHYIA_TRAY_STARTUP_SIGNAL: signalPath };
|
|
52
|
+
spawnBackground(args, env);
|
|
53
|
+
|
|
54
|
+
const timer = setInterval(() => {
|
|
55
|
+
if (settled) return;
|
|
56
|
+
let raw: string;
|
|
57
|
+
try {
|
|
58
|
+
raw = readFileSync(signalPath, "utf8");
|
|
59
|
+
} catch {
|
|
60
|
+
if (Date.now() >= deadline) finish(new Error("the background process did not report startup within the deadline"));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const signal = JSON.parse(raw) as { status?: string; url?: string; message?: string };
|
|
65
|
+
if (signal.status === "ready" && typeof signal.url === "string") finish(null, signal.url);
|
|
66
|
+
else if (signal.status === "failed") finish(new Error(signal.message ?? "the background process failed to start"));
|
|
67
|
+
} catch {
|
|
68
|
+
// A partially written file is read again on the next tick.
|
|
69
|
+
}
|
|
70
|
+
}, 150);
|
|
71
|
+
timer.unref?.();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Starts a Bun process that owns no console window.
|
|
77
|
+
*
|
|
78
|
+
* On Windows, spawning the child directly is not enough: a console-subsystem
|
|
79
|
+
* child of a window-less parent allocates a new console, and when the parent is
|
|
80
|
+
* inside Windows Terminal the tab is already on screen before any flag can take
|
|
81
|
+
* effect — measured: `detached: true` plus `windowsHide: true` still produced a
|
|
82
|
+
* Windows Terminal window titled with the bun path. `wscript.exe` is a
|
|
83
|
+
* GUI-subsystem host, so it never allocates a console, and a child launched
|
|
84
|
+
* from it with window style 0 inherits that. The VBS is that launcher.
|
|
85
|
+
*
|
|
86
|
+
* Elsewhere `detached` is enough and the helper is not used.
|
|
87
|
+
*/
|
|
88
|
+
export function launchBackground(args: readonly string[]): void {
|
|
89
|
+
spawnBackground(args, process.env);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function spawnBackground(args: readonly string[], env: NodeJS.ProcessEnv): void {
|
|
93
|
+
if (process.platform !== "win32") {
|
|
94
|
+
const child = spawn(process.execPath, [...args], {
|
|
95
|
+
cwd: PACKAGE_ROOT,
|
|
96
|
+
detached: true,
|
|
97
|
+
stdio: "ignore",
|
|
98
|
+
env,
|
|
99
|
+
});
|
|
100
|
+
child.unref();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const launcher = resolvePackagePath("scripts", "cli", "tray", "hidden-window.vbs");
|
|
104
|
+
const child = spawn(
|
|
105
|
+
"wscript.exe",
|
|
106
|
+
["//nologo", launcher, process.execPath, ...args],
|
|
107
|
+
{ cwd: PACKAGE_ROOT, detached: true, stdio: "ignore", windowsHide: true, env },
|
|
108
|
+
);
|
|
109
|
+
child.unref();
|
|
110
|
+
}
|
package/src/boot/app.ts
CHANGED
|
@@ -34,14 +34,21 @@ import {
|
|
|
34
34
|
import { createResponsesCompactHandler } from "../transport/dispatch/responses-compact";
|
|
35
35
|
import { createSystemoneHandler } from "../transport/dispatch/systemone";
|
|
36
36
|
import { createWebsearchHandler } from "../transport/dispatch/websearch";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
createAudioSpeechHandler,
|
|
39
|
+
createAudioTranscriptionHandler,
|
|
40
|
+
createEmbeddingHandler,
|
|
41
|
+
createImageGenerationHandler,
|
|
42
|
+
createVideoGenerationHandler,
|
|
43
|
+
} from "../transport/dispatch/generation";
|
|
38
44
|
import { createTokenCountHandler } from "../transport/dispatch/token-count";
|
|
39
45
|
|
|
40
46
|
import { createTransportPipeline } from "../transport/guards/pipeline";
|
|
41
47
|
import { createDrainHandler, isLoopbackPeer, tokenMatches } from "../transport/drain-endpoint";
|
|
42
48
|
import { createConsoleAccountResetHandler } from "../console/auth/account-reset";
|
|
49
|
+
import { resetConsoleAuthState } from "../console/auth/account-reset-store";
|
|
43
50
|
import { createBackupExportHandler } from "../console/backup/export-endpoint";
|
|
44
|
-
import {
|
|
51
|
+
import { tenants } from "../persistence/schema";
|
|
45
52
|
import { PublicModelCatalogStore, type AllowedModelEntry } from "../console/providers/catalog/public-model-store";
|
|
46
53
|
import type { IpAbuseProtectionService } from "../security/abuse";
|
|
47
54
|
import type { ReadinessCheckResult } from "../persistence/readiness";
|
|
@@ -514,6 +521,13 @@ export function createGatewayApp(deps: GatewayAppDeps) {
|
|
|
514
521
|
// Mounted with no model behind it on purpose: the surface exists so it can
|
|
515
522
|
// be finished when an upstream is known, rather than 404ing like a typo.
|
|
516
523
|
const handleVideoGeneration = createVideoGenerationHandler(generationDeps);
|
|
524
|
+
// Passthrough surfaces: routed and accounted for, but never interpreted.
|
|
525
|
+
// No bundled adapter implements embeddings or audio, and inventing a wire
|
|
526
|
+
// for them would be a guess; what these need to be useful is the route, the
|
|
527
|
+
// pipeline, and a compatible upstream behind it.
|
|
528
|
+
const handleEmbedding = createEmbeddingHandler(generationDeps);
|
|
529
|
+
const handleAudioTranscription = createAudioTranscriptionHandler(generationDeps);
|
|
530
|
+
const handleAudioSpeech = createAudioSpeechHandler(generationDeps);
|
|
517
531
|
// Shares the generation dependency bundle: same pipeline, and a count is
|
|
518
532
|
// neither chat-shaped nor generation-shaped, so it needs exactly the same
|
|
519
533
|
// routing, leasing, and accounting surface.
|
|
@@ -535,6 +549,9 @@ export function createGatewayApp(deps: GatewayAppDeps) {
|
|
|
535
549
|
routes.post("/search", handleWebsearch);
|
|
536
550
|
routes.post("/images/generations", handleImageGeneration);
|
|
537
551
|
routes.post("/videos/generations", handleVideoGeneration);
|
|
552
|
+
routes.post("/embeddings", handleEmbedding);
|
|
553
|
+
routes.post("/audio/transcriptions", handleAudioTranscription);
|
|
554
|
+
routes.post("/audio/speech", handleAudioSpeech);
|
|
538
555
|
routes.get("/models", handleModelsList);
|
|
539
556
|
routes.get("/models/info", handleModelsDetail);
|
|
540
557
|
routes.get("/models/*", handleModelsDetail);
|
|
@@ -582,10 +599,7 @@ export function createGatewayApp(deps: GatewayAppDeps) {
|
|
|
582
599
|
createConsoleAccountResetHandler({
|
|
583
600
|
token: deps.drainToken,
|
|
584
601
|
resolvePeerAddress: (request) => peerAddresses.get(request) ?? null,
|
|
585
|
-
resetConsoleAccount:
|
|
586
|
-
const users = await deps.db.delete(consoleUsers);
|
|
587
|
-
return users.rowCount ?? 0;
|
|
588
|
-
},
|
|
602
|
+
resetConsoleAccount: () => resetConsoleAuthState(deps.db),
|
|
589
603
|
}),
|
|
590
604
|
);
|
|
591
605
|
}
|
package/src/boot/dependencies.ts
CHANGED
|
@@ -32,6 +32,9 @@ import { RedisAdmissionCounterStore } from "../security/admission/redis-store";
|
|
|
32
32
|
import { sweepLeases } from "../security/admission/lease-sweep";
|
|
33
33
|
import { DrizzleApiKeyStore } from "../persistence/stores/api-key-store";
|
|
34
34
|
import { UsageFlushBuffer } from "../persistence/usage-flush";
|
|
35
|
+
import { setPendingUsageSource } from "../security/api-key-auth";
|
|
36
|
+
import { HealthEventFlushBuffer, setHealthEventBuffer } from "../persistence/health-event-flush";
|
|
37
|
+
import { subscribePeerSettingsRevisions } from "../persistence/settings-revision";
|
|
35
38
|
import { deleteExpiredSessions } from "../console/auth/session-service";
|
|
36
39
|
import { InMemoryIpAbuseStore, IpAbuseProtectionService, RedisIpAbuseStore } from "../security/abuse";
|
|
37
40
|
import {
|
|
@@ -111,6 +114,7 @@ export interface ProductionDeps {
|
|
|
111
114
|
poolSelector: NetworkPoolSelector;
|
|
112
115
|
telemetryBuffer: TelemetryBatchBuffer;
|
|
113
116
|
usageFlush: UsageFlushBuffer;
|
|
117
|
+
healthEventFlush: HealthEventFlushBuffer;
|
|
114
118
|
trustedProxyBoundary: TrustedProxyBoundary;
|
|
115
119
|
providerRegistry: ProviderRegistry;
|
|
116
120
|
resolveOAuthRefresher: (providerId: string) => Promise<OAuthTokenRefresher | undefined>;
|
|
@@ -204,7 +208,21 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
204
208
|
}
|
|
205
209
|
return [...totals].map(([accountId, inflight]) => ({ accountId, inflight }));
|
|
206
210
|
};
|
|
211
|
+
// Peer processes' settings mutations arrive here. Subscribed once for the
|
|
212
|
+
// process: the revision counter is process-global, and the handler only
|
|
213
|
+
// moves it forward.
|
|
214
|
+
subscribePeerSettingsRevisions();
|
|
207
215
|
const usageFlush = new UsageFlushBuffer(db);
|
|
216
|
+
// Auth reads lifetime usage to seed the admission counter. The persisted
|
|
217
|
+
// value lags by up to one flush interval, so hand it the in-flight deltas
|
|
218
|
+
// too — otherwise the seed is systematically under what has actually been
|
|
219
|
+
// spent, worst exactly when the key is busiest.
|
|
220
|
+
setPendingUsageSource((apiKeyId) => usageFlush.pendingFor(apiKeyId));
|
|
221
|
+
// Health audit rows are append-only and read by nobody in dispatch, so they
|
|
222
|
+
// no longer land inside the request. The status write they accompany stays
|
|
223
|
+
// synchronous — routing reads it.
|
|
224
|
+
const healthEventFlush = new HealthEventFlushBuffer(db);
|
|
225
|
+
setHealthEventBuffer(healthEventFlush);
|
|
208
226
|
const admissionStore = redis
|
|
209
227
|
? new RedisAdmissionCounterStore(redis)
|
|
210
228
|
: new InMemoryAdmissionCounterStore();
|
|
@@ -548,6 +566,7 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
548
566
|
poolAgentResolver,
|
|
549
567
|
telemetryBuffer,
|
|
550
568
|
usageFlush,
|
|
569
|
+
healthEventFlush,
|
|
551
570
|
poolSelector,
|
|
552
571
|
admissionService,
|
|
553
572
|
trustedProxyBoundary: resolveTrustedProxyBoundary(),
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wires Elysia's TypeBox bridge statically, for a source-run process.
|
|
3
|
+
*
|
|
4
|
+
* Elysia reaches TypeBox through a lazy `require("typebox/schema")` that
|
|
5
|
+
* resolves to a partial namespace outside a bundler. The bridge supplies all
|
|
6
|
+
* five namespaces explicitly so the lookup never has to happen.
|
|
7
|
+
*
|
|
8
|
+
* This is necessary but **not sufficient** for `NODE_ENV=production`: it does
|
|
9
|
+
* not stop the eager validator branch from reading
|
|
10
|
+
* `this.tb.buildResult.external`, which is still absent on a source run. That
|
|
11
|
+
* half is owned by `resolveElysiaPrecompile()`, which turns precompilation off
|
|
12
|
+
* for a source run. Keep both — wiring the bridge without the precompile guard
|
|
13
|
+
* still crashes, and the guard without the bridge leaves the lazy require in
|
|
14
|
+
* place for anything else that needs it.
|
|
15
|
+
*
|
|
16
|
+
* Keep this import first in `main.ts` so it runs before any module builds a
|
|
17
|
+
* route.
|
|
18
|
+
*/
|
|
19
|
+
import { setupTypebox } from "elysia";
|
|
20
|
+
import * as typeboxType from "typebox";
|
|
21
|
+
import * as typeboxCompile from "typebox/compile";
|
|
22
|
+
import * as typeboxSchema from "typebox/schema";
|
|
23
|
+
import * as typeboxSystem from "typebox/system";
|
|
24
|
+
import * as typeboxValue from "typebox/value";
|
|
25
|
+
|
|
26
|
+
setupTypebox({
|
|
27
|
+
typebox: {
|
|
28
|
+
// The type side (`type` + `system`) and the value side (`value` + `schema`
|
|
29
|
+
// + `compile`) must both be complete; Elysia rejects a partial set because
|
|
30
|
+
// a half-wired bridge fails later with a less obvious error.
|
|
31
|
+
type: typeboxType,
|
|
32
|
+
system: typeboxSystem,
|
|
33
|
+
value: typeboxValue,
|
|
34
|
+
schema: typeboxSchema,
|
|
35
|
+
compile: typeboxCompile,
|
|
36
|
+
},
|
|
37
|
+
});
|
package/src/boot/lifecycle.ts
CHANGED
|
@@ -220,12 +220,14 @@ export async function bootstrap(): Promise<CartethyiaBoot> {
|
|
|
220
220
|
flushTelemetry: async () => {
|
|
221
221
|
await deps.telemetryBuffer.flush();
|
|
222
222
|
await deps.usageFlush.flush();
|
|
223
|
+
await deps.healthEventFlush.flush();
|
|
223
224
|
},
|
|
224
225
|
closePools: async () => {
|
|
225
226
|
await boot.server?.stop();
|
|
226
227
|
await deps.scheduledTasks.stop();
|
|
227
228
|
await deps.telemetryBuffer.stop({ flush: true });
|
|
228
229
|
await deps.usageFlush.stop({ flush: true });
|
|
230
|
+
await deps.healthEventFlush.stop();
|
|
229
231
|
await Promise.allSettled([
|
|
230
232
|
closeDb(),
|
|
231
233
|
closeRedis(),
|