cartethyia 2.7.9 → 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 -2
- package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Cc0vKpMG.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-CXsaOAO2.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BeLpJ4ma.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-DnV0uZ3m.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-CBxAjo63.js} +1 -1
- 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-BOoO8rpz.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CgoPc5B9.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-D9bFQern.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-B_p4tL8k.js} +1 -1
- 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-CQ6bPUf_.js → ProviderIcon-D_0PiZtg.js} +1 -1
- 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-BJ87Pp0U.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-C5HPWqEv.js → SettingsPage-CctTga2C.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage-febLU4r1.js} +1 -1
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-M6fvR5Wz.js} +1 -1
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-nyeuRqz9.js} +1 -1
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-BFOlvrJC.js} +1 -1
- 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-CqTLw5e7.js +1 -0
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-C83EqLB0.js} +1 -1
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-D1xn7pIe.js} +1 -1
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-DU3Lq5Lm.js} +1 -1
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-Dw1U_drK.js} +1 -1
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.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-B5L2jPQ_.js → dialog-BjFR5UHy.js} +1 -1
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-DkjUouAj.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-B-LVLV6o.js → entry-CECVELyy.js} +1 -1
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-DYYofQ7W.js} +1 -1
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-DLW4i8zK.js} +1 -1
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/{index-BU64Lqvp.js → index-1-fCqe64.js} +2 -2
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-DGvU2oLK.js} +1 -1
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/{input-BCVkdR9d.js → input-iNnO1X2K.js} +1 -1
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-0q8UGypb.js} +1 -1
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-C0w2bQjJ.js} +1 -1
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/{live-Cae01iu8.js → live-M5aJ_xCa.js} +1 -1
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/{network-CRib7H_2.js → network-BKeQGT3K.js} +1 -1
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-CivAk3da.js} +1 -1
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-CpzvI5ep.js} +1 -1
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.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/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-BFQX2VBc.js} +1 -1
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/{select-ChoFWyKz.js → select-CXbck0jG.js} +2 -2
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-C_XvMHh9.js} +1 -1
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-DggRiCxE.js} +1 -1
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-ClFzfbYp.js} +1 -1
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/{switch-Db70BMix.js → switch-CTIFxKqS.js} +1 -1
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-BClG3yWj.js} +1 -1
- package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +54 -53
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -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 +1 -1
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +20 -0
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +40 -28
- 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/domain-registration.ts +0 -2
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +7 -13
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/contracts.ts +2 -2
- package/src/console/providers/catalog/discover-constants.ts +6 -0
- package/src/console/providers/catalog/routes.ts +1 -0
- package/src/console/providers/catalog/store.ts +21 -16
- package/src/console/quota/account-quota-global.ts +14 -24
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +6 -18
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/operations.ts +31 -46
- package/src/console/shared/errors.ts +33 -6
- package/src/main.ts +2 -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/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 +18 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +8 -0
- package/src/persistence/ttl-cache.ts +20 -2
- package/src/protocol/codecs.ts +1 -1
- package/src/protocol/dsml.ts +34 -86
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/request/chat.ts +10 -10
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +3 -4
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/providers/adapters/compatible-adapter.ts +71 -12
- 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 +30 -49
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +61 -36
- package/src/providers/discovery/probe-phases.ts +14 -4
- package/src/providers/discovery/probing-service.ts +15 -2
- 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 +5 -1
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +173 -17
- package/src/providers/integrations/buddy/buddy-identity.ts +19 -6
- package/src/providers/integrations/buddy/capabilities.ts +2 -2
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +3 -0
- package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +37 -22
- 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-exec.ts +9 -2
- package/src/providers/integrations/cursor/cursor-request.ts +26 -12
- package/src/providers/integrations/cursor/cursor-shared.ts +7 -4
- package/src/providers/integrations/cursor/cursor.ts +89 -40
- 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/devin/protocol/devin-proto.ts +1 -27
- package/src/providers/integrations/factory/factory-oauth.ts +17 -44
- 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/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.ts → hermes/hermes.ts} +9 -9
- 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/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-protocol.ts +0 -3
- package/src/providers/integrations/perch/perch-quota.ts +2 -16
- package/src/providers/integrations/perch/perch.ts +3 -13
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +18 -59
- package/src/providers/integrations/qoder/qoder-adapter.ts +1 -12
- package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
- package/src/providers/integrations/qoder/qoder-catalog.ts +20 -5
- package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
- package/src/providers/integrations/qoder/qoder-checkin.ts +4 -4
- package/src/providers/integrations/qoder/qoder-grpc.ts +0 -1
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -107
- package/src/providers/integrations/qoder/qoder.ts +0 -1
- package/src/providers/integrations/trae/trae-protocol.ts +9 -5
- package/src/providers/integrations/trae/trae-quota.ts +6 -28
- package/src/providers/integrations/trae/trae-realm.ts +38 -0
- package/src/providers/integrations/trae/trae.ts +7 -24
- 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 +199 -307
- package/src/providers/operations/account-health-service.ts +82 -1
- package/src/providers/operations/account-refresh-status.ts +22 -37
- package/src/providers/operations/account-tier.ts +15 -16
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +5 -51
- package/src/providers/operations/cooldown-rules.ts +11 -16
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- 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 +4 -2
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +40 -10
- 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 +75 -18
- 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 +8 -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 +8 -36
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +63 -0
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +16 -30
- 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 +393 -32
- 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 +7 -1
- 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 +22 -5
- package/src/transport/surface/responses/parse.ts +3 -11
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/pagination-ecPvG_8g.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-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- 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-B7kIWFm5.js → app-DIXLjojg.js} +0 -0
- /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-DIXLjojg.js.gz} +0 -0
|
@@ -1,77 +1,77 @@
|
|
|
1
1
|
{
|
|
2
|
-
"count":
|
|
2
|
+
"count": 122,
|
|
3
3
|
"assets": [
|
|
4
|
-
"assets/BannedPage-
|
|
5
|
-
"assets/CliToolDetailPage-
|
|
6
|
-
"assets/CliToolsPage-
|
|
7
|
-
"assets/CombosPage-
|
|
8
|
-
"assets/ConsoleLogPage-
|
|
9
|
-
"assets/CustomizationPage-
|
|
10
|
-
"assets/HealthEventsModal-
|
|
11
|
-
"assets/InjectSystemPromptPanel-
|
|
12
|
-
"assets/LoginPage-
|
|
13
|
-
"assets/ModelPicker-
|
|
14
|
-
"assets/OverviewPage-
|
|
15
|
-
"assets/ProviderDetailPage-
|
|
16
|
-
"assets/ProviderIcon-
|
|
17
|
-
"assets/ProvidersPage-
|
|
18
|
-
"assets/ProxyPage-
|
|
19
|
-
"assets/QuotaPage-
|
|
20
|
-
"assets/SettingsPage-
|
|
21
|
-
"assets/SetupPage-
|
|
22
|
-
"assets/StudioPage-
|
|
4
|
+
"assets/BannedPage-Cc0vKpMG.js",
|
|
5
|
+
"assets/CliToolDetailPage-Bqj2aa1T.js",
|
|
6
|
+
"assets/CliToolsPage-CXsaOAO2.js",
|
|
7
|
+
"assets/CombosPage-BeLpJ4ma.js",
|
|
8
|
+
"assets/ConsoleLogPage-DnV0uZ3m.js",
|
|
9
|
+
"assets/CustomizationPage-CBxAjo63.js",
|
|
10
|
+
"assets/HealthEventsModal-B5yEE0k6.js",
|
|
11
|
+
"assets/InjectSystemPromptPanel-BWFLECmz.js",
|
|
12
|
+
"assets/LoginPage-CgoPc5B9.js",
|
|
13
|
+
"assets/ModelPicker-D9bFQern.js",
|
|
14
|
+
"assets/OverviewPage-B_p4tL8k.js",
|
|
15
|
+
"assets/ProviderDetailPage-rs5E2rxv.js",
|
|
16
|
+
"assets/ProviderIcon-D_0PiZtg.js",
|
|
17
|
+
"assets/ProvidersPage-CLI2bJTK.js",
|
|
18
|
+
"assets/ProxyPage-BwNC_-Cx.js",
|
|
19
|
+
"assets/QuotaPage-DA-a1iSN.js",
|
|
20
|
+
"assets/SettingsPage-CctTga2C.js",
|
|
21
|
+
"assets/SetupPage-febLU4r1.js",
|
|
22
|
+
"assets/StudioPage-M6fvR5Wz.js",
|
|
23
23
|
"assets/ToolIcon-CfeLA-Z_.js",
|
|
24
|
-
"assets/UsagePage-
|
|
25
|
-
"assets/api-keys-
|
|
26
|
-
"assets/app-
|
|
27
|
-
"assets/app-
|
|
24
|
+
"assets/UsagePage-nyeuRqz9.js",
|
|
25
|
+
"assets/api-keys-BFOlvrJC.js",
|
|
26
|
+
"assets/app-DIXLjojg.js",
|
|
27
|
+
"assets/app-Dx2QgsNj.css",
|
|
28
28
|
"assets/arrow-left-CpBxkoxI.js",
|
|
29
29
|
"assets/arrow-right-Dl5yKnp8.js",
|
|
30
30
|
"assets/arrow-up-59ms1iiT.js",
|
|
31
|
-
"assets/assert-
|
|
31
|
+
"assets/assert-CqTLw5e7.js",
|
|
32
32
|
"assets/backup-file-gdKIlg8U.js",
|
|
33
|
-
"assets/badge-
|
|
33
|
+
"assets/badge-C83EqLB0.js",
|
|
34
34
|
"assets/ban-durations-B58FyRxC.js",
|
|
35
|
-
"assets/banner-
|
|
35
|
+
"assets/banner-D1xn7pIe.js",
|
|
36
36
|
"assets/brain-Cwed_72z.js",
|
|
37
|
-
"assets/card-
|
|
37
|
+
"assets/card-DU3Lq5Lm.js",
|
|
38
38
|
"assets/chevron-left-Bf6CaTSJ.js",
|
|
39
39
|
"assets/chevron-right-BXIy5l3h.js",
|
|
40
40
|
"assets/circle-alert-CntYHV5v.js",
|
|
41
41
|
"assets/circle-check-BU3v_mFW.js",
|
|
42
42
|
"assets/circle-question-mark-Cz4un8sH.js",
|
|
43
|
-
"assets/cli-tools-
|
|
44
|
-
"assets/clipboard-button-
|
|
43
|
+
"assets/cli-tools-Dw1U_drK.js",
|
|
44
|
+
"assets/clipboard-button-CEL5Sg_J.js",
|
|
45
45
|
"assets/database-Dz9N3zGd.js",
|
|
46
|
-
"assets/dialog-
|
|
46
|
+
"assets/dialog-BjFR5UHy.js",
|
|
47
47
|
"assets/dialog-actions-B1axpEg7.js",
|
|
48
48
|
"assets/download-dRFo5_Wu.js",
|
|
49
|
-
"assets/drawer-
|
|
50
|
-
"assets/entry-
|
|
51
|
-
"assets/entry-
|
|
52
|
-
"assets/entry-
|
|
49
|
+
"assets/drawer-HvBW0AjI.js",
|
|
50
|
+
"assets/entry-BSu3gFlt.js",
|
|
51
|
+
"assets/entry-CECVELyy.js",
|
|
52
|
+
"assets/entry-DYYofQ7W.js",
|
|
53
53
|
"assets/eye-C_hawfH4.js",
|
|
54
54
|
"assets/eye-off-Bk5g2luF.js",
|
|
55
55
|
"assets/fingerprint-pattern-BWuHtbW4.js",
|
|
56
|
-
"assets/formatters-
|
|
56
|
+
"assets/formatters-DLW4i8zK.js",
|
|
57
57
|
"assets/gauge-BbIMy7MB.js",
|
|
58
58
|
"assets/house-h9AZlJuT.js",
|
|
59
59
|
"assets/image-BPaouViG.js",
|
|
60
|
-
"assets/index-
|
|
61
|
-
"assets/inline-
|
|
62
|
-
"assets/input-
|
|
60
|
+
"assets/index-1-fCqe64.js",
|
|
61
|
+
"assets/inline-DGvU2oLK.js",
|
|
62
|
+
"assets/input-iNnO1X2K.js",
|
|
63
63
|
"assets/jsx-runtime-CXUMngI_.js",
|
|
64
64
|
"assets/key-round-UkavsGxp.js",
|
|
65
65
|
"assets/layers-2-glBbjl.js",
|
|
66
|
-
"assets/layout-
|
|
67
|
-
"assets/list-row-
|
|
68
|
-
"assets/live-
|
|
66
|
+
"assets/layout-0q8UGypb.js",
|
|
67
|
+
"assets/list-row-C0w2bQjJ.js",
|
|
68
|
+
"assets/live-M5aJ_xCa.js",
|
|
69
69
|
"assets/log-in-BmRjy60c.js",
|
|
70
70
|
"assets/message-circle-CCxGwRvv.js",
|
|
71
71
|
"assets/mutation-BQ0ccH_A.js",
|
|
72
|
-
"assets/network-
|
|
73
|
-
"assets/numbers-
|
|
74
|
-
"assets/pagination-
|
|
72
|
+
"assets/network-BKeQGT3K.js",
|
|
73
|
+
"assets/numbers-CivAk3da.js",
|
|
74
|
+
"assets/pagination-CpzvI5ep.js",
|
|
75
75
|
"assets/pencil-768ysRAr.js",
|
|
76
76
|
"assets/plus-DIdX_g-x.js",
|
|
77
77
|
"assets/power-off-Yjw9GMJh.js",
|
|
@@ -95,22 +95,23 @@
|
|
|
95
95
|
"assets/providers/parallel.svg",
|
|
96
96
|
"assets/providers/tokenharbor.svg",
|
|
97
97
|
"assets/providers/windsurf.svg",
|
|
98
|
+
"assets/providers/zro.svg",
|
|
98
99
|
"assets/query-keys-IKLh0YFd.js",
|
|
99
|
-
"assets/quota-
|
|
100
|
+
"assets/quota-DiPD6UKH.js",
|
|
100
101
|
"assets/refresh-cw-BqJFk4sU.js",
|
|
101
102
|
"assets/rotate-ccw-CufcqITi.js",
|
|
102
103
|
"assets/route-CNSNj9Zm.js",
|
|
103
|
-
"assets/routing-
|
|
104
|
+
"assets/routing-BFQX2VBc.js",
|
|
104
105
|
"assets/scaling-nWE4Cx8f.js",
|
|
105
106
|
"assets/search-JOKus9rb.js",
|
|
106
|
-
"assets/select-
|
|
107
|
+
"assets/select-CXbck0jG.js",
|
|
107
108
|
"assets/settings-2-Ds9DQp0t.js",
|
|
108
|
-
"assets/settings-
|
|
109
|
+
"assets/settings-C_XvMHh9.js",
|
|
109
110
|
"assets/shield-BMQNxORh.js",
|
|
110
111
|
"assets/shield-check-DVS0Foqh.js",
|
|
111
|
-
"assets/stack-
|
|
112
|
-
"assets/state-
|
|
113
|
-
"assets/switch-
|
|
112
|
+
"assets/stack-DggRiCxE.js",
|
|
113
|
+
"assets/state-ClFzfbYp.js",
|
|
114
|
+
"assets/switch-CTIFxKqS.js",
|
|
114
115
|
"assets/terminal-0_IjPWTk.js",
|
|
115
116
|
"assets/tokens-COzario9.js",
|
|
116
117
|
"assets/trash-Cppn5c3R.js",
|
|
@@ -118,7 +119,7 @@
|
|
|
118
119
|
"assets/use-modal-focus-By7oJoGd.js",
|
|
119
120
|
"assets/use-reduced-motion-C4B0R6Sz.js",
|
|
120
121
|
"assets/use-timeout-BwcBBRrT.js",
|
|
121
|
-
"assets/useMutation-
|
|
122
|
+
"assets/useMutation-BClG3yWj.js",
|
|
122
123
|
"assets/wrench-B6E1oEuY.js",
|
|
123
124
|
"assets/zap-C4EeVW4q.js",
|
|
124
125
|
"index.html"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Freshness stamp for `quota_plan`: the plan column is a bare string, so
|
|
2
|
+
-- routing cannot tell a fresh read from a stale one. This timestamp is written
|
|
3
|
+
-- together with `quota_plan` on a successful quota fetch and lets the routing
|
|
4
|
+
-- engine downgrade a plan older than the freshness horizon to `unknown`
|
|
5
|
+
-- instead of trusting it blindly.
|
|
6
|
+
ALTER TABLE "provider_accounts"
|
|
7
|
+
ADD COLUMN IF NOT EXISTS "quota_plan_fetched_at" timestamptz;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cartethyia",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "A powerful, self-hosted reverse proxy for OpenAI- and Anthropic-compatible AI clients, powered by native Bun and Elysia. It serves both protocols from a single server, routes every model across any configured provider, and translates requests and responses in real time — streaming and prompt caching included. Underneath it runs a full delivery engine: per-account health tracking, quota and cooldown enforcement, automatic failover across pools and providers, usage accounting, and telemetry. Point OpenCode, Cline, Claude Code, Codex, or any compatible client at Cartethyia and it keeps its native protocol while the gateway absorbs every provider-specific difference behind it. Runs on embedded SQLite or external PostgreSQL with optional Redis coordination, ships as a global CLI with a system-tray console, and needs only Bun 1.4+ alongside Node.js/npm.",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"type": "module",
|
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
|
@@ -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";
|
|
@@ -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();
|
|
@@ -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
|
@@ -264,6 +264,26 @@ export const CONFIG_SPEC = {
|
|
|
264
264
|
CARTETHYIA_ACCOUNT_AUTH_REJECTED_COOLDOWN_MS: { kind: "int", default: 300_000, min: 0, max: 86_400_000 },
|
|
265
265
|
CARTETHYIA_ACCOUNT_POLICY_BLOCK_COOLDOWN_MS: { kind: "int", default: 300_000, min: 0, max: 86_400_000 },
|
|
266
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 },
|
|
267
287
|
|
|
268
288
|
// Proxy pool agents
|
|
269
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,6 +78,38 @@ 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
115
|
async listKeys(access: AccessDecision | undefined, includeChildren = false): Promise<ApiKeyResponse[]> {
|
|
@@ -92,17 +124,7 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
|
|
|
92
124
|
const visible = includeChildren
|
|
93
125
|
? records.filter((record) => record.revokedAt === undefined)
|
|
94
126
|
: topLevel;
|
|
95
|
-
|
|
96
|
-
// every recipient commit adds to it (see ApiKeyAdmissionService
|
|
97
|
-
// commitUsage), so reporting it raw gives the enforced family total.
|
|
98
|
-
// No telemetry add-on is needed, and adding it here would double-count.
|
|
99
|
-
return Promise.all(
|
|
100
|
-
visible.map(async (record) => {
|
|
101
|
-
const response = sanitizeApiKeyResponse(record);
|
|
102
|
-
if (record.keyMode !== "share") return response;
|
|
103
|
-
return response;
|
|
104
|
-
}),
|
|
105
|
-
);
|
|
127
|
+
return visible.map(sanitizeApiKeyResponse);
|
|
106
128
|
},
|
|
107
129
|
async listActiveBannedKeys(access: AccessDecision | undefined): Promise<ApiKeyResponse[]> {
|
|
108
130
|
const authorized = requireTenantScope(access, "dashboard:read");
|
|
@@ -343,11 +365,7 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
|
|
|
343
365
|
});
|
|
344
366
|
if (!updated) throw new ConsoleDomainError("key_not_found", 404, "Key not found");
|
|
345
367
|
const children = await config.store.listChildren(authorized.tenantId, keyId);
|
|
346
|
-
|
|
347
|
-
// cached snapshots must die with the parent — otherwise a denylist /
|
|
348
|
-
// budget edit looks like a no-op for up to AUTH_CACHE_TTL_MS per child.
|
|
349
|
-
invalidateApiKeyCache(keyId);
|
|
350
|
-
for (const child of children) invalidateApiKeyCache(child.id);
|
|
368
|
+
invalidateFamilyCaches(keyId, children);
|
|
351
369
|
// The allow/deny list and prefix decide which models `/v1/models`
|
|
352
370
|
// returns, which is a different cache from the auth snapshot: the
|
|
353
371
|
// listing is cached per (tenant, key, mode, list, prefix), so editing
|
|
@@ -397,10 +415,7 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
|
|
|
397
415
|
// Drop stale admission counters so a newly lowered one-time/recurring
|
|
398
416
|
// budget cannot be bypassed by a counter seeded under the looser limit.
|
|
399
417
|
// Share children admit under the parent id, so purge the parent too.
|
|
400
|
-
await
|
|
401
|
-
config.admissionService.purgeKey(keyId),
|
|
402
|
-
...children.map((child) => config.admissionService.purgeKey(child.id)),
|
|
403
|
-
]);
|
|
418
|
+
await purgeFamilyAdmission(config.admissionService, keyId, children);
|
|
404
419
|
}
|
|
405
420
|
// Adding a daily/monthly ceiling to a key that already spent in the
|
|
406
421
|
// current bucket must not hand it a full fresh budget: seed the counter
|
|
@@ -447,12 +462,8 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
|
|
|
447
462
|
const children = await config.store.listChildren(authorized.tenantId, keyId);
|
|
448
463
|
const removed = await config.store.remove(authorized.tenantId, keyId);
|
|
449
464
|
if (!removed) throw new ConsoleDomainError("key_not_found", 404, "Key not found");
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
await Promise.all([
|
|
453
|
-
config.admissionService.purgeKey(keyId),
|
|
454
|
-
...children.map((child) => config.admissionService.purgeKey(child.id)),
|
|
455
|
-
]);
|
|
465
|
+
invalidateFamilyCaches(keyId, children);
|
|
466
|
+
await purgeFamilyAdmission(config.admissionService, keyId, children);
|
|
456
467
|
await config.auditSink?.record({
|
|
457
468
|
access: authorized,
|
|
458
469
|
action: "api_key.deleted",
|
|
@@ -626,8 +637,9 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
|
|
|
626
637
|
keyPrefix: generated.prefix,
|
|
627
638
|
});
|
|
628
639
|
if (!updated) throw new ConsoleDomainError("key_not_found", 404, "Key not found");
|
|
629
|
-
|
|
630
|
-
|
|
640
|
+
// Regeneration is personal-key only, so the family is the key itself.
|
|
641
|
+
invalidateFamilyCaches(keyId, []);
|
|
642
|
+
await purgeFamilyAdmission(config.admissionService, keyId, []);
|
|
631
643
|
const shareStore = config.shareStore;
|
|
632
644
|
let share: ShareKeyResponse | null = null;
|
|
633
645
|
if (shareStore) {
|
|
@@ -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;
|