cartethyia 2.7.0 → 2.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +107 -106
- package/README.md +25 -2
- package/dist/dashboard/assets/{BannedPage-P76QLGJA.js → BannedPage-XaUmiBrC.js} +1 -1
- package/dist/dashboard/assets/BannedPage-XaUmiBrC.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-DdBggJS9.js → CliToolDetailPage-CqD7QiLM.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-aN1iXhhP.js → CliToolsPage-0XUo4UMn.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-DE-F1nxZ.js → CombosPage-CXGS_Ct-.js} +1 -1
- package/dist/dashboard/assets/CombosPage-CXGS_Ct-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-3CUxMy2X.js → CustomizationPage-DAG5J9EY.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BdmRLnq2.js +1 -0
- package/dist/dashboard/assets/LoginPage-BdmRLnq2.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-DKVslqG3.js → ModelPicker-BH8Tehdi.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js +1 -0
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js +4 -0
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js +9 -0
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-BjldQwIU.js → QuotaPage-7P2n--Fv.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-DUIoLRxk.js → SettingsPage-CxZ1fTkN.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js +1 -0
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-fI7JkdIB.js → SortableList-Dqf4zpS-.js} +1 -1
- package/dist/dashboard/assets/SortableList-Dqf4zpS-.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CHVZqskZ.js → StudioPage-Byj2lAEm.js} +3 -3
- package/dist/dashboard/assets/StudioPage-Byj2lAEm.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-CTkLAuRc.js +56 -0
- package/dist/dashboard/assets/UsagePage-CTkLAuRc.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-DFY_fZU4.js → api-keys-CUi6UXsR.js} +1 -1
- package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css +2 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
- package/dist/dashboard/assets/{assert-CwdnZARt.js → assert-67xXH5rf.js} +1 -1
- package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
- package/dist/dashboard/assets/{badge-Dr-uf0Au.js → badge-DC2L43Mj.js} +1 -1
- package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BddmMdoS.js → banner-BBVcb0J3.js} +1 -1
- package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
- package/dist/dashboard/assets/{card-CK0PXWlX.js → card-Cca1XC6G.js} +1 -1
- package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-CcB6mGh2.js → cli-tools-KvsWa1Jl.js} +1 -1
- package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DGb2P9Yx.js → clipboard-button-DUzEbynq.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-LkfuL50u.js → dialog-DFvWhwTa.js} +1 -1
- package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-ZbEZ_84c.js → drawer-CFM2sMX1.js} +1 -1
- package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BJfsVMvA.js → entry-Bqy8RFvM.js} +1 -1
- package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CWBY5A0f.js → entry-ClCMCWWQ.js} +3 -3
- package/dist/dashboard/assets/entry-ClCMCWWQ.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CvC1-Zqx.js → entry-_xHo5x6r.js} +1 -1
- package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CWWZ-uCZ.js +1 -0
- package/dist/dashboard/assets/formatters-CWWZ-uCZ.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-BxpznFLt.js → github-badge-C9e8Ts7e.js} +1 -1
- package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
- package/dist/dashboard/assets/{index-CDzP3ovJ.js → index-DFVFrnWW.js} +2 -2
- package/dist/dashboard/assets/index-DFVFrnWW.js.gz +0 -0
- package/dist/dashboard/assets/{inline-Ae13MQ5w.js → inline-BFUcvllj.js} +1 -1
- package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
- package/dist/dashboard/assets/{input-D23fo7cp.js → input-C_ILAvBb.js} +1 -1
- package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
- package/dist/dashboard/assets/{layout-CSsxjj5P.js → layout-CBIyT6Rt.js} +1 -1
- package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-dmbwZMDY.js → list-row-ebJ_-Nsb.js} +1 -1
- package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
- package/dist/dashboard/assets/{live-BG1oUsZZ.js → live-D0mm--ME.js} +1 -1
- package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-DSF-e4ho.js → mutation-sMD965oO.js} +1 -1
- package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-Bm_aECY7.js → numbers-BO9dKaFr.js} +1 -1
- package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
- package/dist/dashboard/assets/providers/inception.svg +5 -0
- package/dist/dashboard/assets/providers/inception.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-CF56nYIV.js → query-keys-CfL_Zdoh.js} +1 -1
- package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CeA71n7G.js → routing-BnIpi1IJ.js} +1 -1
- package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
- package/dist/dashboard/assets/{select-KIT0rC6G.js → select-DBNVLdUu.js} +1 -1
- package/dist/dashboard/assets/select-DBNVLdUu.js.gz +0 -0
- package/dist/dashboard/assets/{settings-B_2gkePf.js → settings-BAFhDgX8.js} +1 -1
- package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
- package/dist/dashboard/assets/{stack-D7WOSzcX.js → stack-EMrOMt4I.js} +1 -1
- package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
- package/dist/dashboard/assets/{state-BBxtKZOQ.js → state-CWoo7ty5.js} +1 -1
- package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DNS_D5Ea.js → switch-CCjT6IkF.js} +1 -1
- package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BgoxdoMk.js → useMutation-DjkLHUcE.js} +1 -1
- package/dist/dashboard/assets/useMutation-DjkLHUcE.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 +54 -53
- package/migrations/full-postgres/0024_add_pool_provider_cooldowns.sql +12 -0
- package/migrations/full-postgres/0025_add_pool_random_strategy.sql +3 -0
- package/migrations/lite-sqlite3/0024_add_pool_provider_cooldowns.sql +12 -0
- package/migrations/lite-sqlite3/0025_add_pool_random_strategy.sql +20 -0
- package/package.json +96 -96
- package/scripts/cli/console-menu.ts +5 -2
- package/scripts/cli/tray.ts +21 -6
- package/src/boot/app.ts +684 -714
- package/src/boot/dependencies.ts +65 -62
- package/src/config/cooldowns.ts +16 -0
- package/src/config/providers.ts +13 -1
- package/src/config/resources.ts +70 -70
- package/src/config/security.ts +37 -37
- package/src/config/spec.ts +371 -358
- package/src/console/cli-tools/contracts.ts +10 -4
- package/src/console/console-router.ts +233 -225
- package/src/console/domain-registration.ts +398 -381
- package/src/console/providers/catalog/catalog-projections.ts +2 -6
- package/src/console/providers/catalog/contracts.ts +19 -0
- package/src/console/providers/catalog/provider-operations.ts +38 -0
- package/src/console/providers/catalog/routes.ts +14 -0
- package/src/console/providers/catalog/store-helpers.ts +326 -0
- package/src/console/providers/catalog/store.ts +1292 -1563
- package/src/console/quota/account-quota-global.ts +2 -2
- package/src/console/quota/account-quota-shared.ts +1 -1
- package/src/console/quota/account-quota-tenant.ts +35 -12
- package/src/console/quota/quota-view.ts +2 -2
- package/src/console/routing/pools/contracts.ts +41 -1
- package/src/console/routing/pools/operations.ts +183 -4
- package/src/console/routing/pools/relay-deploy.ts +17 -4
- package/src/console/routing/pools/routes.ts +30 -0
- package/src/console/routing/pools/store.ts +98 -8
- package/src/main.ts +183 -167
- package/src/network/outbound-fetch.ts +124 -208
- package/src/network/pool/agent.ts +15 -0
- package/src/network/pool/pool-state.ts +7 -0
- package/src/network/pool/selector.ts +110 -2
- package/src/network/pool-cooldown-store.ts +252 -0
- package/src/network/pool-health-machine.ts +5 -3
- package/src/network/pool-health.ts +36 -9
- package/src/observability/event-query.ts +57 -0
- package/src/observability/metrics.ts +467 -467
- package/src/observability/runtime-metrics.ts +269 -269
- package/src/persistence/health-event-flush.ts +2 -0
- package/src/persistence/schema.ts +19 -0
- package/src/persistence/usage-flush.ts +8 -3
- package/src/protocol/canonical-model.ts +1 -1
- package/src/protocol/dsml.ts +377 -0
- package/src/protocol/gateway-error.ts +232 -232
- package/src/{providers/provider-reasoning.ts → protocol/reasoning.ts} +10 -9
- package/src/protocol/request/chat.ts +7 -7
- package/src/protocol/request/codex.ts +2 -2
- package/src/protocol/request/gemini.ts +1 -1
- package/src/protocol/request/messages.ts +6 -6
- package/src/protocol/request/responses.ts +7 -7
- package/src/protocol/response/dsml.ts +12 -133
- package/src/{transport → protocol}/translation/attachment-integrity.ts +2 -2
- package/src/{transport → protocol}/translation/cache-controls.ts +1 -1
- package/src/{transport → protocol}/translation/capabilities.ts +2 -2
- package/src/{transport → protocol}/translation/normalize.ts +1 -1
- package/src/{transport → protocol}/translation/quirks.ts +1 -1
- package/src/{transport → protocol}/translation/search.ts +1 -1
- package/src/{transport → protocol}/translation/thinking.ts +23 -1
- package/src/{transport → protocol}/translation/tool-id.ts +1 -1
- package/src/{transport → protocol}/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream-parsing.ts +176 -0
- package/src/providers/adapters/compatible-adapter.ts +11 -2
- package/src/providers/authentication/oauth-refresh-service.ts +9 -5
- package/src/providers/default-registry.ts +22 -5
- package/src/providers/discovery/probe-phases.ts +16 -3
- package/src/providers/discovery/probing-service.ts +4 -1
- package/src/providers/integrations/anthropic.ts +1 -1
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -4
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +141 -8
- package/src/providers/integrations/buddy/buddy-checkin.ts +5 -1
- package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -82
- package/src/providers/integrations/buddy/buddy-identity.ts +25 -0
- package/src/providers/integrations/buddy/buddy-server-tools.ts +240 -0
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +36 -0
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +6 -31
- package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +19 -4
- package/src/providers/integrations/buddy/turing-device-token.ts +262 -0
- package/src/providers/integrations/buddy/workbuddy/provider.ts +2 -0
- package/src/providers/integrations/buddy/workbuddy/shared.ts +56 -3
- package/src/providers/integrations/claude/claude.ts +1 -1
- package/src/providers/integrations/cloudflareai/cloudflareai.ts +1 -1
- package/src/providers/integrations/codex/codex-errors.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +1 -1
- package/src/providers/integrations/factory/factory.ts +1 -1
- package/src/providers/integrations/github/github.ts +1 -1
- package/src/providers/integrations/grok/grok.ts +1 -1
- package/src/providers/integrations/inception/provider.ts +91 -0
- package/src/providers/integrations/inferhub.ts +1 -1
- package/src/providers/integrations/kiro/kiro-request.ts +1 -1
- package/src/providers/integrations/mistral.ts +193 -19
- package/src/providers/integrations/ollamacloud-models.ts +50 -0
- package/src/providers/integrations/opencode/fingerprint.ts +19 -2
- package/src/providers/integrations/opencode/prompt.ts +111 -0
- package/src/providers/integrations/opencode/provider.ts +101 -23
- package/src/providers/integrations/opencode/tool-definitions.ts +4 -4
- package/src/providers/integrations/qoder/qoder-checkin.ts +303 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +12 -1
- package/src/providers/integrations/qoder/qoder-realm.ts +20 -0
- package/src/providers/integrations/qoder/qoder-text-tools.ts +277 -0
- package/src/providers/integrations/qoder/qoder.ts +315 -400
- package/src/providers/integrations/shared/install-id.ts +2 -1
- package/src/providers/integrations/trae/trae-cache.ts +110 -0
- package/src/providers/integrations/trae/trae-oauth.ts +29 -4
- package/src/providers/integrations/trae/trae-protocol.ts +2 -1
- package/src/providers/integrations/trae/trae.ts +106 -23
- package/src/providers/operations/account-error-classification.ts +1684 -0
- package/src/providers/operations/account-health-service.ts +27 -1503
- package/src/providers/operations/account-reset-service.ts +2 -0
- package/src/providers/operations/provider-catalog-service.ts +11 -1
- package/src/providers/provider-contracts.ts +7 -4
- package/src/providers/provider-metadata.ts +25 -4
- package/src/providers/provider-probe.ts +2 -2
- package/src/{console → providers}/quota/cache.ts +1 -1
- package/src/{console → providers}/quota/refresh.ts +5 -5
- package/src/security/cli-client-fingerprint.ts +9 -2
- package/src/transport/dispatch/attempt-finalize.ts +114 -12
- package/src/transport/dispatch/attempt-loop.ts +3 -0
- package/src/transport/dispatch/empty-stream.ts +9 -2
- package/src/transport/dispatch/generation.ts +37 -55
- package/src/transport/dispatch/handler-deps.ts +32 -0
- package/src/transport/dispatch/leases.ts +9 -4
- package/src/transport/dispatch/proxy-request.ts +6 -30
- package/src/transport/dispatch/responses-compact.ts +22 -43
- package/src/transport/dispatch/streaming/finalize.ts +3 -0
- package/src/transport/dispatch/streaming/stream.ts +0 -8
- package/src/transport/dispatch/systemone.ts +22 -42
- package/src/transport/dispatch/token-count.ts +23 -39
- package/src/transport/dispatch/upstream.ts +1 -3
- package/src/transport/dispatch/websearch.ts +22 -41
- package/src/transport/failure-policy.ts +92 -178
- package/src/transport/guards/abuse-guard.ts +10 -0
- package/src/transport/guards/auth-guard.ts +109 -109
- package/src/transport/guards/error-lifecycle.ts +1 -1
- package/src/transport/guards/pipeline.ts +220 -220
- package/src/transport/guards/request-context.ts +245 -238
- package/src/transport/request/degradation.ts +20 -1
- package/src/transport/request/inflight.ts +4 -114
- package/src/transport/request/preparer.ts +6 -6
- package/src/transport/request/state.ts +66 -38
- package/src/transport/request/tenant-preferences.ts +1 -1
- package/src/transport/routing/route-catalog.ts +75 -43
- package/src/transport/routing/router.ts +30 -2
- package/src/transport/surface/messages/parse.ts +1 -1
- package/src/transport/surface/responses/encode.ts +2 -3
- package/src/transport/surface/responses/parse.ts +49 -7
- package/src/types/undici.d.ts +33 -0
- package/src/workers/buddy-growth-tasks.ts +346 -425
- package/src/workers/daily-checkin.ts +17 -6
- package/src/workers/quota-refresh-sweep.ts +2 -2
- package/src/workers/scheduler.ts +17 -1
- package/dist/dashboard/assets/BannedPage-P76QLGJA.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DdBggJS9.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-aN1iXhhP.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DE-F1nxZ.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-3CUxMy2X.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-B_0FUKWc.js +0 -1
- package/dist/dashboard/assets/LoginPage-B_0FUKWc.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-DKVslqG3.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js +0 -1
- package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js +0 -7
- package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BjldQwIU.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DUIoLRxk.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-_bCaTM0v.js +0 -1
- package/dist/dashboard/assets/SetupPage-_bCaTM0v.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-fI7JkdIB.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-CHVZqskZ.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DiFBymHj.js +0 -56
- package/dist/dashboard/assets/UsagePage-DiFBymHj.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DFY_fZU4.js.gz +0 -0
- package/dist/dashboard/assets/app-BOJ1_xUB.css +0 -2
- package/dist/dashboard/assets/app-BOJ1_xUB.css.gz +0 -0
- package/dist/dashboard/assets/assert-CwdnZARt.js.gz +0 -0
- package/dist/dashboard/assets/badge-Dr-uf0Au.js.gz +0 -0
- package/dist/dashboard/assets/banner-BddmMdoS.js.gz +0 -0
- package/dist/dashboard/assets/card-CK0PXWlX.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CcB6mGh2.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DGb2P9Yx.js.gz +0 -0
- package/dist/dashboard/assets/cog-CdWhsajs.js +0 -1
- package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
- package/dist/dashboard/assets/dialog-LkfuL50u.js.gz +0 -0
- package/dist/dashboard/assets/drawer-ZbEZ_84c.js.gz +0 -0
- package/dist/dashboard/assets/entry-BJfsVMvA.js.gz +0 -0
- package/dist/dashboard/assets/entry-CWBY5A0f.js.gz +0 -0
- package/dist/dashboard/assets/entry-CvC1-Zqx.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-BxpznFLt.js.gz +0 -0
- package/dist/dashboard/assets/index-CDzP3ovJ.js.gz +0 -0
- package/dist/dashboard/assets/inline-Ae13MQ5w.js.gz +0 -0
- package/dist/dashboard/assets/input-D23fo7cp.js.gz +0 -0
- package/dist/dashboard/assets/layout-CSsxjj5P.js.gz +0 -0
- package/dist/dashboard/assets/list-row-dmbwZMDY.js.gz +0 -0
- package/dist/dashboard/assets/live-BG1oUsZZ.js.gz +0 -0
- package/dist/dashboard/assets/mutation-DSF-e4ho.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Bm_aECY7.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-CF56nYIV.js.gz +0 -0
- package/dist/dashboard/assets/routing-CeA71n7G.js.gz +0 -0
- package/dist/dashboard/assets/select-KIT0rC6G.js.gz +0 -0
- package/dist/dashboard/assets/settings-B_2gkePf.js.gz +0 -0
- package/dist/dashboard/assets/stack-D7WOSzcX.js.gz +0 -0
- package/dist/dashboard/assets/state-BBxtKZOQ.js.gz +0 -0
- package/dist/dashboard/assets/switch-DNS_D5Ea.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BgoxdoMk.js.gz +0 -0
- /package/dist/dashboard/assets/{app-CaXZ9SCn.js → app-BaWd0puM.js} +0 -0
- /package/dist/dashboard/assets/{app-CaXZ9SCn.js.gz → app-BaWd0puM.js.gz} +0 -0
|
@@ -1,220 +1,220 @@
|
|
|
1
|
-
import { Elysia } from "elysia";
|
|
2
|
-
import { createIngressPolicyMiddleware } from "./body-policy";
|
|
3
|
-
import {
|
|
4
|
-
createCanonicalRequestMiddleware,
|
|
5
|
-
createClientIdentityMiddleware,
|
|
6
|
-
createProxyRoutePreparationMiddleware,
|
|
7
|
-
createRequestContextMiddleware,
|
|
8
|
-
} from "./request-context";
|
|
9
|
-
import type { CanonicalAdapter, ShutdownDrainSource } from "../contracts";
|
|
10
|
-
import { createApiKeyAuthenticationMiddleware } from "./auth-guard";
|
|
11
|
-
import { createDependencyReadinessMiddleware } from "./readiness-guard";
|
|
12
|
-
import { createIpAbuseProtectionMiddleware } from "./abuse-guard";
|
|
13
|
-
import { createHeadroomGuard } from "./headroom-guard";
|
|
14
|
-
import {
|
|
15
|
-
createErrorNormalizationMiddleware,
|
|
16
|
-
registerRequestCleanup,
|
|
17
|
-
registerTelemetryLifecycle,
|
|
18
|
-
type AfterResponseApp,
|
|
19
|
-
} from "./error-lifecycle";
|
|
20
|
-
import type { ReadinessCheckResult } from "../../persistence/readiness";
|
|
21
|
-
import type { IpAbuseProtectionService } from "../../security/abuse";
|
|
22
|
-
import type { ProxyRequestPreparer } from "../request/preparer";
|
|
23
|
-
import { ProxyRequestStateStore } from "../request/state";
|
|
24
|
-
import type { SurfaceAdapterRegistry } from "../surface/adapters";
|
|
25
|
-
import type { TrustedProxyBoundary } from "../../config/network";
|
|
26
|
-
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
27
|
-
import type { RedisClient } from "../../persistence/redis";
|
|
28
|
-
import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer";
|
|
29
|
-
import type { RuntimeMemorySnapshot } from "../../observability/runtime-metrics";
|
|
30
|
-
import {
|
|
31
|
-
TelemetryPayloadCapture,
|
|
32
|
-
payloadCaptureKeepsBodies,
|
|
33
|
-
payloadRetentionMs,
|
|
34
|
-
resolvePayloadCapture,
|
|
35
|
-
} from "../../observability/payload-capture";
|
|
36
|
-
|
|
37
|
-
/** Shared state/context carried by every ordered transport stage. */
|
|
38
|
-
export interface TransportPipelineContext {
|
|
39
|
-
readonly db: CartethyiaDatabase;
|
|
40
|
-
readonly stateStore: ProxyRequestStateStore;
|
|
41
|
-
readonly surfaceRegistry: SurfaceAdapterRegistry;
|
|
42
|
-
readonly adapters: ReadonlyMap<string, CanonicalAdapter>;
|
|
43
|
-
readonly preparer: ProxyRequestPreparer;
|
|
44
|
-
readonly readiness: () => Promise<ReadinessCheckResult>;
|
|
45
|
-
readonly trustedProxyBoundary: TrustedProxyBoundary;
|
|
46
|
-
readonly resolvePeerAddress?: (request: Request) => string | null;
|
|
47
|
-
readonly ipAbuseProtection?: IpAbuseProtectionService;
|
|
48
|
-
readonly maxBodyBytes?: number;
|
|
49
|
-
readonly requestDeadlineMs?: number;
|
|
50
|
-
readonly verifiedHttps?: boolean;
|
|
51
|
-
/** Graduated strikes for repeated invalid-model requests. */
|
|
52
|
-
readonly shutdownCoordinator?: ShutdownDrainSource;
|
|
53
|
-
readonly telemetry?: TelemetryBatchBuffer;
|
|
54
|
-
/**
|
|
55
|
-
* Shared client for the headroom guard's shed marker. Optional: the shed
|
|
56
|
-
* decision is local, and Redis is only written for fleet visibility.
|
|
57
|
-
*/
|
|
58
|
-
readonly redis?: RedisClient | undefined;
|
|
59
|
-
/**
|
|
60
|
-
* The sampled memory state the headroom guard sheds against.
|
|
61
|
-
*
|
|
62
|
-
* Supplied by the runtime sampler. When absent the guard cannot compare RSS
|
|
63
|
-
* against a limit and does not shed — so this must be wired in production,
|
|
64
|
-
* and the sampler must be initialized at boot even before its first tick.
|
|
65
|
-
*/
|
|
66
|
-
readonly memorySnapshot?: () => RuntimeMemorySnapshot | undefined;
|
|
67
|
-
/**
|
|
68
|
-
* Whether invoke is switched off for a tenant. Optional: without it the
|
|
69
|
-
* gateway never refuses on the kill switch, which is the pre-change
|
|
70
|
-
* behaviour rather than a failure.
|
|
71
|
-
*/
|
|
72
|
-
readonly isInvokeDisabled?: (tenantId: string) => Promise<boolean | undefined>;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* The single ingress owner. `mountRoot` applies the process-wide request
|
|
77
|
-
* context and error-normalization lifecycle; `createGateway` composes the
|
|
78
|
-
* ordered fail-closed `/v1/*` stage chain plus the telemetry/cleanup
|
|
79
|
-
* lifecycle around caller-registered gateway routes. Composition code
|
|
80
|
-
* (`app.ts`) mounts — it never assembles middleware policy itself.
|
|
81
|
-
*/
|
|
82
|
-
export interface TransportPipeline {
|
|
83
|
-
/** Applies root-scoped request context and error normalization. */
|
|
84
|
-
mountRoot(app: Elysia<any, any, any, any, any, any, any, any>): void;
|
|
85
|
-
/** Builds the complete `/v1/*` gateway plugin around registered routes. */
|
|
86
|
-
createGateway(registerRoutes: (routes: Elysia<any, any, any, any, any, any, any, any>) => void): Elysia;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Builds the one ordered middleware pipeline. Request context and error
|
|
91
|
-
* normalization are mounted at the root; the remaining stages are mounted
|
|
92
|
-
* before every `/v1/*` route, preserving fail-closed ordering.
|
|
93
|
-
*/
|
|
94
|
-
export function createTransportPipeline(context: TransportPipelineContext): TransportPipeline {
|
|
95
|
-
const stages: Elysia[] = [
|
|
96
|
-
createHeadroomGuard({
|
|
97
|
-
store: context.stateStore,
|
|
98
|
-
...(context.memorySnapshot === undefined ? {} : { memorySnapshot: context.memorySnapshot }),
|
|
99
|
-
...(context.redis === undefined ? {} : { redis: context.redis }),
|
|
100
|
-
}),
|
|
101
|
-
createDependencyReadinessMiddleware({
|
|
102
|
-
readiness: context.readiness,
|
|
103
|
-
...(context.shutdownCoordinator ? { shutdownCoordinator: context.shutdownCoordinator } : {}),
|
|
104
|
-
}),
|
|
105
|
-
createIngressPolicyMiddleware({
|
|
106
|
-
stateStore: context.stateStore,
|
|
107
|
-
...(context.maxBodyBytes === undefined ? {} : { maxBodyBytes: context.maxBodyBytes }),
|
|
108
|
-
}),
|
|
109
|
-
createClientIdentityMiddleware({
|
|
110
|
-
stateStore: context.stateStore,
|
|
111
|
-
trustedProxyBoundary: context.trustedProxyBoundary,
|
|
112
|
-
...(context.resolvePeerAddress ? { resolvePeerAddress: context.resolvePeerAddress } : {}),
|
|
113
|
-
}),
|
|
114
|
-
createApiKeyAuthenticationMiddleware({
|
|
115
|
-
db: context.db,
|
|
116
|
-
stateStore: context.stateStore,
|
|
117
|
-
...(context.isInvokeDisabled
|
|
118
|
-
? { isInvokeDisabled: context.isInvokeDisabled }
|
|
119
|
-
: {}),
|
|
120
|
-
}),
|
|
121
|
-
createCanonicalRequestMiddleware({
|
|
122
|
-
stateStore: context.stateStore,
|
|
123
|
-
surfaceRegistry: context.surfaceRegistry,
|
|
124
|
-
adapters: context.adapters,
|
|
125
|
-
keepsIngressBody: (tenantId) => payloadCaptureKeepsBodies(context.db, tenantId),
|
|
126
|
-
}),
|
|
127
|
-
createProxyRoutePreparationMiddleware({
|
|
128
|
-
stateStore: context.stateStore,
|
|
129
|
-
preparer: context.preparer,
|
|
130
|
-
}),
|
|
131
|
-
];
|
|
132
|
-
const mountRoot = (app: Elysia<any, any, any, any, any, any, any, any>): void => {
|
|
133
|
-
app.use(
|
|
134
|
-
createRequestContextMiddleware({
|
|
135
|
-
stateStore: context.stateStore,
|
|
136
|
-
...(context.requestDeadlineMs === undefined
|
|
137
|
-
? {}
|
|
138
|
-
: { requestDeadlineMs: context.requestDeadlineMs }),
|
|
139
|
-
}),
|
|
140
|
-
);
|
|
141
|
-
// Root-mounted, not a gateway stage: its `request` hook must run for every
|
|
142
|
-
// `/v1/*` request, including paths that match no route, or a caller can
|
|
143
|
-
// hammer the gateway without ever being counted. See the middleware's own
|
|
144
|
-
// doc for the measured bypass this closes.
|
|
145
|
-
if (context.ipAbuseProtection) {
|
|
146
|
-
app.use(
|
|
147
|
-
createIpAbuseProtectionMiddleware({
|
|
148
|
-
stateStore: context.stateStore,
|
|
149
|
-
ipAbuseProtection: context.ipAbuseProtection,
|
|
150
|
-
trustedProxyBoundary: context.trustedProxyBoundary,
|
|
151
|
-
...(context.resolvePeerAddress ? { resolvePeerAddress: context.resolvePeerAddress } : {}),
|
|
152
|
-
}),
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
app.use(
|
|
156
|
-
createErrorNormalizationMiddleware({
|
|
157
|
-
stateStore: context.stateStore,
|
|
158
|
-
hsts: context.verifiedHttps === true,
|
|
159
|
-
}),
|
|
160
|
-
);
|
|
161
|
-
// Finalize + cleanup live at the ROOT, not on the gateway plugin.
|
|
162
|
-
//
|
|
163
|
-
// A plugin-scoped `afterResponse` only fires for a request that matched a
|
|
164
|
-
// registered route, so an unregistered `/v1/*` path — the cheapest thing a
|
|
165
|
-
// scanner can send — was admitted by the root `request` hook (which does
|
|
166
|
-
// run for every inbound request, that is why the in-flight count and the
|
|
167
|
-
// IP-abuse counter are there) but never cleaned up. Measured: one
|
|
168
|
-
// unmatched `/v1/*` request left `proxy_in_flight` permanently higher and
|
|
169
|
-
// `activeCount()` permanently one larger, so the gauge only ever climbed
|
|
170
|
-
// for the life of the process. The root hook runs for the unmatched path
|
|
171
|
-
// too, which is what makes the increment and the decrement symmetric.
|
|
172
|
-
//
|
|
173
|
-
// Ordering is safe because a root `afterResponse` still runs *after* the
|
|
174
|
-
// matched handler returns (measured: request -> handler -> root.afterResponse
|
|
175
|
-
// -> plugin.afterResponse), so a dispatch route is finalized exactly as
|
|
176
|
-
// before. Registering it only here — never also on the gateway — is what
|
|
177
|
-
// keeps a matched route from finalizing twice.
|
|
178
|
-
// `mountRoot` receives a fully-widened `Elysia<any, ...>`, whose overloaded
|
|
179
|
-
// `afterResponse` signature is not structurally assignable to the minimal
|
|
180
|
-
// `AfterResponseApp` hook interface even though the runtime shape matches
|
|
181
|
-
// (Elysia passes `{ request }`). The cast is confined to this one call.
|
|
182
|
-
const rootApp = app as unknown as AfterResponseApp;
|
|
183
|
-
if (context.telemetry) {
|
|
184
|
-
registerTelemetryLifecycle(rootApp, {
|
|
185
|
-
stateStore: context.stateStore,
|
|
186
|
-
telemetryBuffer: context.telemetry,
|
|
187
|
-
// One capture for the process, shared with every request state so an
|
|
188
|
-
// early rejection can record its payload from the finalize hook. It
|
|
189
|
-
// resolves the tenant's retention itself because an early rejection has
|
|
190
|
-
// no preference memo on its state — nothing resolved one before the
|
|
191
|
-
// request was refused.
|
|
192
|
-
payloadCapture: new TelemetryPayloadCapture(context.db, (tenantId) =>
|
|
193
|
-
resolvePayloadCapture(context.db, tenantId).then(
|
|
194
|
-
(resolved) => (resolved.ok ? resolved.retentionMs : payloadRetentionMs()),
|
|
195
|
-
),
|
|
196
|
-
),
|
|
197
|
-
// The early-rejection capture resolves the tenant's capture mode
|
|
198
|
-
// through the same preference path dispatched requests use, instead of
|
|
199
|
-
// capturing bodies under the deployment default regardless of the
|
|
200
|
-
// tenant's none/metadata choice.
|
|
201
|
-
db: context.db,
|
|
202
|
-
});
|
|
203
|
-
} else {
|
|
204
|
-
registerRequestCleanup(rootApp, { stateStore: context.stateStore });
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
const createGateway = (
|
|
208
|
-
registerRoutes: (routes: Elysia<any, any, any, any, any, any, any, any>) => void,
|
|
209
|
-
): Elysia<any, any, any, any, any, any, any, any> => {
|
|
210
|
-
const gateway = new Elysia({ prefix: "/v1" });
|
|
211
|
-
for (const plugin of stages) gateway.use(plugin);
|
|
212
|
-
// No telemetry/cleanup registration here: the lifecycle is owned by
|
|
213
|
-
// `mountRoot`. Registering a second copy on this plugin would finalize a
|
|
214
|
-
// matched request twice, and relying on this copy alone would leak every
|
|
215
|
-
// unmatched `/v1/*` request (see `mountRoot`).
|
|
216
|
-
registerRoutes(gateway);
|
|
217
|
-
return gateway;
|
|
218
|
-
};
|
|
219
|
-
return { mountRoot, createGateway };
|
|
220
|
-
}
|
|
1
|
+
import { Elysia } from "elysia";
|
|
2
|
+
import { createIngressPolicyMiddleware } from "./body-policy";
|
|
3
|
+
import {
|
|
4
|
+
createCanonicalRequestMiddleware,
|
|
5
|
+
createClientIdentityMiddleware,
|
|
6
|
+
createProxyRoutePreparationMiddleware,
|
|
7
|
+
createRequestContextMiddleware,
|
|
8
|
+
} from "./request-context";
|
|
9
|
+
import type { CanonicalAdapter, ShutdownDrainSource } from "../contracts";
|
|
10
|
+
import { createApiKeyAuthenticationMiddleware } from "./auth-guard";
|
|
11
|
+
import { createDependencyReadinessMiddleware } from "./readiness-guard";
|
|
12
|
+
import { createIpAbuseProtectionMiddleware } from "./abuse-guard";
|
|
13
|
+
import { createHeadroomGuard } from "./headroom-guard";
|
|
14
|
+
import {
|
|
15
|
+
createErrorNormalizationMiddleware,
|
|
16
|
+
registerRequestCleanup,
|
|
17
|
+
registerTelemetryLifecycle,
|
|
18
|
+
type AfterResponseApp,
|
|
19
|
+
} from "./error-lifecycle";
|
|
20
|
+
import type { ReadinessCheckResult } from "../../persistence/readiness";
|
|
21
|
+
import type { IpAbuseProtectionService } from "../../security/abuse";
|
|
22
|
+
import type { ProxyRequestPreparer } from "../request/preparer";
|
|
23
|
+
import { ProxyRequestStateStore } from "../request/state";
|
|
24
|
+
import type { SurfaceAdapterRegistry } from "../surface/adapters";
|
|
25
|
+
import type { TrustedProxyBoundary } from "../../config/network";
|
|
26
|
+
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
27
|
+
import type { RedisClient } from "../../persistence/redis";
|
|
28
|
+
import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer";
|
|
29
|
+
import type { RuntimeMemorySnapshot } from "../../observability/runtime-metrics";
|
|
30
|
+
import {
|
|
31
|
+
TelemetryPayloadCapture,
|
|
32
|
+
payloadCaptureKeepsBodies,
|
|
33
|
+
payloadRetentionMs,
|
|
34
|
+
resolvePayloadCapture,
|
|
35
|
+
} from "../../observability/payload-capture";
|
|
36
|
+
|
|
37
|
+
/** Shared state/context carried by every ordered transport stage. */
|
|
38
|
+
export interface TransportPipelineContext {
|
|
39
|
+
readonly db: CartethyiaDatabase;
|
|
40
|
+
readonly stateStore: ProxyRequestStateStore;
|
|
41
|
+
readonly surfaceRegistry: SurfaceAdapterRegistry;
|
|
42
|
+
readonly adapters: ReadonlyMap<string, CanonicalAdapter>;
|
|
43
|
+
readonly preparer: ProxyRequestPreparer;
|
|
44
|
+
readonly readiness: () => Promise<ReadinessCheckResult>;
|
|
45
|
+
readonly trustedProxyBoundary: TrustedProxyBoundary;
|
|
46
|
+
readonly resolvePeerAddress?: (request: Request) => string | null;
|
|
47
|
+
readonly ipAbuseProtection?: IpAbuseProtectionService;
|
|
48
|
+
readonly maxBodyBytes?: number;
|
|
49
|
+
readonly requestDeadlineMs?: number;
|
|
50
|
+
readonly verifiedHttps?: boolean;
|
|
51
|
+
/** Graduated strikes for repeated invalid-model requests. */
|
|
52
|
+
readonly shutdownCoordinator?: ShutdownDrainSource;
|
|
53
|
+
readonly telemetry?: TelemetryBatchBuffer;
|
|
54
|
+
/**
|
|
55
|
+
* Shared client for the headroom guard's shed marker. Optional: the shed
|
|
56
|
+
* decision is local, and Redis is only written for fleet visibility.
|
|
57
|
+
*/
|
|
58
|
+
readonly redis?: RedisClient | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* The sampled memory state the headroom guard sheds against.
|
|
61
|
+
*
|
|
62
|
+
* Supplied by the runtime sampler. When absent the guard cannot compare RSS
|
|
63
|
+
* against a limit and does not shed — so this must be wired in production,
|
|
64
|
+
* and the sampler must be initialized at boot even before its first tick.
|
|
65
|
+
*/
|
|
66
|
+
readonly memorySnapshot?: () => RuntimeMemorySnapshot | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Whether invoke is switched off for a tenant. Optional: without it the
|
|
69
|
+
* gateway never refuses on the kill switch, which is the pre-change
|
|
70
|
+
* behaviour rather than a failure.
|
|
71
|
+
*/
|
|
72
|
+
readonly isInvokeDisabled?: (tenantId: string) => Promise<boolean | undefined>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The single ingress owner. `mountRoot` applies the process-wide request
|
|
77
|
+
* context and error-normalization lifecycle; `createGateway` composes the
|
|
78
|
+
* ordered fail-closed `/v1/*` stage chain plus the telemetry/cleanup
|
|
79
|
+
* lifecycle around caller-registered gateway routes. Composition code
|
|
80
|
+
* (`app.ts`) mounts — it never assembles middleware policy itself.
|
|
81
|
+
*/
|
|
82
|
+
export interface TransportPipeline {
|
|
83
|
+
/** Applies root-scoped request context and error normalization. */
|
|
84
|
+
mountRoot(app: Elysia<any, any, any, any, any, any, any, any>): void;
|
|
85
|
+
/** Builds the complete `/v1/*` gateway plugin around registered routes. */
|
|
86
|
+
createGateway(registerRoutes: (routes: Elysia<any, any, any, any, any, any, any, any>) => void): Elysia;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Builds the one ordered middleware pipeline. Request context and error
|
|
91
|
+
* normalization are mounted at the root; the remaining stages are mounted
|
|
92
|
+
* before every `/v1/*` route, preserving fail-closed ordering.
|
|
93
|
+
*/
|
|
94
|
+
export function createTransportPipeline(context: TransportPipelineContext): TransportPipeline {
|
|
95
|
+
const stages: Elysia[] = [
|
|
96
|
+
createHeadroomGuard({
|
|
97
|
+
store: context.stateStore,
|
|
98
|
+
...(context.memorySnapshot === undefined ? {} : { memorySnapshot: context.memorySnapshot }),
|
|
99
|
+
...(context.redis === undefined ? {} : { redis: context.redis }),
|
|
100
|
+
}),
|
|
101
|
+
createDependencyReadinessMiddleware({
|
|
102
|
+
readiness: context.readiness,
|
|
103
|
+
...(context.shutdownCoordinator ? { shutdownCoordinator: context.shutdownCoordinator } : {}),
|
|
104
|
+
}),
|
|
105
|
+
createIngressPolicyMiddleware({
|
|
106
|
+
stateStore: context.stateStore,
|
|
107
|
+
...(context.maxBodyBytes === undefined ? {} : { maxBodyBytes: context.maxBodyBytes }),
|
|
108
|
+
}),
|
|
109
|
+
createClientIdentityMiddleware({
|
|
110
|
+
stateStore: context.stateStore,
|
|
111
|
+
trustedProxyBoundary: context.trustedProxyBoundary,
|
|
112
|
+
...(context.resolvePeerAddress ? { resolvePeerAddress: context.resolvePeerAddress } : {}),
|
|
113
|
+
}),
|
|
114
|
+
createApiKeyAuthenticationMiddleware({
|
|
115
|
+
db: context.db,
|
|
116
|
+
stateStore: context.stateStore,
|
|
117
|
+
...(context.isInvokeDisabled
|
|
118
|
+
? { isInvokeDisabled: context.isInvokeDisabled }
|
|
119
|
+
: {}),
|
|
120
|
+
}),
|
|
121
|
+
createCanonicalRequestMiddleware({
|
|
122
|
+
stateStore: context.stateStore,
|
|
123
|
+
surfaceRegistry: context.surfaceRegistry,
|
|
124
|
+
adapters: context.adapters,
|
|
125
|
+
keepsIngressBody: (tenantId) => payloadCaptureKeepsBodies(context.db, tenantId),
|
|
126
|
+
}),
|
|
127
|
+
createProxyRoutePreparationMiddleware({
|
|
128
|
+
stateStore: context.stateStore,
|
|
129
|
+
preparer: context.preparer,
|
|
130
|
+
}),
|
|
131
|
+
];
|
|
132
|
+
const mountRoot = (app: Elysia<any, any, any, any, any, any, any, any>): void => {
|
|
133
|
+
app.use(
|
|
134
|
+
createRequestContextMiddleware({
|
|
135
|
+
stateStore: context.stateStore,
|
|
136
|
+
...(context.requestDeadlineMs === undefined
|
|
137
|
+
? {}
|
|
138
|
+
: { requestDeadlineMs: context.requestDeadlineMs }),
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
// Root-mounted, not a gateway stage: its `request` hook must run for every
|
|
142
|
+
// `/v1/*` request, including paths that match no route, or a caller can
|
|
143
|
+
// hammer the gateway without ever being counted. See the middleware's own
|
|
144
|
+
// doc for the measured bypass this closes.
|
|
145
|
+
if (context.ipAbuseProtection) {
|
|
146
|
+
app.use(
|
|
147
|
+
createIpAbuseProtectionMiddleware({
|
|
148
|
+
stateStore: context.stateStore,
|
|
149
|
+
ipAbuseProtection: context.ipAbuseProtection,
|
|
150
|
+
trustedProxyBoundary: context.trustedProxyBoundary,
|
|
151
|
+
...(context.resolvePeerAddress ? { resolvePeerAddress: context.resolvePeerAddress } : {}),
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
app.use(
|
|
156
|
+
createErrorNormalizationMiddleware({
|
|
157
|
+
stateStore: context.stateStore,
|
|
158
|
+
hsts: context.verifiedHttps === true,
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
// Finalize + cleanup live at the ROOT, not on the gateway plugin.
|
|
162
|
+
//
|
|
163
|
+
// A plugin-scoped `afterResponse` only fires for a request that matched a
|
|
164
|
+
// registered route, so an unregistered `/v1/*` path — the cheapest thing a
|
|
165
|
+
// scanner can send — was admitted by the root `request` hook (which does
|
|
166
|
+
// run for every inbound request, that is why the in-flight count and the
|
|
167
|
+
// IP-abuse counter are there) but never cleaned up. Measured: one
|
|
168
|
+
// unmatched `/v1/*` request left `proxy_in_flight` permanently higher and
|
|
169
|
+
// `activeCount()` permanently one larger, so the gauge only ever climbed
|
|
170
|
+
// for the life of the process. The root hook runs for the unmatched path
|
|
171
|
+
// too, which is what makes the increment and the decrement symmetric.
|
|
172
|
+
//
|
|
173
|
+
// Ordering is safe because a root `afterResponse` still runs *after* the
|
|
174
|
+
// matched handler returns (measured: request -> handler -> root.afterResponse
|
|
175
|
+
// -> plugin.afterResponse), so a dispatch route is finalized exactly as
|
|
176
|
+
// before. Registering it only here — never also on the gateway — is what
|
|
177
|
+
// keeps a matched route from finalizing twice.
|
|
178
|
+
// `mountRoot` receives a fully-widened `Elysia<any, ...>`, whose overloaded
|
|
179
|
+
// `afterResponse` signature is not structurally assignable to the minimal
|
|
180
|
+
// `AfterResponseApp` hook interface even though the runtime shape matches
|
|
181
|
+
// (Elysia passes `{ request }`). The cast is confined to this one call.
|
|
182
|
+
const rootApp = app as unknown as AfterResponseApp;
|
|
183
|
+
if (context.telemetry) {
|
|
184
|
+
registerTelemetryLifecycle(rootApp, {
|
|
185
|
+
stateStore: context.stateStore,
|
|
186
|
+
telemetryBuffer: context.telemetry,
|
|
187
|
+
// One capture for the process, shared with every request state so an
|
|
188
|
+
// early rejection can record its payload from the finalize hook. It
|
|
189
|
+
// resolves the tenant's retention itself because an early rejection has
|
|
190
|
+
// no preference memo on its state — nothing resolved one before the
|
|
191
|
+
// request was refused.
|
|
192
|
+
payloadCapture: new TelemetryPayloadCapture(context.db, (tenantId) =>
|
|
193
|
+
resolvePayloadCapture(context.db, tenantId).then(
|
|
194
|
+
(resolved) => (resolved.ok ? resolved.retentionMs : payloadRetentionMs()),
|
|
195
|
+
),
|
|
196
|
+
),
|
|
197
|
+
// The early-rejection capture resolves the tenant's capture mode
|
|
198
|
+
// through the same preference path dispatched requests use, instead of
|
|
199
|
+
// capturing bodies under the deployment default regardless of the
|
|
200
|
+
// tenant's none/metadata choice.
|
|
201
|
+
db: context.db,
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
registerRequestCleanup(rootApp, { stateStore: context.stateStore });
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const createGateway = (
|
|
208
|
+
registerRoutes: (routes: Elysia<any, any, any, any, any, any, any, any>) => void,
|
|
209
|
+
): Elysia<any, any, any, any, any, any, any, any> => {
|
|
210
|
+
const gateway = new Elysia({ prefix: "/v1" });
|
|
211
|
+
for (const plugin of stages) gateway.use(plugin);
|
|
212
|
+
// No telemetry/cleanup registration here: the lifecycle is owned by
|
|
213
|
+
// `mountRoot`. Registering a second copy on this plugin would finalize a
|
|
214
|
+
// matched request twice, and relying on this copy alone would leak every
|
|
215
|
+
// unmatched `/v1/*` request (see `mountRoot`).
|
|
216
|
+
registerRoutes(gateway);
|
|
217
|
+
return gateway;
|
|
218
|
+
};
|
|
219
|
+
return { mountRoot, createGateway };
|
|
220
|
+
}
|