cartethyia 2.6.7 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -7
- package/README.md +25 -2
- package/dist/dashboard/assets/{BannedPage-Dskhyp0l.js → BannedPage-D-J75hBM.js} +1 -1
- package/dist/dashboard/assets/BannedPage-D-J75hBM.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage--SJhk7qQ.js → CliToolDetailPage-CVe6RaBH.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-CVe6RaBH.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-C203VT-c.js → CliToolsPage-Dd8ZSamp.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-Dd8ZSamp.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BcS7kGG1.js → CombosPage-QoJ6foxo.js} +1 -1
- package/dist/dashboard/assets/CombosPage-QoJ6foxo.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-uujbBYwC.js → CustomizationPage-CKUFTcW4.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-CKUFTcW4.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DubdQcvX.js +1 -0
- package/dist/dashboard/assets/LoginPage-DubdQcvX.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-CA1kLs45.js → ModelPicker-BH8Tehdi.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js +4 -0
- package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js +9 -0
- package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-BueSWX5D.js → QuotaPage-ATpaxrbl.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-ATpaxrbl.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CETophpx.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CETophpx.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-BdgECjqq.js +1 -0
- package/dist/dashboard/assets/SetupPage-BdgECjqq.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DrKeMcGN.js → SortableList-CAK-EN9z.js} +1 -1
- package/dist/dashboard/assets/SortableList-CAK-EN9z.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-qjuYtccc.js → StudioPage-B7W2_RhT.js} +3 -3
- package/dist/dashboard/assets/StudioPage-B7W2_RhT.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-6T4EFDjW.js +56 -0
- package/dist/dashboard/assets/UsagePage-6T4EFDjW.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-C23S9sgp.js → api-keys-CUi6UXsR.js} +1 -1
- package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css +2 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
- package/dist/dashboard/assets/{assert-DU6qrMA_.js → assert-67xXH5rf.js} +1 -1
- package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
- package/dist/dashboard/assets/{badge-Bkz_-RrM.js → badge-DC2L43Mj.js} +1 -1
- package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
- package/dist/dashboard/assets/{banner-C-7y3VKV.js → banner-BBVcb0J3.js} +1 -1
- package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
- package/dist/dashboard/assets/{card-BOPBuBzD.js → card-Cca1XC6G.js} +1 -1
- package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-BaFUuFFF.js → cli-tools-KvsWa1Jl.js} +1 -1
- package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-e-Mlk0FH.js → clipboard-button-DUzEbynq.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-Iec_ywbD.js → dialog-DFvWhwTa.js} +1 -1
- package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-Bs8MIIHc.js → drawer-CFM2sMX1.js} +1 -1
- package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
- package/dist/dashboard/assets/{entry-DOddwoc0.js → entry-BLBLxX0h.js} +3 -3
- package/dist/dashboard/assets/entry-BLBLxX0h.js.gz +0 -0
- package/dist/dashboard/assets/{entry-DcobaKFu.js → entry-Bqy8RFvM.js} +1 -1
- package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BRszm4_Z.js → entry-_xHo5x6r.js} +1 -1
- package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
- package/dist/dashboard/assets/formatters-mpBPqAQ8.js +1 -0
- package/dist/dashboard/assets/formatters-mpBPqAQ8.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-BuDsrsPn.js → github-badge-C9e8Ts7e.js} +1 -1
- package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
- package/dist/dashboard/assets/{index-BeLRhsmt.js → index-D6QzEC_n.js} +2 -2
- package/dist/dashboard/assets/index-D6QzEC_n.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DToMICwl.js → inline-BFUcvllj.js} +1 -1
- package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
- package/dist/dashboard/assets/{input-WlyHWeG2.js → input-C_ILAvBb.js} +1 -1
- package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DaJ5baJr.js → layout-CBIyT6Rt.js} +1 -1
- package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-B3oSK13z.js → list-row-ebJ_-Nsb.js} +1 -1
- package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
- package/dist/dashboard/assets/{live-ta6GMb-8.js → live-D0mm--ME.js} +1 -1
- package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +1 -0
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-g_fduiNw.js → mutation-sMD965oO.js} +1 -1
- package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-Dulqgys5.js → numbers-BO9dKaFr.js} +1 -1
- package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
- package/dist/dashboard/assets/providers/inception.svg +5 -0
- package/dist/dashboard/assets/providers/inception.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-UETqfytp.js → query-keys-CfL_Zdoh.js} +1 -1
- package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
- package/dist/dashboard/assets/{routing-BPUo-W8d.js → routing-BnIpi1IJ.js} +1 -1
- package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
- package/dist/dashboard/assets/{select-BfZ3SCTM.js → select-Cj5Nw43g.js} +1 -1
- package/dist/dashboard/assets/select-Cj5Nw43g.js.gz +0 -0
- package/dist/dashboard/assets/settings-BAFhDgX8.js +1 -0
- package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
- package/dist/dashboard/assets/{stack-B1w5BM0u.js → stack-EMrOMt4I.js} +1 -1
- package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
- package/dist/dashboard/assets/{state-DhZBfWgx.js → state-CWoo7ty5.js} +1 -1
- package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
- package/dist/dashboard/assets/{switch-yD7D99Lg.js → switch-CCjT6IkF.js} +1 -1
- package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-EDFRkn1i.js → useMutation-DjkLHUcE.js} +1 -1
- package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +55 -54
- package/migrations/full-postgres/0024_add_pool_provider_cooldowns.sql +12 -0
- package/migrations/full-postgres/0025_add_pool_random_strategy.sql +3 -0
- package/migrations/lite-sqlite3/0024_add_pool_provider_cooldowns.sql +12 -0
- package/migrations/lite-sqlite3/0025_add_pool_random_strategy.sql +20 -0
- package/package.json +1 -1
- package/scripts/cli/console-menu.ts +5 -2
- package/scripts/cli/tray.ts +21 -6
- package/src/boot/app.ts +24 -62
- package/src/boot/dependencies.ts +732 -750
- package/src/config/cooldowns.ts +16 -0
- package/src/config/providers.ts +13 -1
- package/src/config/resources.ts +13 -80
- package/src/config/security.ts +2 -38
- package/src/config/spec.ts +16 -23
- package/src/console/console-router.ts +7 -7
- package/src/console/domain-registration.ts +19 -12
- package/src/console/providers/catalog/catalog-projections.ts +2 -6
- package/src/console/providers/catalog/contracts.ts +19 -0
- package/src/console/providers/catalog/provider-operations.ts +38 -0
- package/src/console/providers/catalog/routes.ts +14 -0
- package/src/console/providers/catalog/store-helpers.ts +326 -0
- package/src/console/providers/catalog/store.ts +1292 -1563
- package/src/console/quota/account-quota-global.ts +2 -2
- package/src/console/quota/account-quota-shared.ts +1 -1
- package/src/console/quota/account-quota-tenant.ts +35 -12
- package/src/console/quota/quota-view.ts +2 -2
- package/src/console/remote-support/contracts.ts +3 -1
- package/src/console/remote-support/service.ts +115 -6
- package/src/console/routing/pools/contracts.ts +41 -1
- package/src/console/routing/pools/operations.ts +183 -4
- package/src/console/routing/pools/relay-deploy.ts +17 -4
- package/src/console/routing/pools/routes.ts +30 -0
- package/src/console/routing/pools/store.ts +98 -8
- package/src/main.ts +16 -2
- package/src/network/outbound-fetch.ts +226 -365
- package/src/network/pool/agent.ts +8 -0
- package/src/network/pool/pool-state.ts +7 -0
- package/src/network/pool/selector.ts +110 -2
- package/src/network/pool-cooldown-store.ts +252 -0
- package/src/network/pool-health-machine.ts +5 -3
- package/src/network/pool-health.ts +36 -9
- package/src/observability/event-query.ts +57 -0
- package/src/observability/metrics.ts +15 -5
- package/src/observability/runtime-metrics.ts +30 -15
- package/src/persistence/health-event-flush.ts +2 -0
- package/src/persistence/schema.ts +19 -0
- package/src/persistence/usage-flush.ts +8 -3
- package/src/protocol/canonical-model.ts +1 -1
- package/src/protocol/dsml.ts +377 -0
- package/src/protocol/gateway-error.ts +0 -1
- package/src/{providers/provider-reasoning.ts → protocol/reasoning.ts} +10 -9
- package/src/protocol/request/chat.ts +7 -7
- package/src/protocol/request/codex.ts +2 -2
- package/src/protocol/request/gemini.ts +1 -1
- package/src/protocol/request/messages.ts +6 -6
- package/src/protocol/request/responses.ts +7 -7
- package/src/protocol/response/dsml.ts +12 -133
- package/src/{transport → protocol}/translation/attachment-integrity.ts +2 -2
- package/src/{transport → protocol}/translation/cache-controls.ts +1 -1
- package/src/{transport → protocol}/translation/capabilities.ts +2 -2
- package/src/{transport → protocol}/translation/normalize.ts +1 -1
- package/src/{transport → protocol}/translation/quirks.ts +1 -1
- package/src/{transport → protocol}/translation/search.ts +1 -1
- package/src/{transport → protocol}/translation/thinking.ts +23 -1
- package/src/{transport → protocol}/translation/tool-id.ts +1 -1
- package/src/{transport → protocol}/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream-parsing.ts +176 -0
- package/src/providers/adapters/compatible-adapter.ts +11 -2
- package/src/providers/authentication/oauth-refresh-service.ts +9 -5
- package/src/providers/default-registry.ts +21 -5
- package/src/providers/discovery/probe-phases.ts +16 -3
- package/src/providers/discovery/probing-service.ts +4 -1
- package/src/providers/integrations/anthropic.ts +1 -1
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -4
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +141 -8
- package/src/providers/integrations/buddy/buddy-checkin.ts +5 -1
- package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -82
- package/src/providers/integrations/buddy/buddy-identity.ts +25 -0
- package/src/providers/integrations/buddy/buddy-server-tools.ts +240 -0
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +36 -0
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +6 -31
- package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +19 -4
- package/src/providers/integrations/buddy/turing-device-token.ts +262 -0
- package/src/providers/integrations/buddy/workbuddy/provider.ts +2 -0
- package/src/providers/integrations/buddy/workbuddy/shared.ts +56 -3
- package/src/providers/integrations/claude/claude.ts +1 -1
- package/src/providers/integrations/cloudflareai/cloudflareai.ts +1 -1
- package/src/providers/integrations/codex/codex-errors.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +1 -1
- package/src/providers/integrations/commandcode/commandcode.ts +134 -104
- package/src/providers/integrations/commandcode/discovery.ts +6 -5
- package/src/providers/integrations/factory/factory.ts +1 -1
- package/src/providers/integrations/github/github.ts +1 -1
- package/src/providers/integrations/grok/grok.ts +1 -1
- package/src/providers/integrations/inception/provider.ts +91 -0
- package/src/providers/integrations/inferhub.ts +1 -1
- package/src/providers/integrations/kiro/kiro-request.ts +1 -1
- package/src/providers/integrations/mistral.ts +193 -19
- package/src/providers/integrations/opencode/fingerprint.ts +19 -2
- package/src/providers/integrations/opencode/prompt.ts +111 -0
- package/src/providers/integrations/opencode/provider.ts +101 -23
- package/src/providers/integrations/opencode/tool-definitions.ts +4 -4
- package/src/providers/integrations/qoder/qoder-checkin.ts +303 -0
- package/src/providers/integrations/qoder/qoder.ts +1641 -884
- package/src/providers/integrations/shared/install-id.ts +2 -1
- package/src/providers/integrations/trae/trae-cache.ts +110 -0
- package/src/providers/integrations/trae/trae-oauth.ts +29 -4
- package/src/providers/integrations/trae/trae-protocol.ts +2 -1
- package/src/providers/integrations/trae/trae.ts +106 -23
- package/src/providers/operations/account-error-classification.ts +1660 -0
- package/src/providers/operations/account-health-service.ts +621 -2097
- package/src/providers/operations/account-reset-service.ts +2 -0
- package/src/providers/provider-contracts.ts +0 -2
- package/src/providers/provider-metadata.ts +25 -4
- package/src/providers/provider-probe.ts +2 -2
- package/src/{console → providers}/quota/cache.ts +1 -1
- package/src/{console → providers}/quota/refresh.ts +5 -5
- package/src/security/abuse.ts +17 -0
- package/src/transport/dispatch/attempt-finalize.ts +114 -12
- package/src/transport/dispatch/attempt-loop.ts +3 -0
- package/src/transport/dispatch/empty-stream.ts +224 -217
- package/src/transport/dispatch/generation.ts +37 -55
- package/src/transport/dispatch/handler-deps.ts +32 -0
- package/src/transport/dispatch/leases.ts +9 -4
- package/src/transport/dispatch/proxy-request.ts +6 -30
- package/src/transport/dispatch/responses-compact.ts +22 -43
- package/src/transport/dispatch/streaming/finalize.ts +3 -0
- package/src/transport/dispatch/streaming/stream.ts +0 -8
- package/src/transport/dispatch/systemone.ts +22 -42
- package/src/transport/dispatch/token-count.ts +23 -39
- package/src/transport/dispatch/upstream.ts +1 -3
- package/src/transport/dispatch/websearch.ts +22 -41
- package/src/transport/failure-policy.ts +59 -178
- package/src/transport/guards/abuse-guard.ts +10 -0
- package/src/transport/guards/auth-guard.ts +0 -18
- package/src/transport/guards/error-lifecycle.ts +1 -1
- package/src/transport/guards/pipeline.ts +0 -6
- package/src/transport/guards/request-context.ts +18 -91
- package/src/transport/request/degradation.ts +20 -1
- package/src/transport/request/inflight.ts +4 -95
- package/src/transport/request/preparer.ts +6 -6
- package/src/transport/request/state.ts +66 -38
- package/src/transport/request/tenant-preferences.ts +1 -1
- package/src/transport/routing/route-catalog.ts +41 -31
- package/src/transport/routing/router.ts +2 -2
- package/src/transport/surface/messages/parse.ts +1 -1
- package/src/workers/buddy-growth-tasks.ts +346 -425
- package/src/workers/daily-checkin.ts +17 -6
- package/src/workers/quota-refresh-sweep.ts +2 -2
- package/src/workers/scheduler.ts +17 -1
- package/dist/dashboard/assets/BannedPage-Dskhyp0l.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage--SJhk7qQ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-C203VT-c.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BcS7kGG1.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-ChmiSLwO.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-ChmiSLwO.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-uujbBYwC.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-fKAWd-OQ.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-fKAWd-OQ.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DoOUthbG.js +0 -1
- package/dist/dashboard/assets/LoginPage-DoOUthbG.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-CA1kLs45.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-C5Z-UBz1.js +0 -1
- package/dist/dashboard/assets/OverviewPage-C5Z-UBz1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CFu1qGcT.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-CFu1qGcT.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CnlJZbxF.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-CnlJZbxF.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-B9mBTweh.js +0 -7
- package/dist/dashboard/assets/ProxyPage-B9mBTweh.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BueSWX5D.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-B0x00MVY.js +0 -1
- package/dist/dashboard/assets/SettingsPage-B0x00MVY.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-DQ9oDRpi.js +0 -1
- package/dist/dashboard/assets/SetupPage-DQ9oDRpi.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DrKeMcGN.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-qjuYtccc.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Be7aNviQ.js +0 -56
- package/dist/dashboard/assets/UsagePage-Be7aNviQ.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
- package/dist/dashboard/assets/app-BUSiYL6F.css +0 -2
- package/dist/dashboard/assets/app-BUSiYL6F.css.gz +0 -0
- package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
- package/dist/dashboard/assets/badge-Bkz_-RrM.js.gz +0 -0
- package/dist/dashboard/assets/banner-C-7y3VKV.js.gz +0 -0
- package/dist/dashboard/assets/card-BOPBuBzD.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-e-Mlk0FH.js.gz +0 -0
- package/dist/dashboard/assets/cog-CdWhsajs.js +0 -1
- package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
- package/dist/dashboard/assets/dialog-Iec_ywbD.js.gz +0 -0
- package/dist/dashboard/assets/drawer-Bs8MIIHc.js.gz +0 -0
- package/dist/dashboard/assets/entry-BRszm4_Z.js.gz +0 -0
- package/dist/dashboard/assets/entry-DOddwoc0.js.gz +0 -0
- package/dist/dashboard/assets/entry-DcobaKFu.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-BuDsrsPn.js.gz +0 -0
- package/dist/dashboard/assets/index-BeLRhsmt.js.gz +0 -0
- package/dist/dashboard/assets/inline-DToMICwl.js.gz +0 -0
- package/dist/dashboard/assets/input-WlyHWeG2.js.gz +0 -0
- package/dist/dashboard/assets/layout-DaJ5baJr.js.gz +0 -0
- package/dist/dashboard/assets/list-row-B3oSK13z.js.gz +0 -0
- package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js +0 -1
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
- package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Dulqgys5.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
- package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
- package/dist/dashboard/assets/select-BfZ3SCTM.js.gz +0 -0
- package/dist/dashboard/assets/settings--3hxSyry.js +0 -1
- package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
- package/dist/dashboard/assets/stack-B1w5BM0u.js.gz +0 -0
- package/dist/dashboard/assets/state-DhZBfWgx.js.gz +0 -0
- package/dist/dashboard/assets/switch-yD7D99Lg.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
- package/src/console/admin/model-abuse/contracts.ts +0 -38
- package/src/console/admin/model-abuse/routes.ts +0 -97
- package/src/security/model-abuse.ts +0 -704
- package/src/transport/guards/concurrency-guard.ts +0 -125
- /package/dist/dashboard/assets/{app-MdVfwraB.js → app-BaWd0puM.js} +0 -0
- /package/dist/dashboard/assets/{app-MdVfwraB.js.gz → app-BaWd0puM.js.gz} +0 -0
package/src/config/cooldowns.ts
CHANGED
|
@@ -168,6 +168,21 @@ export function resolveAccountDisabledRecoveryMs(): number {
|
|
|
168
168
|
return _disabledRecovery;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
let _poolProviderRateLimitCooldown: number | undefined;
|
|
172
|
+
/**
|
|
173
|
+
* Fallback cooldown for a provider-scoped egress 429 when the upstream named no
|
|
174
|
+
* window (default 1h). See `CARTETHYIA_POOL_PROVIDER_RATE_LIMIT_COOLDOWN_MS`.
|
|
175
|
+
* An upstream `retryAfterMs` always wins over this.
|
|
176
|
+
*/
|
|
177
|
+
export function resolvePoolProviderRateLimitCooldownMs(): number {
|
|
178
|
+
if (_poolProviderRateLimitCooldown === undefined)
|
|
179
|
+
_poolProviderRateLimitCooldown = readInt(
|
|
180
|
+
"CARTETHYIA_POOL_PROVIDER_RATE_LIMIT_COOLDOWN_MS",
|
|
181
|
+
CONFIG_SPEC.CARTETHYIA_POOL_PROVIDER_RATE_LIMIT_COOLDOWN_MS,
|
|
182
|
+
);
|
|
183
|
+
return _poolProviderRateLimitCooldown;
|
|
184
|
+
}
|
|
185
|
+
|
|
171
186
|
/**
|
|
172
187
|
* Resets all cached cooldown values. Call this in tests that override
|
|
173
188
|
* cooldown env-vars between test cases.
|
|
@@ -180,6 +195,7 @@ export function resetCooldownCacheForTests(): void {
|
|
|
180
195
|
_unclassifiedCooldown = undefined;
|
|
181
196
|
_escalationMaxCooldown = undefined;
|
|
182
197
|
_poolCooldown = undefined;
|
|
198
|
+
_poolProviderRateLimitCooldown = undefined;
|
|
183
199
|
_authRejectedCooldown = undefined;
|
|
184
200
|
_policyBlockCooldown = undefined;
|
|
185
201
|
_disabledRecovery = undefined;
|
package/src/config/providers.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* public origin.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { CONFIG_SPEC, readInt, readRequired, readText } from "./spec";
|
|
7
|
+
import { CONFIG_SPEC, readInt, readOptionalText, readRequired, readText } from "./spec";
|
|
8
8
|
|
|
9
9
|
/** Reads and normalizes the public OAuth origin. */
|
|
10
10
|
export function requirePublicOrigin(): string {
|
|
@@ -48,6 +48,18 @@ export function resolveKimiQuotaBaseUrl(): string {
|
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Operator override for the directory installation identities are persisted in.
|
|
53
|
+
* Read here so the only literal `process.env` for this value stays in config;
|
|
54
|
+
* the providers that stamp an install id consume the resolver instead.
|
|
55
|
+
*/
|
|
56
|
+
export function resolveInstallIdDir(): string | undefined {
|
|
57
|
+
return readOptionalText(
|
|
58
|
+
"CARTETHYIA_INSTALL_ID_DIR",
|
|
59
|
+
CONFIG_SPEC.CARTETHYIA_INSTALL_ID_DIR,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
/**
|
|
52
64
|
* Operator override for the OpenCode free-tier ids upstream refuses, or
|
|
53
65
|
* `undefined` when unset — meaning the bundled, measured list stands.
|
package/src/config/resources.ts
CHANGED
|
@@ -1,40 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Canonical resource policy: how much memory this process may use, and how
|
|
3
|
-
* much
|
|
3
|
+
* much room it must keep free.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* have explained what happened.
|
|
5
|
+
* The memory figure used to be decided in unrelated places — a host-wide
|
|
6
|
+
* `os.totalmem()` in the headroom guard, a fallback constant in
|
|
7
|
+
* `runtime-metrics.ts` — which meant a container limited to 512 MiB shed on
|
|
8
|
+
* the same threshold as a 4 GiB one and only reacted once the *host* was
|
|
9
|
+
* nearly full. On a 1-core / 2 GiB VPS that is the difference between
|
|
10
|
+
* shedding early and being OOM-killed, and the OOM kills the dashboard too —
|
|
11
|
+
* the one surface that would have explained what happened.
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* The transient-copies factor is why this is not simply "limit / body": a
|
|
20
|
-
* request body is held more than once between the socket read, the JSON parse,
|
|
21
|
-
* and the upstream re-serialization, so budgeting one copy per request
|
|
22
|
-
* understates the real hold by a factor of about two.
|
|
13
|
+
* How many requests may run at once is NOT decided here. That is admission's
|
|
14
|
+
* job: the tenant ceiling and each key's own limit, both set in Settings. A
|
|
15
|
+
* second, process-wide ceiling derived from memory sat below the number an
|
|
16
|
+
* operator could configure and refused requests that every configured limit
|
|
17
|
+
* still allowed, which is why it is gone.
|
|
23
18
|
*/
|
|
24
19
|
import { CONFIG_SPEC, readOptionalInt } from "./spec";
|
|
25
|
-
import { resolveMaxBodyBytes } from "./runtime";
|
|
26
20
|
|
|
27
21
|
/** Used when no limit is discoverable at all (native dev, macOS, Windows). */
|
|
28
22
|
export const FALLBACK_MEMORY_LIMIT_BYTES = 512 * 1024 * 1024;
|
|
29
23
|
|
|
30
|
-
/**
|
|
31
|
-
* Ceiling on concurrent in-flight requests per process.
|
|
32
|
-
*
|
|
33
|
-
* A hard ceiling even when memory would allow more: this process is one core,
|
|
34
|
-
* and past this point queueing delay, not memory, is what degrades. It also
|
|
35
|
-
* bounds every per-request structure that is sized by concurrency.
|
|
36
|
-
*/
|
|
37
|
-
export const MAX_PROCESS_CONCURRENCY = 64;
|
|
38
24
|
|
|
39
25
|
/**
|
|
40
26
|
* How many copies of a request body are resident at once.
|
|
@@ -52,35 +38,6 @@ export const REQUEST_BODY_BUDGET_FRACTION = 0.25;
|
|
|
52
38
|
/** Share of the memory limit kept free for allocation spikes and GC slack. */
|
|
53
39
|
export const MEMORY_HEADROOM_FRACTION = 0.2;
|
|
54
40
|
|
|
55
|
-
/**
|
|
56
|
-
* Concurrency this process should admit, derived from the memory limit and the
|
|
57
|
-
* body ceiling rather than guessed.
|
|
58
|
-
*
|
|
59
|
-
* Worked examples, which are the point of having one formula:
|
|
60
|
-
* - 2 GiB limit, 4 MiB bodies → 64 (the ceiling; memory is not the binder)
|
|
61
|
-
* - 512 MiB limit, 4 MiB bodies → 16
|
|
62
|
-
* - 256 MiB limit, 16 MiB bodies → 2
|
|
63
|
-
*
|
|
64
|
-
* An explicit override is honoured, clamped to 1..MAX_PROCESS_CONCURRENCY. It
|
|
65
|
-
* is the operator's call and is never silently raised back to the derived
|
|
66
|
-
* value; exceeding the derived figure is a deliberate choice, so it is
|
|
67
|
-
* reported at the call site rather than corrected here.
|
|
68
|
-
*/
|
|
69
|
-
export function deriveProcessConcurrency(options: {
|
|
70
|
-
readonly memoryLimitBytes: number;
|
|
71
|
-
readonly maxBodyBytes: number;
|
|
72
|
-
readonly override?: number | undefined;
|
|
73
|
-
}): number {
|
|
74
|
-
if (options.override !== undefined) {
|
|
75
|
-
if (!Number.isInteger(options.override)) return 1;
|
|
76
|
-
return Math.min(MAX_PROCESS_CONCURRENCY, Math.max(1, options.override));
|
|
77
|
-
}
|
|
78
|
-
const body = Math.max(1, options.maxBodyBytes);
|
|
79
|
-
const perRequest = REQUEST_BODY_TRANSIENT_COPIES * body;
|
|
80
|
-
const budget = Math.max(1, Math.floor(options.memoryLimitBytes * REQUEST_BODY_BUDGET_FRACTION));
|
|
81
|
-
const derived = Math.floor(budget / perRequest);
|
|
82
|
-
return Math.min(MAX_PROCESS_CONCURRENCY, Math.max(1, derived));
|
|
83
|
-
}
|
|
84
41
|
|
|
85
42
|
/**
|
|
86
43
|
* Bytes kept free below the memory limit.
|
|
@@ -94,16 +51,6 @@ export function deriveMemoryHeadroomBytes(memoryLimitBytes: number): number {
|
|
|
94
51
|
return Math.ceil(memoryLimitBytes * MEMORY_HEADROOM_FRACTION);
|
|
95
52
|
}
|
|
96
53
|
|
|
97
|
-
/**
|
|
98
|
-
* The optional operator override, read through the config spec so "unset" and
|
|
99
|
-
* "out of range" are treated the way every other knob treats them.
|
|
100
|
-
*/
|
|
101
|
-
export function resolveProcessConcurrencyOverride(): number | undefined {
|
|
102
|
-
return readOptionalInt(
|
|
103
|
-
"CARTETHYIA_PROCESS_CONCURRENCY_LIMIT",
|
|
104
|
-
CONFIG_SPEC.CARTETHYIA_PROCESS_CONCURRENCY_LIMIT,
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
54
|
|
|
108
55
|
/**
|
|
109
56
|
* Slack added on top of every key's concurrency limit.
|
|
@@ -121,17 +68,3 @@ export function resolveConcurrencyTolerance(): number {
|
|
|
121
68
|
return value === undefined ? 0 : Math.max(0, value);
|
|
122
69
|
}
|
|
123
70
|
|
|
124
|
-
/**
|
|
125
|
-
* The effective concurrency for this process.
|
|
126
|
-
*
|
|
127
|
-
* Takes the limit as an argument rather than resolving it here: the limit
|
|
128
|
-
* owner is `resolveMemoryLimitBytes()` in `observability/runtime-metrics.ts`,
|
|
129
|
-
* and duplicating that resolution would let the two drift.
|
|
130
|
-
*/
|
|
131
|
-
export function resolveProcessConcurrency(memoryLimitBytes: number): number {
|
|
132
|
-
return deriveProcessConcurrency({
|
|
133
|
-
memoryLimitBytes,
|
|
134
|
-
maxBodyBytes: resolveMaxBodyBytes(),
|
|
135
|
-
override: resolveProcessConcurrencyOverride(),
|
|
136
|
-
});
|
|
137
|
-
}
|
package/src/config/security.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Security resolvers: the application encryption key
|
|
3
|
-
* ceiling
|
|
2
|
+
* Security resolvers: the application encryption key and the abuse-protection
|
|
3
|
+
* ceiling.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { CONFIG_SPEC, readInt, readRequired } from "./spec";
|
|
@@ -16,42 +16,6 @@ export function resolveIpRateLimit(): number {
|
|
|
16
16
|
return readInt("IP_RATE_MAX_PER_WINDOW", CONFIG_SPEC.IP_RATE_MAX_PER_WINDOW);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
/** Invalid-model attempts inside the window that ban a client address. */
|
|
20
|
-
export function resolveModelStrikeThreshold(): number {
|
|
21
|
-
return readInt(
|
|
22
|
-
"CARTETHYIA_MODEL_STRIKE_THRESHOLD",
|
|
23
|
-
CONFIG_SPEC.CARTETHYIA_MODEL_STRIKE_THRESHOLD,
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Sliding window (ms) that keeps an invalid-model attempt countable. */
|
|
28
|
-
export function resolveModelStrikeWindowMs(): number {
|
|
29
|
-
return readInt(
|
|
30
|
-
"CARTETHYIA_MODEL_STRIKE_WINDOW_MS",
|
|
31
|
-
CONFIG_SPEC.CARTETHYIA_MODEL_STRIKE_WINDOW_MS,
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** How long (ms) a model-abuse ban lasts before it lapses on its own. */
|
|
36
|
-
export function resolveModelBanTtlMs(): number {
|
|
37
|
-
return readInt("CARTETHYIA_MODEL_BAN_TTL_MS", CONFIG_SPEC.CARTETHYIA_MODEL_BAN_TTL_MS);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* The longest model-abuse ban the console will record, however long a request
|
|
42
|
-
* asks for.
|
|
43
|
-
*
|
|
44
|
-
* A separate knob from the ban TTL because the two are different decisions:
|
|
45
|
-
* the TTL is what a graduated ban gets, this is the ceiling on what an
|
|
46
|
-
* operator may ask for by hand. Raising one was never meant to raise the other.
|
|
47
|
-
*/
|
|
48
|
-
export function resolveModelBanMaxTtlMs(): number {
|
|
49
|
-
return readInt(
|
|
50
|
-
"CARTETHYIA_MODEL_BAN_MAX_TTL_MS",
|
|
51
|
-
CONFIG_SPEC.CARTETHYIA_MODEL_BAN_MAX_TTL_MS,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
19
|
/** Reads the application encryption key without caching it. */
|
|
56
20
|
export function requireEncryptionKeyEnv(): string {
|
|
57
21
|
return readRequired("CARTETHYIA_ENCRYPTION_KEY", CONFIG_SPEC.CARTETHYIA_ENCRYPTION_KEY);
|
package/src/config/spec.ts
CHANGED
|
@@ -90,10 +90,9 @@ export const CONFIG_SPEC = {
|
|
|
90
90
|
CARTETHYIA_SERVER_MAX_BODY_BYTES: {
|
|
91
91
|
kind: "int",
|
|
92
92
|
// A 500K-token context is roughly 2 MB of text, so 4 MB admits every real
|
|
93
|
-
// request while halving the worst-case hold.
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
// larger payloads raises this.
|
|
93
|
+
// request while halving the worst-case hold. How many may be in flight is
|
|
94
|
+
// admission's job, so an operator with larger payloads raises this and
|
|
95
|
+
// sets the ceiling in Settings.
|
|
97
96
|
default: 4 * 1024 * 1024,
|
|
98
97
|
min: 1024,
|
|
99
98
|
max: Number.MAX_SAFE_INTEGER,
|
|
@@ -119,7 +118,6 @@ export const CONFIG_SPEC = {
|
|
|
119
118
|
// Optional-int, not int-with-a-zero-default: "unset" and "explicitly zero"
|
|
120
119
|
// must not be the same thing. Zero is not a valid ceiling, so a default of 0
|
|
121
120
|
// would need a second sentinel to mean "derive".
|
|
122
|
-
CARTETHYIA_PROCESS_CONCURRENCY_LIMIT: { kind: "optional-int", min: 1, max: 64 },
|
|
123
121
|
// Slack above each key's concurrency limit. Effective ceiling is
|
|
124
122
|
// limit + tolerance; only requests past it are refused.
|
|
125
123
|
CARTETHYIA_CONCURRENCY_TOLERANCE: { kind: "optional-int", min: 0, max: 1024 },
|
|
@@ -139,6 +137,7 @@ export const CONFIG_SPEC = {
|
|
|
139
137
|
// requires a loopback peer and a timing-safe token match; unset = the route
|
|
140
138
|
// does not exist.
|
|
141
139
|
CARTETHYIA_DRAIN_TOKEN: { kind: "optional-text" },
|
|
140
|
+
CARTETHYIA_INSTALL_ID_DIR: { kind: "optional-text" },
|
|
142
141
|
|
|
143
142
|
// Network policy (trusted proxy + SSRF)
|
|
144
143
|
TRUSTED_PROXY_CIDRS: { kind: "list" },
|
|
@@ -242,6 +241,18 @@ export const CONFIG_SPEC = {
|
|
|
242
241
|
},
|
|
243
242
|
CARTETHYIA_ACCOUNT_COOLDOWN_ESCALATION_MAX_MS: { kind: "int", default: 3_600_000, min: 0, max: 86_400_000 },
|
|
244
243
|
CARTETHYIA_POOL_COOLDOWN_MS: { kind: "int", default: 120_000, min: 0, max: 86_400_000 },
|
|
244
|
+
/**
|
|
245
|
+
* A provider-scoped egress cooldown (a 429 the pool itself earned) when the
|
|
246
|
+
* upstream named no window. Default 1h: an egress that exhausted a
|
|
247
|
+
* request-counted allowance stays refused long past minute 15, and a short
|
|
248
|
+
* default sent the pool back into rotation to be refused again.
|
|
249
|
+
*/
|
|
250
|
+
CARTETHYIA_POOL_PROVIDER_RATE_LIMIT_COOLDOWN_MS: {
|
|
251
|
+
kind: "int",
|
|
252
|
+
default: 3_600_000,
|
|
253
|
+
min: 0,
|
|
254
|
+
max: 86_400_000,
|
|
255
|
+
},
|
|
245
256
|
CARTETHYIA_ACCOUNT_AUTH_REJECTED_COOLDOWN_MS: { kind: "int", default: 300_000, min: 0, max: 86_400_000 },
|
|
246
257
|
CARTETHYIA_ACCOUNT_POLICY_BLOCK_COOLDOWN_MS: { kind: "int", default: 300_000, min: 0, max: 86_400_000 },
|
|
247
258
|
CARTETHYIA_ACCOUNT_DISABLED_RECOVERY_MS: { kind: "int", default: 86_400_000, min: 0, max: 604_800_000 },
|
|
@@ -252,24 +263,6 @@ export const CONFIG_SPEC = {
|
|
|
252
263
|
|
|
253
264
|
// Security (encryption key, public origin, abuse ceiling)
|
|
254
265
|
IP_RATE_MAX_PER_WINDOW: { kind: "int", default: 240, min: 1, max: Number.MAX_SAFE_INTEGER },
|
|
255
|
-
// Invalid-model attempts inside the sliding window that ban the client
|
|
256
|
-
// address, how long an attempt stays countable, and how long the ban lasts.
|
|
257
|
-
// A valid request does not reset the window.
|
|
258
|
-
CARTETHYIA_MODEL_STRIKE_THRESHOLD: { kind: "int", default: 10, min: 1, max: 100 },
|
|
259
|
-
CARTETHYIA_MODEL_STRIKE_WINDOW_MS: { kind: "int", default: 1_800_000, min: 1_000, max: 86_400_000 },
|
|
260
|
-
CARTETHYIA_MODEL_BAN_TTL_MS: { kind: "int", default: 21_600_000, min: 1_000, max: 604_800_000 },
|
|
261
|
-
/**
|
|
262
|
-
* The longest ban the console will record, however long a request asks for.
|
|
263
|
-
* The "Permanent" choice is a very long TTL rather than an unbounded one,
|
|
264
|
-
* and this is what keeps a typo in a duration from parking a caller for a
|
|
265
|
-
* decade.
|
|
266
|
-
*/
|
|
267
|
-
CARTETHYIA_MODEL_BAN_MAX_TTL_MS: {
|
|
268
|
-
kind: "int",
|
|
269
|
-
default: 604_800_000,
|
|
270
|
-
min: 1_000,
|
|
271
|
-
max: 604_800_000,
|
|
272
|
-
},
|
|
273
266
|
CARTETHYIA_ENCRYPTION_KEY: {
|
|
274
267
|
kind: "required",
|
|
275
268
|
error:
|
|
@@ -26,11 +26,11 @@ import type { NetworkPoolSelector } from "../network/pool/selector";
|
|
|
26
26
|
import type { TelemetryBatchBuffer } from "../observability/telemetry-buffer";
|
|
27
27
|
import type { OAuthRefreshService } from "../providers/authentication/oauth-refresh-service";
|
|
28
28
|
import type { ApiKeyAdmissionService } from "../security/admission/service";
|
|
29
|
-
import type { ModelStrikeService } from "../security/model-abuse";
|
|
30
29
|
import type { RedisClient } from "../persistence/redis";
|
|
31
30
|
import { CliToolMappingStore } from "./cli-tools/store";
|
|
32
31
|
import { CliToolService } from "./cli-tools/service";
|
|
33
32
|
import { DrizzleCliToolSecretSource } from "./cli-tools/secret-source";
|
|
33
|
+
import type { ShareLinkStore } from "../persistence/stores/share-store";
|
|
34
34
|
import type { ProviderRegistry } from "../providers/provider-registry";
|
|
35
35
|
import type { BundledProviderCatalog } from "../providers/operations/provider-catalog-service";
|
|
36
36
|
import type { ValidatedNetworkBindingFactory } from "../network/pool/resolver";
|
|
@@ -65,13 +65,13 @@ export interface ConsoleApiCompositionDeps {
|
|
|
65
65
|
readonly networkBindingFactory: ValidatedNetworkBindingFactory;
|
|
66
66
|
readonly redis: RedisClient | undefined;
|
|
67
67
|
readonly oauthRefreshService: OAuthRefreshService;
|
|
68
|
-
readonly admissionService: Pick<ApiKeyAdmissionService, "purgeKey">;
|
|
69
68
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
69
|
+
* Share-link store shared with the public share router, so both surfaces
|
|
70
|
+
* wrap the same table through one instance. Optional: absent means the
|
|
71
|
+
* console builds its own store from `db`.
|
|
73
72
|
*/
|
|
74
|
-
readonly
|
|
73
|
+
readonly shareStore?: ShareLinkStore;
|
|
74
|
+
readonly admissionService: Pick<ApiKeyAdmissionService, "purgeKey">;
|
|
75
75
|
readonly readRoutingAccountInflight?:
|
|
76
76
|
| ((
|
|
77
77
|
providerId: string,
|
|
@@ -190,11 +190,11 @@ export function createConsoleRouter(deps: ConsoleApiCompositionDeps): Elysia {
|
|
|
190
190
|
poolSelector,
|
|
191
191
|
telemetryBuffer,
|
|
192
192
|
...(deps.stateStore ? { stateStore: deps.stateStore } : {}),
|
|
193
|
+
...(deps.shareStore ? { shareStore: deps.shareStore } : {}),
|
|
193
194
|
providerRegistry: deps.providerRegistry,
|
|
194
195
|
bundledModelCatalog: deps.bundledModelCatalog,
|
|
195
196
|
networkBindingFactory: deps.networkBindingFactory,
|
|
196
197
|
admissionService: deps.admissionService,
|
|
197
|
-
...(deps.modelStrikes ? { modelStrikes: deps.modelStrikes } : {}),
|
|
198
198
|
readRoutingAccountInflight: deps.readRoutingAccountInflight,
|
|
199
199
|
...(deps.flushBuffers ? { flushBuffers: deps.flushBuffers } : {}),
|
|
200
200
|
credentialService,
|
|
@@ -34,7 +34,6 @@ import { DrizzleObservabilityStore } from "./observability/store";
|
|
|
34
34
|
import { createLiveRoutes } from "./observability/live";
|
|
35
35
|
import { createLogRoutes } from "./observability/logs";
|
|
36
36
|
import { createAuditRoutes } from "./audit/routes";
|
|
37
|
-
import { createModelAbuseRoutes } from "./admin/model-abuse/routes";
|
|
38
37
|
import { DrizzleAuditReadStore } from "./audit/store";
|
|
39
38
|
import { createPerformanceRoutes } from "./observability/performance";
|
|
40
39
|
import { createUpdateRoutes } from "./updates/routes";
|
|
@@ -47,6 +46,7 @@ import type { ConsoleCredentialService } from "./auth/credentials";
|
|
|
47
46
|
import { resolveTelemetryRetentionDays } from "../config/telemetry";
|
|
48
47
|
import { payloadRetentionMs, resolvePayloadCapture } from "../observability/payload-capture";
|
|
49
48
|
import { TELEMETRY_EVENT_DIR } from "../observability/event-log";
|
|
49
|
+
import { providerUsageMatrix } from "../observability/event-query";
|
|
50
50
|
|
|
51
51
|
import { InMemoryOAuthFlowStore, OAuthFlowStore } from "../providers/authentication/oauth-flow-store";
|
|
52
52
|
import { OAuthCallbackListener } from "./providers/oauth/callback-listener";
|
|
@@ -56,7 +56,10 @@ import {
|
|
|
56
56
|
} from "../providers/operations/provider-credential-service";
|
|
57
57
|
import { syncByokProvider } from "../providers/operations/provider-catalog-service";
|
|
58
58
|
import { DrizzleApiKeyStore } from "../persistence/stores/api-key-store";
|
|
59
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
DrizzleShareLinkStore,
|
|
61
|
+
type ShareLinkStore,
|
|
62
|
+
} from "../persistence/stores/share-store";
|
|
60
63
|
import { createShareUsagePort } from "./share/share-usage";
|
|
61
64
|
import { resolveSsrfPolicy } from "../config/network";
|
|
62
65
|
import { createValidatedFetch } from "../network/outbound-fetch";
|
|
@@ -65,7 +68,6 @@ import type { CliToolService } from "./cli-tools/service";
|
|
|
65
68
|
import type { NetworkPoolSelector } from "../network/pool/selector";
|
|
66
69
|
import type { LiveInFlightSource } from "./observability/live";
|
|
67
70
|
import type { ApiKeyAdmissionService } from "../security/admission/service";
|
|
68
|
-
import type { ModelStrikeService } from "../security/model-abuse";
|
|
69
71
|
import type { TelemetryBatchBuffer } from "../observability/telemetry-buffer";
|
|
70
72
|
import type { OAuthRefreshService } from "../providers/authentication/oauth-refresh-service";
|
|
71
73
|
import { resolveRedisBackend } from "../persistence/redis";
|
|
@@ -101,9 +103,13 @@ export interface ConsoleDomainContext {
|
|
|
101
103
|
};
|
|
102
104
|
/** Owns the live in-flight gauge the console surfaces. */
|
|
103
105
|
readonly stateStore?: LiveInFlightSource;
|
|
106
|
+
/**
|
|
107
|
+
* Share-link store shared with the public share router, so both surfaces
|
|
108
|
+
* wrap the same table through one instance. Falls back to a fresh store
|
|
109
|
+
* when absent (reduced compositions, tests).
|
|
110
|
+
*/
|
|
111
|
+
readonly shareStore?: ShareLinkStore;
|
|
104
112
|
readonly admissionService: Pick<ApiKeyAdmissionService, "purgeKey">;
|
|
105
|
-
/** Graduated model-abuse strikes: list and lift bans from the console. */
|
|
106
|
-
readonly modelStrikes?: Pick<ModelStrikeService, "listBans" | "unban" | "ban">;
|
|
107
113
|
readonly readRoutingAccountInflight?:
|
|
108
114
|
| ((
|
|
109
115
|
providerId: string,
|
|
@@ -229,7 +235,7 @@ export function registerConsoleDomains(
|
|
|
229
235
|
const runtimeSettingsStore = new DrizzleRuntimeSettingsStore(ctx.db, resolveRedisBackend(ctx.redis));
|
|
230
236
|
const modelRoutingStore = new DrizzleModelRoutingStore(ctx.db);
|
|
231
237
|
const apiKeyStore = new DrizzleApiKeyStore(ctx.db);
|
|
232
|
-
const shareStore = new DrizzleShareLinkStore(ctx.db);
|
|
238
|
+
const shareStore = ctx.shareStore ?? new DrizzleShareLinkStore(ctx.db);
|
|
233
239
|
const shareActivity = createShareUsagePort({ db: ctx.db, redis: ctx.redis });
|
|
234
240
|
|
|
235
241
|
|
|
@@ -259,12 +265,6 @@ export function registerConsoleDomains(
|
|
|
259
265
|
resolveConfigKey: () => inferhubKeys.read(),
|
|
260
266
|
keyStore: inferhubKeys,
|
|
261
267
|
}));
|
|
262
|
-
if (ctx.modelStrikes) {
|
|
263
|
-
console.use(createModelAbuseRoutes({
|
|
264
|
-
strikes: ctx.modelStrikes,
|
|
265
|
-
accessResolver: ctx.accessResolver,
|
|
266
|
-
}));
|
|
267
|
-
}
|
|
268
268
|
console.use(createProviderCatalogRoutes({
|
|
269
269
|
store: providerCatalogStore,
|
|
270
270
|
accessResolver: ctx.accessResolver,
|
|
@@ -280,6 +280,13 @@ export function registerConsoleDomains(
|
|
|
280
280
|
aliases: await modelRoutingStore.listAliases(tenantId),
|
|
281
281
|
combos: await modelRoutingStore.listCombos(tenantId),
|
|
282
282
|
}),
|
|
283
|
+
providerUsageMatrix: (tenantId, providerId, days) =>
|
|
284
|
+
providerUsageMatrix(
|
|
285
|
+
ctx.db,
|
|
286
|
+
tenantId,
|
|
287
|
+
providerId,
|
|
288
|
+
new Date(Date.now() - days * 86_400_000),
|
|
289
|
+
),
|
|
283
290
|
}));
|
|
284
291
|
console.use(createProviderDetailRoutes({
|
|
285
292
|
store: providerDetailStore,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* segment. `mapProviderRow` is the single `providers` row projection.
|
|
10
10
|
*/
|
|
11
11
|
import { isBundledProviderId } from "../../../providers/provider-contracts";
|
|
12
|
+
import { BUILTIN_DEFAULT_ENDPOINTS } from "../../../protocol/codecs";
|
|
12
13
|
|
|
13
14
|
import { providerDisplayName, providerServiceKinds } from "../../../providers/provider-metadata";
|
|
14
15
|
import type { CompatibilityProfile } from "../../../providers/provider-metadata";
|
|
@@ -26,12 +27,7 @@ export interface ProviderDiscoveryLookup {
|
|
|
26
27
|
hasModelDiscovery(providerId: string): boolean;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export const DEFAULT_ENDPOINT_BY_WIRE_FAMILY: Record<WireFamily, string> =
|
|
30
|
-
chat: "/v1/chat/completions",
|
|
31
|
-
responses: "/v1/responses",
|
|
32
|
-
messages: "/v1/messages",
|
|
33
|
-
gemini: "/api/llm/g/v1/generate",
|
|
34
|
-
};
|
|
30
|
+
export const DEFAULT_ENDPOINT_BY_WIRE_FAMILY: Record<WireFamily, string> = BUILTIN_DEFAULT_ENDPOINTS;
|
|
35
31
|
export function endpointPathForProviderModel(
|
|
36
32
|
wireFamily: WireFamily,
|
|
37
33
|
compatibilityProfile: unknown,
|
|
@@ -557,6 +557,14 @@ export interface ProviderAccountExport {
|
|
|
557
557
|
accessToken: string;
|
|
558
558
|
/** Decrypted OAuth refresh token, omitted when the account has none. */
|
|
559
559
|
refreshToken?: string;
|
|
560
|
+
/**
|
|
561
|
+
* Non-secret upstream auth configuration carried on the account (the
|
|
562
|
+
* upstream user id, provisioned region, auth method, profile). Re-import
|
|
563
|
+
* must restore it: Trae refresh is addressed per upstream user id, and the
|
|
564
|
+
* data-plane host is region-scoped, so dropping it breaks re-imported
|
|
565
|
+
* accounts on their first refresh or dispatch.
|
|
566
|
+
*/
|
|
567
|
+
authState?: Readonly<Record<string, unknown>>;
|
|
560
568
|
createdAt: string;
|
|
561
569
|
/** Live routing admission count at export time; absent when unavailable. */
|
|
562
570
|
inflight?: number;
|
|
@@ -771,6 +779,17 @@ export interface ProviderCatalogStore {
|
|
|
771
779
|
syncModels(tenantId: string, providerId: string): Promise<{ synced: number }>;
|
|
772
780
|
listAccounts(tenantId: string, providerId: string): Promise<readonly ProviderAccountResponse[]>;
|
|
773
781
|
listAllAccounts(tenantId: string): Promise<readonly ProviderAccountResponse[]>;
|
|
782
|
+
/**
|
|
783
|
+
* Non-secret upstream auth configuration for the given owned accounts,
|
|
784
|
+
* keyed by account id. Used by the account-export route so a re-import can
|
|
785
|
+
* restore the upstream user id / region that refresh and dispatch are
|
|
786
|
+
* addressed by; never surfaced on the read models.
|
|
787
|
+
*/
|
|
788
|
+
listAccountAuthStates(
|
|
789
|
+
tenantId: string,
|
|
790
|
+
providerId: string,
|
|
791
|
+
accountIds: readonly string[],
|
|
792
|
+
): Promise<Readonly<Record<string, Readonly<Record<string, unknown>>>>>;
|
|
774
793
|
createAccount(
|
|
775
794
|
tenantId: string,
|
|
776
795
|
providerId: string,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
requireTenantAnyScope,
|
|
18
18
|
} from "../../shared/errors";
|
|
19
19
|
import type { AccessDecision } from "../../../security/access-control";
|
|
20
|
+
import type { ProviderUsageDayRow } from "../../../observability/event-query";
|
|
20
21
|
import { isBundledProviderId } from "../../../providers/provider-contracts";
|
|
21
22
|
import type { ProviderRegistry } from "../../../providers/provider-registry";
|
|
22
23
|
import { getProviderClientVersion, getRouteIdentityVersions } from "../../../providers/operations/client-versions";
|
|
@@ -146,6 +147,16 @@ export interface ProviderCatalogConfig {
|
|
|
146
147
|
readonly members: readonly string[];
|
|
147
148
|
}[];
|
|
148
149
|
}>;
|
|
150
|
+
/**
|
|
151
|
+
* Day × model usage aggregate feeding the provider's Activity Matrix card.
|
|
152
|
+
* Optional so hosts without telemetry reads (and existing test doubles) keep
|
|
153
|
+
* the route returning an empty matrix instead of failing.
|
|
154
|
+
*/
|
|
155
|
+
readonly providerUsageMatrix?: (
|
|
156
|
+
tenantId: string,
|
|
157
|
+
providerId: string,
|
|
158
|
+
days: number,
|
|
159
|
+
) => Promise<readonly ProviderUsageDayRow[]>;
|
|
149
160
|
/**
|
|
150
161
|
* Resolves the stored credential and optional OAuth refresh token for export,
|
|
151
162
|
* decrypting them without applying provider-specific dispatch codecs.
|
|
@@ -249,6 +260,19 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
249
260
|
throw new ConsoleDomainError("provider_not_found", 404, `Provider ${providerId} not found`);
|
|
250
261
|
return attachProviderCapabilities(sanitizeProviderResponse(rec, true), config.providerRegistry);
|
|
251
262
|
},
|
|
263
|
+
async getUsageMatrix(
|
|
264
|
+
access: AccessDecision | undefined,
|
|
265
|
+
providerId: string,
|
|
266
|
+
days: number,
|
|
267
|
+
): Promise<{ readonly days: number; readonly rows: readonly ProviderUsageDayRow[] }> {
|
|
268
|
+
const a = requireTenantAnyScope(access, PROVIDER_READ_SCOPES);
|
|
269
|
+
// Bounded window even on a hostile query string: the card reads a
|
|
270
|
+
// calendar, not history, and the aggregate scan cost grows with `days`.
|
|
271
|
+
const bounded = Math.min(Math.max(Math.trunc(days) || 1, 1), 366);
|
|
272
|
+
if (!config.providerUsageMatrix) return { days: bounded, rows: [] };
|
|
273
|
+
const rows = await config.providerUsageMatrix(a.tenantId, providerId, bounded);
|
|
274
|
+
return { days: bounded, rows };
|
|
275
|
+
},
|
|
252
276
|
async createProvider(
|
|
253
277
|
access: AccessDecision | undefined,
|
|
254
278
|
request: CreateProviderRequest,
|
|
@@ -639,6 +663,11 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
639
663
|
const selected = owned.filter(
|
|
640
664
|
(account) => wanted.has(account.id) && account.tenantId === a.tenantId,
|
|
641
665
|
);
|
|
666
|
+
const authStates = await config.store.listAccountAuthStates(
|
|
667
|
+
a.tenantId,
|
|
668
|
+
providerId,
|
|
669
|
+
selected.map((account) => account.id),
|
|
670
|
+
);
|
|
642
671
|
const accounts = await Promise.all(
|
|
643
672
|
selected.map(async (account): Promise<ProviderAccountExport> => {
|
|
644
673
|
let accessToken = "";
|
|
@@ -661,6 +690,15 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
661
690
|
status: account.status,
|
|
662
691
|
accessToken,
|
|
663
692
|
...(refreshToken === undefined ? {} : { refreshToken }),
|
|
693
|
+
// Non-secret auth state is part of the credential export: without
|
|
694
|
+
// it a re-import loses the upstream user id and region that Trae
|
|
695
|
+
// refresh and dispatch are addressed by.
|
|
696
|
+
...(() => {
|
|
697
|
+
const authState = authStates[account.id];
|
|
698
|
+
return authState !== undefined && Object.keys(authState).length > 0
|
|
699
|
+
? { authState }
|
|
700
|
+
: {};
|
|
701
|
+
})(),
|
|
664
702
|
...(account.inflight === undefined ? {} : { inflight: account.inflight }),
|
|
665
703
|
createdAt: account.createdAt,
|
|
666
704
|
...(account.cooldownUntil ? { cooldownUntil: account.cooldownUntil } : {}),
|
|
@@ -147,6 +147,9 @@ const deleteModelBody = t.Object({
|
|
|
147
147
|
const bulkDeleteModelsBody = t.Object({
|
|
148
148
|
items: t.Array(deleteModelBody, { minItems: 1, maxItems: 1000 }),
|
|
149
149
|
});
|
|
150
|
+
const usageMatrixQuery = t.Object({
|
|
151
|
+
days: t.Optional(t.Integer({ minimum: 1, maximum: 366 })),
|
|
152
|
+
});
|
|
150
153
|
export function createProviderCatalogRoutes(config: ProviderCatalogConfig): Elysia {
|
|
151
154
|
const factory = createProviderCatalogOperations(config);
|
|
152
155
|
const modelFactory = createModelCatalogOperations(config);
|
|
@@ -191,6 +194,17 @@ export function createProviderCatalogRoutes(config: ProviderCatalogConfig): Elys
|
|
|
191
194
|
.get("/:providerId", async ({ request, params }) => {
|
|
192
195
|
return await factory.getProviderDetail(config.accessResolver(request), params.providerId);
|
|
193
196
|
})
|
|
197
|
+
.get(
|
|
198
|
+
"/:providerId/usage/matrix",
|
|
199
|
+
{ query: usageMatrixQuery },
|
|
200
|
+
async ({ request, params, query }) => {
|
|
201
|
+
return await factory.getUsageMatrix(
|
|
202
|
+
config.accessResolver(request),
|
|
203
|
+
params.providerId,
|
|
204
|
+
query.days ?? 30,
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
)
|
|
194
208
|
.post("/", { body: createProviderBody }, async ({ request, body, set }) => {
|
|
195
209
|
set.status = 201;
|
|
196
210
|
return await factory.createProvider(
|