cartethyia 2.7.8 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -3
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js +1 -0
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-CXsaOAO2.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js +1 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-CgoPc5B9.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-D9bFQern.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js +1 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BwNC_-Cx.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-febLU4r1.js} +1 -1
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-M6fvR5Wz.js} +1 -1
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js +56 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js +1 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
- package/dist/dashboard/assets/{assert-CidQVoNo.js → assert-CqTLw5e7.js} +1 -1
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-C83EqLB0.js} +1 -1
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
- package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-D1xn7pIe.js} +1 -1
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/favicon.webp +0 -0
- package/dist/dashboard/assets/brand/login_register.webp +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
- package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/og_image.webp +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
- package/dist/dashboard/assets/{card-_HwAEqFF.js → card-DU3Lq5Lm.js} +1 -1
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js +1 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CEL5Sg_J.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-BjFR5UHy.js} +1 -1
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-HvBW0AjI.js} +1 -1
- package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
- package/dist/dashboard/assets/{entry-COaC-irN.js → entry-CECVELyy.js} +1 -1
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js +1 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-DLW4i8zK.js} +1 -1
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/{index-CgKmFngv.js → index-1-fCqe64.js} +2 -2
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2BirKry.js → inline-DGvU2oLK.js} +1 -1
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/{input-D-qV9dKY.js → input-iNnO1X2K.js} +1 -1
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-0q8UGypb.js} +1 -1
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-C0w2bQjJ.js} +1 -1
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/{live-DGizF06-.js → live-M5aJ_xCa.js} +1 -1
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
- package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
- package/dist/dashboard/assets/{network-C9UE-bOr.js → network-BKeQGT3K.js} +1 -1
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-CivAk3da.js} +1 -1
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-CpzvI5ep.js} +1 -1
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
- package/dist/dashboard/assets/providers/devin.svg +9 -0
- package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
- package/dist/dashboard/assets/providers/windsurf.svg +10 -0
- package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
- package/dist/dashboard/assets/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
- package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js +1 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/{select-BCOcv8WK.js → select-CXbck0jG.js} +2 -2
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js +1 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DggRiCxE.js} +1 -1
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/{state-DXbGTx7N.js → state-ClFzfbYp.js} +1 -1
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/story/25817331.webp +0 -0
- package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
- package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
- package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
- package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
- package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
- package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
- package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
- package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-CTIFxKqS.js} +1 -1
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BClG3yWj.js} +1 -1
- package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.webp +0 -0
- package/dist/dashboard/console-backgrounds_initial.webp +0 -0
- package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +59 -56
- package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
- package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
- package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/package.json +1 -1
- package/src/boot/app.ts +10 -13
- package/src/boot/dependencies.ts +12 -4
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/cooldowns.ts +1 -1
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +20 -6
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +50 -31
- package/src/console/auth/account-reset.ts +7 -9
- package/src/console/backup/contracts.ts +4 -0
- package/src/console/backup/export-endpoint.ts +7 -5
- package/src/console/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -2
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +93 -0
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -0
- package/src/console/providers/catalog/discover-constants.ts +6 -0
- package/src/console/providers/catalog/model-operations.ts +122 -10
- package/src/console/providers/catalog/provider-operations.ts +116 -8
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +65 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +249 -4
- package/src/console/quota/account-quota-global.ts +97 -19
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +6 -18
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/operations.ts +31 -46
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/shared/errors.ts +33 -6
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +3 -3
- package/src/network/pool/byte-accounting.ts +3 -2
- package/src/network/pool/selector.ts +182 -39
- package/src/network/pool-cooldown-store.ts +89 -14
- package/src/network/pool-health.ts +49 -61
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/ssrf.ts +6 -6
- package/src/observability/event-log.ts +7 -361
- package/src/observability/event-query.ts +18 -4
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +15 -25
- package/src/observability/telemetry-reconciliation.ts +9 -37
- package/src/observability/telemetry-row.ts +125 -0
- package/src/persistence/db-mode.ts +4 -1
- package/src/persistence/dialect.ts +30 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +28 -2
- package/src/persistence/ttl-cache.ts +20 -2
- package/src/protocol/codecs.ts +1 -1
- package/src/protocol/dsml.ts +35 -87
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +14 -14
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +4 -5
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/stream-error-frames.ts +13 -3
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/protocol/translation/quirks.ts +1 -1
- package/src/protocol/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +15 -4
- package/src/protocol/upstream-parsing.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +78 -17
- package/src/providers/authentication/oauth-client.ts +1 -1
- package/src/providers/authentication/oauth-device-flow.ts +98 -8
- package/src/providers/authentication/oauth-refresh-service.ts +153 -59
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +117 -27
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +74 -33
- package/src/providers/discovery/probing-service.ts +293 -165
- package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
- package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +121 -2
- package/src/providers/integrations/antigravity/antigravity.ts +13 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +191 -35
- package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
- package/src/providers/integrations/buddy/buddy-identity.ts +89 -6
- package/src/providers/integrations/buddy/buddy-media.ts +4 -5
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
- package/src/providers/integrations/buddy/capabilities.ts +6 -43
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +23 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +38 -27
- package/src/providers/integrations/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline.ts +2 -2
- package/src/providers/integrations/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
- package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +745 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +35 -20
- package/src/providers/integrations/cursor/cursor-shared.ts +26 -5
- package/src/providers/integrations/cursor/cursor.ts +139 -70
- package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
- package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
- package/src/providers/integrations/devin/devin-auth.ts +353 -0
- package/src/providers/integrations/devin/devin-catalog.ts +207 -0
- package/src/providers/integrations/devin/devin-http.ts +91 -0
- package/src/providers/integrations/devin/devin-quota.ts +214 -0
- package/src/providers/integrations/devin/devin-shared.ts +111 -0
- package/src/providers/integrations/devin/devin.ts +539 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +558 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory-oauth.ts +17 -44
- package/src/providers/integrations/factory/factory.ts +2 -2
- package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
- package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
- package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
- package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
- package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
- package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
- package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
- package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
- package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
- package/src/providers/integrations/freebuff/freebuff.ts +682 -0
- package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
- package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
- package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
- package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
- package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
- package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
- package/src/providers/integrations/freepi/freepi.ts +292 -0
- package/src/providers/integrations/gemini.ts +2 -2
- package/src/providers/integrations/grok/grok-oauth.ts +48 -45
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
- package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
- package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +13 -21
- package/src/providers/integrations/hermes/index.ts +2 -0
- package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
- package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
- package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- package/src/providers/integrations/ollamacloud/index.ts +2 -0
- package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
- package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +208 -0
- package/src/providers/integrations/perch/perch-quota.ts +123 -0
- package/src/providers/integrations/perch/perch-realm.ts +82 -0
- package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
- package/src/providers/integrations/perch/perch.ts +305 -0
- package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
- package/src/providers/integrations/qoder/chat-proto.ts +328 -328
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +90 -40
- package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +26 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
- package/src/providers/integrations/qoder/qoder-checkin.ts +6 -6
- package/src/providers/integrations/qoder/qoder-grpc.ts +210 -7
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -108
- package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
- package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
- package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
- package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
- package/src/providers/integrations/qoder/qoder.ts +3 -7
- package/src/providers/integrations/trae/trae-catalog.ts +9 -14
- package/src/providers/integrations/trae/trae-headers.ts +36 -11
- package/src/providers/integrations/trae/trae-oauth.ts +14 -17
- package/src/providers/integrations/trae/trae-protocol.ts +12 -11
- package/src/providers/integrations/trae/trae-quota.ts +96 -36
- package/src/providers/integrations/trae/trae-realm.ts +74 -15
- package/src/providers/integrations/trae/trae.ts +99 -26
- package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
- package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
- package/src/providers/integrations/zro/zro.ts +204 -0
- package/src/providers/operations/account-error-classification.ts +235 -278
- package/src/providers/operations/account-health-service.ts +273 -26
- package/src/providers/operations/account-refresh-status.ts +269 -0
- package/src/providers/operations/account-tier.ts +66 -0
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +42 -150
- package/src/providers/operations/cooldown-rules.ts +56 -0
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +102 -32
- package/src/providers/operations/provider-rules/github.ts +48 -0
- package/src/providers/operations/provider-rules/grok.ts +33 -0
- package/src/providers/operations/provider-rules/inception.ts +49 -0
- package/src/providers/operations/provider-rules/index.ts +32 -0
- package/src/providers/operations/provider-rules/perch.ts +37 -0
- package/src/providers/operations/provider-rules/qoder.ts +70 -0
- package/src/providers/operations/provider-rules/trae.ts +81 -0
- package/src/providers/provider-contracts.ts +33 -2
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +99 -14
- package/src/providers/provider-model-definition.ts +36 -0
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- package/src/providers/quota/quota-limit-windows.ts +16 -5
- package/src/providers/quota/quota-support.ts +1 -1
- package/src/providers/quota/quota-window-parser.ts +90 -18
- package/src/providers/quota/refresh.ts +96 -18
- package/src/security/abuse.ts +71 -0
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/transport/dispatch/attempt-finalize.ts +14 -13
- package/src/transport/dispatch/attempt-loop.ts +71 -17
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +9 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/streaming/finalize.ts +9 -6
- package/src/transport/dispatch/streaming/stream.ts +9 -36
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +67 -1
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +129 -37
- package/src/transport/guards/body-policy.ts +7 -1
- package/src/transport/guards/error-lifecycle.ts +13 -1
- package/src/transport/guards/request-context.ts +22 -14
- package/src/transport/request/preparer.ts +81 -71
- package/src/transport/routing/bounded-lru.ts +14 -0
- package/src/transport/routing/drain-reserve.ts +129 -0
- package/src/transport/routing/plan-gate.ts +7 -1
- package/src/transport/routing/round-robin.ts +60 -0
- package/src/transport/routing/route-catalog.ts +123 -111
- package/src/transport/routing/route-model.ts +51 -9
- package/src/transport/routing/router.ts +418 -33
- package/src/transport/surface/adapters.ts +11 -2
- package/src/transport/surface/chat/adapter.ts +2 -2
- package/src/transport/surface/chat/encode.ts +47 -4
- package/src/transport/surface/chat/parse.ts +2 -12
- package/src/transport/surface/completion.ts +44 -33
- package/src/transport/surface/content-parts.ts +21 -0
- package/src/transport/surface/messages/adapter.ts +9 -2
- package/src/transport/surface/messages/encode.ts +11 -3
- package/src/transport/surface/messages/parse.ts +2 -1
- package/src/transport/surface/messages/stream.ts +33 -17
- package/src/transport/surface/responses/encode.ts +34 -6
- package/src/transport/surface/responses/parse.ts +3 -11
- package/src/workers/daily-checkin.ts +1 -1
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/scheduler.ts +22 -4
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js +0 -1
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
- package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
- package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.png +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/favicon.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
- package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
- package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
- package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
- package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
- package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
- package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
- package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
- package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
- package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
- package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
- package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
- package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
- package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
- package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
- package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
- package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
- package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
- package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
- package/src/providers/operations/model-discovery-cache.ts +0 -34
- package/src/providers/operations/probe-fetch.ts +0 -15
- package/src/providers/operations/provider-version-cache.ts +0 -30
- package/src/providers/provider-capabilities.ts +0 -33
- package/src/providers/provider-credential-envelope.ts +0 -43
- /package/dist/dashboard/assets/{app-CTxoqyYB.js → app-DIXLjojg.js} +0 -0
- /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-DIXLjojg.js.gz} +0 -0
package/src/boot/app.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { chatAdapter } from "../transport/surface/chat/adapter";
|
|
|
10
10
|
import { responsesAdapter } from "../transport/surface/responses/adapter";
|
|
11
11
|
import { messagesAdapter } from "../transport/surface/messages/adapter";
|
|
12
12
|
import { completionAdapter } from "../transport/surface/completion";
|
|
13
|
-
import { SurfaceAdapterRegistry } from "../transport/surface/adapters";
|
|
13
|
+
import { SurfaceAdapterRegistry, type SurfaceAdapter } from "../transport/surface/adapters";
|
|
14
14
|
import { GatewayError, publicGatewayErrorBody } from "../protocol/gateway-error";
|
|
15
15
|
import { shutdownError, shutdownNotice } from "../transport/shutdown-notice";
|
|
16
16
|
import type { CanonicalAdapter } from "../transport/contracts";
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
import { createTokenCountHandler } from "../transport/dispatch/token-count";
|
|
48
48
|
|
|
49
49
|
import { createTransportPipeline } from "../transport/guards/pipeline";
|
|
50
|
-
import { createDrainHandler,
|
|
50
|
+
import { createDrainHandler, requireLoopbackAdmin } from "../transport/drain-endpoint";
|
|
51
51
|
import { createConsoleAccountResetHandler } from "../console/auth/account-reset";
|
|
52
52
|
import { resetConsoleAuthState } from "../console/auth/account-reset-store";
|
|
53
53
|
import { createBackupExportHandler } from "../console/backup/export-endpoint";
|
|
@@ -210,7 +210,7 @@ export function createGatewayApp(deps: GatewayAppDeps) {
|
|
|
210
210
|
deps.mode === "route-only"
|
|
211
211
|
? (deps.checkRedis ?? checkRedisConnection)
|
|
212
212
|
: checkRedisConnection;
|
|
213
|
-
const registry = new SurfaceAdapterRegistry([
|
|
213
|
+
const registry = new SurfaceAdapterRegistry<SurfaceAdapter & CanonicalAdapter>([
|
|
214
214
|
chatAdapter,
|
|
215
215
|
responsesAdapter,
|
|
216
216
|
messagesAdapter,
|
|
@@ -424,12 +424,7 @@ export function createGatewayApp(deps: GatewayAppDeps) {
|
|
|
424
424
|
db: deps.db,
|
|
425
425
|
stateStore: requestStateStore,
|
|
426
426
|
surfaceRegistry: registry,
|
|
427
|
-
adapters: new Map<string, CanonicalAdapter>(
|
|
428
|
-
["chat", chatAdapter],
|
|
429
|
-
["responses", responsesAdapter],
|
|
430
|
-
["messages", messagesAdapter],
|
|
431
|
-
["completion", completionAdapter],
|
|
432
|
-
]),
|
|
427
|
+
adapters: new Map<string, CanonicalAdapter>(registry.bySurface()),
|
|
433
428
|
preparer: deps.proxyPreparer,
|
|
434
429
|
readiness: deps.readiness,
|
|
435
430
|
trustedProxyBoundary: deps.trustedProxyBoundary,
|
|
@@ -652,10 +647,12 @@ export function createGatewayApp(deps: GatewayAppDeps) {
|
|
|
652
647
|
// well-formed. What refused it is where it came from — the client asked
|
|
653
648
|
// for something only the local host may ask. `invalid_request` told an
|
|
654
649
|
// operator to fix the body, when the body was never the problem.
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
650
|
+
requireLoopbackAdmin(request, {
|
|
651
|
+
peer: peerAddresses.get(request) ?? null,
|
|
652
|
+
token,
|
|
653
|
+
policyRejectedMessage: "task trigger is only available from loopback (request {requestId}). This operation is only allowed from a trusted loopback connection — no hard feelings 🫂",
|
|
654
|
+
authenticationFailedMessage: "invalid drain token (request {requestId}). Wrong key, right door — check the token 🔑",
|
|
655
|
+
});
|
|
659
656
|
const name = params.name;
|
|
660
657
|
if (!tasks.names().includes(name))
|
|
661
658
|
throw new GatewayError("not_found", 404, `no such task: ${name} (request {requestId}). Nothing here but dust — check the task name 🌙`);
|
package/src/boot/dependencies.ts
CHANGED
|
@@ -39,7 +39,7 @@ import { setPendingUsageSource } from "../security/api-key-auth";
|
|
|
39
39
|
import { HealthEventFlushBuffer, setHealthEventBuffer } from "../persistence/health-event-flush";
|
|
40
40
|
import { subscribePeerSettingsRevisions } from "../persistence/settings-revision";
|
|
41
41
|
import { deleteExpiredSessions } from "../console/auth/session-service";
|
|
42
|
-
import { InMemoryIpAbuseStore, IpAbuseProtectionService, RedisIpAbuseStore } from "../security/abuse";
|
|
42
|
+
import { InMemoryIpAbuseStore, IpAbuseProtectionService, RedisIpAbuseStore, type IpAbuseStore } from "../security/abuse";
|
|
43
43
|
import { checkReadiness } from "../persistence/readiness";
|
|
44
44
|
import { ProxyRequestPreparer } from "../transport/request/preparer";
|
|
45
45
|
import { DrizzleNetworkPoolLoader } from "../network/pool/loader";
|
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
} from "../workers/buddy-growth-tasks";
|
|
63
63
|
import { createAccountSecretResolver } from "../providers/operations/provider-credential-service";
|
|
64
64
|
import { quotaCacheSize } from "../providers/quota/cache";
|
|
65
|
-
import { modelCatalogCacheSize } from "../providers/operations/
|
|
65
|
+
import { modelCatalogCacheSize } from "../providers/operations/provider-caches";
|
|
66
66
|
import { preferencesReaderFor } from "../persistence/tenant-preferences";
|
|
67
67
|
import { sweepExpiredCooldowns } from "../providers/operations/account-health-service";
|
|
68
68
|
import { DrizzleTelemetryStore } from "../persistence/stores/telemetry-store";
|
|
@@ -104,6 +104,8 @@ export interface ProductionDeps {
|
|
|
104
104
|
proxyPreparer: ProxyRequestPreparer;
|
|
105
105
|
networkBindingFactory: ValidatedNetworkBindingFactory;
|
|
106
106
|
ipAbuseProtection: IpAbuseProtectionService;
|
|
107
|
+
/** The abuse store backing manual IP bans and the ban list. */
|
|
108
|
+
ipAbuseStore: IpAbuseStore;
|
|
107
109
|
readiness: () => Promise<ReadinessCheckResult>;
|
|
108
110
|
scheduledTasks: ScheduledTaskRegistry;
|
|
109
111
|
poolAgentResolver: PoolAgentResolver;
|
|
@@ -593,6 +595,11 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
593
595
|
name: "client-version-monitor",
|
|
594
596
|
intervalMs: CLIENT_VERSION_MONITOR_INTERVAL_MS,
|
|
595
597
|
initialDelayMs: CLIENT_VERSION_INITIAL_DELAY_MS,
|
|
598
|
+
// Pure outbound reads with no shared rows/files: they must not be gated
|
|
599
|
+
// by the process-wide busy latch, or a nearly-continuous heavy sweep
|
|
600
|
+
// (quota-refresh) starves this monitor at its single initial tick and
|
|
601
|
+
// every 30-minute interval after it.
|
|
602
|
+
skipGate: true,
|
|
596
603
|
run: refreshProviderClientVersions,
|
|
597
604
|
});
|
|
598
605
|
|
|
@@ -606,13 +613,13 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
606
613
|
// first open after an expiry always blocks on the provider.
|
|
607
614
|
//
|
|
608
615
|
// It also carries the daily check-in ride-along: each account refreshed
|
|
609
|
-
// here gets its once-per-day
|
|
616
|
+
// here gets its once-per-day CodeBuddy credit grant claimed in
|
|
610
617
|
// the same pass (one attempt per account per day, guarded by a day
|
|
611
618
|
// marker), so no second timer duplicates the credential resolution.
|
|
612
619
|
//
|
|
613
620
|
// Registered in **every** mode, not only when Redis is present: gating it on
|
|
614
621
|
// Redis would cost Lite proactive quota warming and the check-in ride-along
|
|
615
|
-
// above that claims the once-per-day
|
|
622
|
+
// above that claims the once-per-day CodeBuddy credit grant. The
|
|
616
623
|
// whole sweep chain accepts `RedisClient | undefined` and falls back to the
|
|
617
624
|
// in-process TTL cache and an in-process day marker, so Redis is an
|
|
618
625
|
// optimization here rather than a precondition — which is exactly what
|
|
@@ -739,6 +746,7 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
739
746
|
}),
|
|
740
747
|
networkBindingFactory,
|
|
741
748
|
ipAbuseProtection,
|
|
749
|
+
ipAbuseStore: ipStore,
|
|
742
750
|
readiness,
|
|
743
751
|
scheduledTasks,
|
|
744
752
|
poolAgentResolver,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Shared drain sequence for the process's queued writers. The console's manual
|
|
2
|
+
// "flush now" and the shutdown coordinator's telemetry flush must drain exactly
|
|
3
|
+
// the same buffers in the same order — a fourth writer added to one path but
|
|
4
|
+
// not the other would silently stop reaching the disk on one of them.
|
|
5
|
+
//
|
|
6
|
+
// The deps are the minimal write surface each buffer exposes: a `flush()` that
|
|
7
|
+
// resolves when the buffer is drained. The concrete buffers satisfy it, and a
|
|
8
|
+
// test can substitute stubs without casting.
|
|
9
|
+
export interface FlushAllBuffersDeps {
|
|
10
|
+
readonly telemetryBuffer: { flush(): Promise<void> };
|
|
11
|
+
readonly usageFlush: { flush(): Promise<void> };
|
|
12
|
+
readonly healthEventFlush: { flush(): Promise<void> };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Drains every buffered writer, in the canonical order. */
|
|
16
|
+
export async function flushAllBuffers(deps: FlushAllBuffersDeps): Promise<void> {
|
|
17
|
+
await deps.telemetryBuffer.flush();
|
|
18
|
+
await deps.usageFlush.flush();
|
|
19
|
+
await deps.healthEventFlush.flush();
|
|
20
|
+
}
|
package/src/boot/lifecycle.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { closeDb } from "../persistence/boot";
|
|
|
2
2
|
import { closeRedis } from "../persistence/redis";
|
|
3
3
|
import { buildProductionDeps } from "./dependencies";
|
|
4
4
|
import type { ProductionDeps } from "./dependencies";
|
|
5
|
+
import { flushAllBuffers } from "./flush";
|
|
5
6
|
import { withTimeout } from "./timeout";
|
|
6
7
|
import { resolveShutdownDrainWindowMs } from "../config/runtime";
|
|
7
8
|
import { log } from "../observability/logger";
|
|
@@ -35,8 +36,11 @@ export class ShutdownCoordinator {
|
|
|
35
36
|
) {
|
|
36
37
|
this.hooks = hooks ?? {};
|
|
37
38
|
this.drainWindowMs = opts?.drainWindowMs ?? 20_000;
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
// Defaults mirror what `bootstrap()` passes (8_000 / 1_000; drainWindowMs
|
|
40
|
+
// 20_000 is the spec default `resolveShutdownDrainWindowMs()` returns), so
|
|
41
|
+
// reasoning about drain timing from the class alone matches production.
|
|
42
|
+
this.drainTimeoutMs = opts?.drainTimeoutMs ?? 8_000;
|
|
43
|
+
this.flushTimeoutMs = opts?.flushTimeoutMs ?? 1_000;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
/**
|
|
@@ -217,9 +221,7 @@ export async function bootstrap(): Promise<CartethyiaBoot> {
|
|
|
217
221
|
shutdownCoordinator: new ShutdownCoordinator(
|
|
218
222
|
{
|
|
219
223
|
flushTelemetry: async () => {
|
|
220
|
-
await deps
|
|
221
|
-
await deps.usageFlush.flush();
|
|
222
|
-
await deps.healthEventFlush.flush();
|
|
224
|
+
await flushAllBuffers(deps);
|
|
223
225
|
},
|
|
224
226
|
closePools: async () => {
|
|
225
227
|
await boot.server?.stop();
|
package/src/config/cooldowns.ts
CHANGED
|
@@ -119,7 +119,7 @@ export function resolveAccountAuthRejectedCooldownMs(): number {
|
|
|
119
119
|
* Deliberately a *base*, not the 24h flat penalty it replaced. `11140` has two
|
|
120
120
|
* documented causes our classifier cannot separate from the response alone: the
|
|
121
121
|
* user's prompt failed a safety review, **and** sending the wrong desktop
|
|
122
|
-
* `User-Agent` brand (`
|
|
122
|
+
* `User-Agent` brand (`codebuddy/shared.ts`). A flat 24h meant that after we
|
|
123
123
|
* fixed a header regression every buddy account stayed dead for up to a further
|
|
124
124
|
* day, and one unsafe prompt cost a whole account a day.
|
|
125
125
|
*
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Routing configuration knobs.
|
|
2
|
+
import { CONFIG_SPEC, readInt } from "./spec";
|
|
3
|
+
|
|
4
|
+
/** Routing reserve percent (0–100). Accounts at/below this remaining-quota % are demoted. */
|
|
5
|
+
export function accountReservePct(): number {
|
|
6
|
+
return readInt(
|
|
7
|
+
"CARTETHYIA_ACCOUNT_RESERVE_PCT",
|
|
8
|
+
CONFIG_SPEC.CARTETHYIA_ACCOUNT_RESERVE_PCT,
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Session-sticky warm window in ms. 0 disables stickiness. */
|
|
13
|
+
export function routingStickyWarmMs(): number {
|
|
14
|
+
return readInt(
|
|
15
|
+
"CARTETHYIA_ROUTING_STICKY_WARM_MS",
|
|
16
|
+
CONFIG_SPEC.CARTETHYIA_ROUTING_STICKY_WARM_MS,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** LRU cap on remembered session→account sticky state per provider. */
|
|
21
|
+
export function routingStickyMaxSessions(): number {
|
|
22
|
+
return readInt(
|
|
23
|
+
"CARTETHYIA_ROUTING_STICKY_MAX_SESSIONS",
|
|
24
|
+
CONFIG_SPEC.CARTETHYIA_ROUTING_STICKY_MAX_SESSIONS,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared metadata for config docs and tests.
|
|
3
|
+
*
|
|
4
|
+
* `CONFIG_SPEC` declares the gateway's spec-driven variables. These constants
|
|
5
|
+
* name the deliberate asymmetries around it so `.env.example`, the generated
|
|
6
|
+
* reference, and the drift tests all project the same exception lists.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Keys in `CONFIG_SPEC` that are intentionally absent from `.env.example`. */
|
|
10
|
+
export const SPEC_ONLY_ENV_EXAMPLE_KEYS: Readonly<Record<string, true>> = {
|
|
11
|
+
// Setup wizard writes this directly; a sample would mislead copy/paste.
|
|
12
|
+
CARTETHYIA_API_KEY: true,
|
|
13
|
+
// Opt-in diagnostics; sampling them would nudge operators to enable them.
|
|
14
|
+
CARTETHYIA_FORCED_GC: true,
|
|
15
|
+
CARTETHYIA_RUNTIME_METRICS_INTERVAL_MS: true,
|
|
16
|
+
// Advanced knobs documented in the generated configuration reference.
|
|
17
|
+
CARTETHYIA_DISABLED_TASKS: true,
|
|
18
|
+
// Install-identity directory; only operators who need a stable identity
|
|
19
|
+
// across restarts touch it, and the reference covers it.
|
|
20
|
+
CARTETHYIA_INSTALL_ID_DIR: true,
|
|
21
|
+
CARTETHYIA_OAUTH_CALLBACK_PORT: true,
|
|
22
|
+
// Provider-specific overrides. Readers live in `src/config/providers.ts`;
|
|
23
|
+
// the majority of installs do not need them in the starter sample.
|
|
24
|
+
CARTETHYIA_KIMI_OAUTH_HOST: true,
|
|
25
|
+
CARTETHYIA_KIMI_QUOTA_BASE_URL: true,
|
|
26
|
+
CARTETHYIA_GITHUB_ENTERPRISE_DOMAIN: true,
|
|
27
|
+
CARTETHYIA_OPENCODE_UNAVAILABLE_FREE: true,
|
|
28
|
+
// Telemetry retention is deployment-level and documented in the reference.
|
|
29
|
+
CARTETHYIA_TELEMETRY_RETENTION_DAYS: true,
|
|
30
|
+
CARTETHYIA_TELEMETRY_PAYLOAD_MAX_BYTES: true,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Keys in `.env.example` that `CONFIG_SPEC` does not declare. */
|
|
34
|
+
export const ENV_EXAMPLE_ONLY_KEYS: Readonly<Record<string, true>> = {
|
|
35
|
+
// Read outside `src/config/` by their owning modules.
|
|
36
|
+
CARTETHYIA_DB_MODE: true,
|
|
37
|
+
NODE_ENV: true,
|
|
38
|
+
LOG_LEVEL: true,
|
|
39
|
+
DATABASE_URL: true,
|
|
40
|
+
DATABASE_POOL_MAX: true,
|
|
41
|
+
DATABASE_POOL_IDLE_FLOOR: true,
|
|
42
|
+
REDIS_URL: true,
|
|
43
|
+
CARTETHYIA_DATA_DIR: true,
|
|
44
|
+
CARTETHYIA_TELEMETRY_PAYLOAD_DIR: true,
|
|
45
|
+
CARTETHYIA_CONFIG_DIR: true,
|
|
46
|
+
DASHBOARD_PORT: true,
|
|
47
|
+
VITE_BACKEND_URL: true,
|
|
48
|
+
// Test-runner-only knob, not read by the gateway runtime.
|
|
49
|
+
CARTETHYIA_TEST_JOBS: true,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Operator-visible variables that are intentionally outside `CONFIG_SPEC` and
|
|
54
|
+
* therefore omitted from the generated configuration reference.
|
|
55
|
+
*/
|
|
56
|
+
export const CONFIG_REFERENCE_EXTERNAL_VARIABLES = [
|
|
57
|
+
"CARTETHYIA_DB_MODE",
|
|
58
|
+
"DATABASE_URL",
|
|
59
|
+
"REDIS_URL",
|
|
60
|
+
"NODE_ENV",
|
|
61
|
+
"LOG_LEVEL",
|
|
62
|
+
"DATABASE_POOL_MAX",
|
|
63
|
+
"DATABASE_POOL_IDLE_FLOOR",
|
|
64
|
+
"CARTETHYIA_DATA_DIR",
|
|
65
|
+
"CARTETHYIA_TELEMETRY_PAYLOAD_DIR",
|
|
66
|
+
"CARTETHYIA_CONFIG_DIR",
|
|
67
|
+
"DASHBOARD_PORT",
|
|
68
|
+
"VITE_BACKEND_URL",
|
|
69
|
+
"CARTETHYIA_TEST_DATABASE_URL",
|
|
70
|
+
"CARTETHYIA_TEST_JOBS",
|
|
71
|
+
"CARTETHYIA_REDIS_URL",
|
|
72
|
+
] as const;
|
package/src/config/spec.ts
CHANGED
|
@@ -173,12 +173,6 @@ export const CONFIG_SPEC = {
|
|
|
173
173
|
*/
|
|
174
174
|
CARTETHYIA_DISABLED_TASKS: { kind: "list" },
|
|
175
175
|
|
|
176
|
-
/**
|
|
177
|
-
* Force the Qoder UMID bridge to the mini-ELF bundle on Linux
|
|
178
|
-
* (`1` = always mini, `0` = never mini; absent = auto-detect containers).
|
|
179
|
-
*/
|
|
180
|
-
CARTETHYIA_QODER_UMID_MINI: { kind: "int", default: 0, min: 0, max: 1 },
|
|
181
|
-
|
|
182
176
|
/**
|
|
183
177
|
* How long a day's telemetry is kept, in days.
|
|
184
178
|
*
|
|
@@ -270,6 +264,26 @@ export const CONFIG_SPEC = {
|
|
|
270
264
|
CARTETHYIA_ACCOUNT_AUTH_REJECTED_COOLDOWN_MS: { kind: "int", default: 300_000, min: 0, max: 86_400_000 },
|
|
271
265
|
CARTETHYIA_ACCOUNT_POLICY_BLOCK_COOLDOWN_MS: { kind: "int", default: 300_000, min: 0, max: 86_400_000 },
|
|
272
266
|
CARTETHYIA_ACCOUNT_DISABLED_RECOVERY_MS: { kind: "int", default: 86_400_000, min: 0, max: 604_800_000 },
|
|
267
|
+
/**
|
|
268
|
+
* Routing reserve: an account whose remaining quota fraction falls at or
|
|
269
|
+
* below this percent is demoted behind non-reserve siblings during ordering
|
|
270
|
+
* (it is still eligible and still routed — a reserve is a preference, never
|
|
271
|
+
* an exclusion). 0 disables reserve demotion for every account.
|
|
272
|
+
*/
|
|
273
|
+
CARTETHYIA_ACCOUNT_RESERVE_PCT: { kind: "int", default: 20, min: 0, max: 100 },
|
|
274
|
+
/**
|
|
275
|
+
* Session stickiness warm window (milliseconds). Within this window after an
|
|
276
|
+
* account last served a session id, routing prefers that account when it is
|
|
277
|
+
* still eligible — prompt-cache warmth is worth more than perfect load
|
|
278
|
+
* balance. Past the window, the sticky account is only an ordering hint.
|
|
279
|
+
* 0 disables stickiness.
|
|
280
|
+
*/
|
|
281
|
+
CARTETHYIA_ROUTING_STICKY_WARM_MS: { kind: "int", default: 300_000, min: 0, max: 86_400_000 },
|
|
282
|
+
/**
|
|
283
|
+
* Hard ceiling on distinct session ids whose sticky state is retained in
|
|
284
|
+
* memory per provider (LRU). Evicted sessions simply lose their preference.
|
|
285
|
+
*/
|
|
286
|
+
CARTETHYIA_ROUTING_STICKY_MAX_SESSIONS: { kind: "int", default: 256, min: 1, max: 10_000 },
|
|
273
287
|
|
|
274
288
|
// Proxy pool agents
|
|
275
289
|
CARTETHYIA_PROXY_MAX_SOCKETS: { kind: "int", default: 100, min: 1, max: 100_000 },
|
|
@@ -11,7 +11,7 @@ import { type ShareLinkStore, type ShareLinkSummary } from "../../persistence/st
|
|
|
11
11
|
import { API_KEY_MODES, API_KEY_CONCURRENCY_SCOPES, type ApiKeyMode, type ApiKeyModelAccessMode, type ShareLinkKind } from "../../persistence/schema";
|
|
12
12
|
import { isValidTenantKeyScope, type AccessScope } from "../../security/access-control";
|
|
13
13
|
import { normalizeClientRouterId } from "../../security/client-router-fingerprint";
|
|
14
|
-
import type { ShareActivityPort } from "../share/share-usage";
|
|
14
|
+
import type { ShareActivityPort, SharedKeySummary } from "../share/share-usage";
|
|
15
15
|
import { ConsoleDomainError } from "../shared/errors";
|
|
16
16
|
import { pickDefined } from "../shared/object";
|
|
17
17
|
import type { ConsoleAccessResolver } from "../auth/access";
|
|
@@ -35,6 +35,17 @@ export type {
|
|
|
35
35
|
SharedKeySummary,
|
|
36
36
|
SharedKeyActivityDetail,
|
|
37
37
|
} from "../share/share-usage";
|
|
38
|
+
|
|
39
|
+
/** Paged response of the share-child list endpoint. */
|
|
40
|
+
export interface SharedKeyPage {
|
|
41
|
+
readonly rows: readonly SharedKeySummary[];
|
|
42
|
+
readonly total: number;
|
|
43
|
+
readonly active: number;
|
|
44
|
+
readonly revoked: number;
|
|
45
|
+
/** Family-wide lifetime tokens consumed, independent of the page. */
|
|
46
|
+
readonly consumed: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
/** How a share template's concurrency cap applies to its recipients. */
|
|
39
50
|
export type ConcurrencyScope = (typeof API_KEY_CONCURRENCY_SCOPES)[number];
|
|
40
51
|
|
|
@@ -78,25 +78,53 @@ function parseSharedChildrenQuery(query: URLSearchParams): SharedChildrenPageOpt
|
|
|
78
78
|
return { limit, offset, sortBy, order, status };
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Drops the auth-cache snapshot for a key family: the parent first, then
|
|
83
|
+
* every child.
|
|
84
|
+
*
|
|
85
|
+
* Parent policy is live-inherited by children at auth time, so their cached
|
|
86
|
+
* snapshots must die with the parent — otherwise a denylist / budget edit
|
|
87
|
+
* looks like a no-op for up to AUTH_CACHE_TTL_MS per child.
|
|
88
|
+
*/
|
|
89
|
+
function invalidateFamilyCaches(keyId: string, children: readonly ApiKeyRecord[]): void {
|
|
90
|
+
invalidateApiKeyCache(keyId);
|
|
91
|
+
for (const child of children) invalidateApiKeyCache(child.id);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Purges admission counters for a key family: the parent first, then every
|
|
96
|
+
* child.
|
|
97
|
+
*
|
|
98
|
+
* Share children admit under the parent id, so the parent's counter must die
|
|
99
|
+
* with a revoked/regenerated parent too — otherwise a family budget can be
|
|
100
|
+
* bypassed by a counter seeded under the old credential.
|
|
101
|
+
*/
|
|
102
|
+
async function purgeFamilyAdmission(
|
|
103
|
+
admissionService: { purgeKey(apiKeyId: string): Promise<void> },
|
|
104
|
+
keyId: string,
|
|
105
|
+
children: readonly ApiKeyRecord[],
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
await Promise.all([
|
|
108
|
+
admissionService.purgeKey(keyId),
|
|
109
|
+
...children.map((child) => admissionService.purgeKey(child.id)),
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
|
|
81
113
|
export function createApiKeyOperations(config: ApiKeyConfig) {
|
|
82
114
|
const operations = {
|
|
83
|
-
async listKeys(access: AccessDecision | undefined): Promise<ApiKeyResponse[]> {
|
|
115
|
+
async listKeys(access: AccessDecision | undefined, includeChildren = false): Promise<ApiKeyResponse[]> {
|
|
84
116
|
const authorized = requireTenantScope(access, "dashboard:read");
|
|
85
117
|
const records = await config.store.list(authorized.tenantId);
|
|
86
118
|
const topLevel = records.filter(
|
|
87
119
|
(record) => record.revokedAt === undefined && record.parentKeyId === undefined,
|
|
88
120
|
);
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
topLevel
|
|
95
|
-
|
|
96
|
-
if (record.keyMode !== "share") return response;
|
|
97
|
-
return response;
|
|
98
|
-
}),
|
|
99
|
-
);
|
|
121
|
+
// Share children are omitted from the main key list (they belong to a
|
|
122
|
+
// template), but the Banned Users search wants them so a child can be
|
|
123
|
+
// found and banned by hand.
|
|
124
|
+
const visible = includeChildren
|
|
125
|
+
? records.filter((record) => record.revokedAt === undefined)
|
|
126
|
+
: topLevel;
|
|
127
|
+
return visible.map(sanitizeApiKeyResponse);
|
|
100
128
|
},
|
|
101
129
|
async listActiveBannedKeys(access: AccessDecision | undefined): Promise<ApiKeyResponse[]> {
|
|
102
130
|
const authorized = requireTenantScope(access, "dashboard:read");
|
|
@@ -337,11 +365,7 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
|
|
|
337
365
|
});
|
|
338
366
|
if (!updated) throw new ConsoleDomainError("key_not_found", 404, "Key not found");
|
|
339
367
|
const children = await config.store.listChildren(authorized.tenantId, keyId);
|
|
340
|
-
|
|
341
|
-
// cached snapshots must die with the parent — otherwise a denylist /
|
|
342
|
-
// budget edit looks like a no-op for up to AUTH_CACHE_TTL_MS per child.
|
|
343
|
-
invalidateApiKeyCache(keyId);
|
|
344
|
-
for (const child of children) invalidateApiKeyCache(child.id);
|
|
368
|
+
invalidateFamilyCaches(keyId, children);
|
|
345
369
|
// The allow/deny list and prefix decide which models `/v1/models`
|
|
346
370
|
// returns, which is a different cache from the auth snapshot: the
|
|
347
371
|
// listing is cached per (tenant, key, mode, list, prefix), so editing
|
|
@@ -391,10 +415,7 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
|
|
|
391
415
|
// Drop stale admission counters so a newly lowered one-time/recurring
|
|
392
416
|
// budget cannot be bypassed by a counter seeded under the looser limit.
|
|
393
417
|
// Share children admit under the parent id, so purge the parent too.
|
|
394
|
-
await
|
|
395
|
-
config.admissionService.purgeKey(keyId),
|
|
396
|
-
...children.map((child) => config.admissionService.purgeKey(child.id)),
|
|
397
|
-
]);
|
|
418
|
+
await purgeFamilyAdmission(config.admissionService, keyId, children);
|
|
398
419
|
}
|
|
399
420
|
// Adding a daily/monthly ceiling to a key that already spent in the
|
|
400
421
|
// current bucket must not hand it a full fresh budget: seed the counter
|
|
@@ -441,12 +462,8 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
|
|
|
441
462
|
const children = await config.store.listChildren(authorized.tenantId, keyId);
|
|
442
463
|
const removed = await config.store.remove(authorized.tenantId, keyId);
|
|
443
464
|
if (!removed) throw new ConsoleDomainError("key_not_found", 404, "Key not found");
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
await Promise.all([
|
|
447
|
-
config.admissionService.purgeKey(keyId),
|
|
448
|
-
...children.map((child) => config.admissionService.purgeKey(child.id)),
|
|
449
|
-
]);
|
|
465
|
+
invalidateFamilyCaches(keyId, children);
|
|
466
|
+
await purgeFamilyAdmission(config.admissionService, keyId, children);
|
|
450
467
|
await config.auditSink?.record({
|
|
451
468
|
access: authorized,
|
|
452
469
|
action: "api_key.deleted",
|
|
@@ -620,8 +637,9 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
|
|
|
620
637
|
keyPrefix: generated.prefix,
|
|
621
638
|
});
|
|
622
639
|
if (!updated) throw new ConsoleDomainError("key_not_found", 404, "Key not found");
|
|
623
|
-
|
|
624
|
-
|
|
640
|
+
// Regeneration is personal-key only, so the family is the key itself.
|
|
641
|
+
invalidateFamilyCaches(keyId, []);
|
|
642
|
+
await purgeFamilyAdmission(config.admissionService, keyId, []);
|
|
625
643
|
const shareStore = config.shareStore;
|
|
626
644
|
let share: ShareKeyResponse | null = null;
|
|
627
645
|
if (shareStore) {
|
|
@@ -921,9 +939,10 @@ function jsonNull(): Response {
|
|
|
921
939
|
export function createApiKeyRoutes(config: ApiKeyConfig): AnyElysia {
|
|
922
940
|
const factory = createApiKeyOperations(config);
|
|
923
941
|
return new Elysia({ prefix: "/api-keys" })
|
|
924
|
-
.get("/", async ({ request, set }) => {
|
|
942
|
+
.get("/", async ({ request, set, query }) => {
|
|
925
943
|
try {
|
|
926
|
-
|
|
944
|
+
const includeChildren = query?.["includeChildren"] === "1" || query?.["includeChildren"] === "true";
|
|
945
|
+
return await factory.listKeys(config.accessResolver(request), includeChildren);
|
|
927
946
|
} catch (error) {
|
|
928
947
|
return errorResponse(error, set, "API-key operation failed");
|
|
929
948
|
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* the operator to type it rather than press Enter on a menu row.
|
|
24
24
|
*/
|
|
25
25
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
26
|
-
import {
|
|
26
|
+
import { requireLoopbackAdmin } from "../../transport/drain-endpoint";
|
|
27
27
|
|
|
28
28
|
interface ConsoleAccountResetDeps {
|
|
29
29
|
/** Shared secret; the route is registered only when this is set. */
|
|
@@ -40,14 +40,12 @@ export function createConsoleAccountResetHandler(
|
|
|
40
40
|
deps: ConsoleAccountResetDeps,
|
|
41
41
|
): (context: { request: Request; body: unknown }) => Promise<Response> {
|
|
42
42
|
return async ({ request, body }) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (!tokenMatches(request.headers.get("x-drain-token"), deps.token))
|
|
50
|
-
throw new GatewayError("authentication_failed", 401, "invalid reset token (request {requestId}). Wrong key, right door — check the token 🔑");
|
|
43
|
+
requireLoopbackAdmin(request, {
|
|
44
|
+
peer: deps.resolvePeerAddress(request),
|
|
45
|
+
token: deps.token,
|
|
46
|
+
policyRejectedMessage: "console account reset is only available from loopback (request {requestId}). This operation is only allowed from a trusted loopback connection — no hard feelings 🫂",
|
|
47
|
+
authenticationFailedMessage: "invalid reset token (request {requestId}). Wrong key, right door — check the token 🔑",
|
|
48
|
+
});
|
|
51
49
|
const confirm =
|
|
52
50
|
body !== null && typeof body === "object" && "confirm" in body
|
|
53
51
|
? body.confirm
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
* as sensitive as the database.
|
|
21
21
|
*/
|
|
22
22
|
import { getTableColumns, getTableName, type Column, type Table } from "drizzle-orm";
|
|
23
|
+
import type { BackupInspection } from "./service";
|
|
24
|
+
import type { DeleteAllScope } from "./store";
|
|
23
25
|
import { resolveMaxBodyBytes } from "../../config/runtime";
|
|
24
26
|
import {
|
|
25
27
|
apiKeys,
|
|
@@ -393,6 +395,8 @@ export type BackupSection = "config" | "telemetry";
|
|
|
393
395
|
|
|
394
396
|
export const BACKUP_SECTIONS: readonly BackupSection[] = ["config", "telemetry"];
|
|
395
397
|
|
|
398
|
+
export type { BackupInspection, DeleteAllScope };
|
|
399
|
+
|
|
396
400
|
export interface BackupPayload {
|
|
397
401
|
readonly app: typeof BACKUP_APP;
|
|
398
402
|
readonly version: number;
|
|
@@ -22,7 +22,7 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
22
22
|
import { homedir } from "node:os";
|
|
23
23
|
import { join } from "node:path";
|
|
24
24
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
25
|
-
import {
|
|
25
|
+
import { requireLoopbackAdmin } from "../../transport/drain-endpoint";
|
|
26
26
|
import { tablesForSection } from "./contracts";
|
|
27
27
|
import { compressBackupStream, exportBackupStream } from "./store";
|
|
28
28
|
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
@@ -52,10 +52,12 @@ export function createBackupExportHandler(
|
|
|
52
52
|
// both: neither refusal is about the body. The first is where the request
|
|
53
53
|
// came from, the second is the credential it carried — and `invalid_request`
|
|
54
54
|
// told an operator to fix a body that was never the problem.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
requireLoopbackAdmin(request, {
|
|
56
|
+
peer: deps.resolvePeerAddress(request),
|
|
57
|
+
token: deps.token,
|
|
58
|
+
policyRejectedMessage: "backup export is only available from loopback (request {requestId}). This operation is only allowed from a trusted loopback connection — no hard feelings 🫂",
|
|
59
|
+
authenticationFailedMessage: "invalid export token (request {requestId}). Wrong key, right door — check the token 🔑",
|
|
60
|
+
});
|
|
59
61
|
|
|
60
62
|
const directory =
|
|
61
63
|
body !== null && typeof body === "object" && "directory" in body
|
|
@@ -8,7 +8,6 @@ import type {
|
|
|
8
8
|
CliMappingSettings,
|
|
9
9
|
DownloadResult,
|
|
10
10
|
ToolDef,
|
|
11
|
-
ToolInjector,
|
|
12
11
|
ToolStatus,
|
|
13
12
|
} from "./contracts";
|
|
14
13
|
import { TOOL_IDS, TOOL_REGISTRY, type ToolId, type ToolRegistryEntry } from "./contracts";
|
|
@@ -47,11 +46,6 @@ function toToolRegistryEntry(def: ToolDef): ToolRegistryEntry {
|
|
|
47
46
|
};
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
function injectorFor(toolId: string): ToolInjector | null {
|
|
51
|
-
const injector = INJECTORS[toolId as ToolId];
|
|
52
|
-
return injector ?? null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
49
|
/** Operator-facing summary of what an apply actually did. */
|
|
56
50
|
function applyMessage(outcome: ApplyOutcome, toolId: string, fileMessage: string): string {
|
|
57
51
|
switch (outcome) {
|
|
@@ -139,8 +133,8 @@ export class CliToolService {
|
|
|
139
133
|
}
|
|
140
134
|
|
|
141
135
|
async getStatus(toolId: string): Promise<ToolStatus | null> {
|
|
142
|
-
const injector =
|
|
143
|
-
if (injector ===
|
|
136
|
+
const injector = INJECTORS[toolId as ToolId];
|
|
137
|
+
if (injector === undefined) return null;
|
|
144
138
|
return injector.getStatus();
|
|
145
139
|
}
|
|
146
140
|
|
|
@@ -249,8 +243,8 @@ export class CliToolService {
|
|
|
249
243
|
}
|
|
250
244
|
|
|
251
245
|
async downloadConfig(toolId: string, input: ApplyInput): Promise<DownloadResult | null> {
|
|
252
|
-
const injector =
|
|
253
|
-
if (injector ===
|
|
246
|
+
const injector = INJECTORS[toolId as ToolId];
|
|
247
|
+
if (injector === undefined) return null;
|
|
254
248
|
if (!input.endpoint || !input.apiKey) return null;
|
|
255
249
|
try {
|
|
256
250
|
return await injector.download(input);
|
|
@@ -281,8 +275,8 @@ export class CliToolService {
|
|
|
281
275
|
apiKeyId: string,
|
|
282
276
|
input: ApplyInput & { mode?: "file" | "remote" | "both" },
|
|
283
277
|
): Promise<ApplyConfigResult | null> {
|
|
284
|
-
const injector =
|
|
285
|
-
if (injector ===
|
|
278
|
+
const injector = INJECTORS[toolId as ToolId];
|
|
279
|
+
if (injector === undefined) return null;
|
|
286
280
|
const mode = input.mode ?? "both";
|
|
287
281
|
const wantsFile = mode === "file" || mode === "both";
|
|
288
282
|
const wantsRemote = mode === "remote" || mode === "both";
|