cartethyia 2.7.8 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -3
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js +1 -0
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-CXsaOAO2.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js +1 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-CgoPc5B9.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-D9bFQern.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js +1 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BwNC_-Cx.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-febLU4r1.js} +1 -1
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-M6fvR5Wz.js} +1 -1
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js +56 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js +1 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
- package/dist/dashboard/assets/{assert-CidQVoNo.js → assert-CqTLw5e7.js} +1 -1
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-C83EqLB0.js} +1 -1
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
- package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-D1xn7pIe.js} +1 -1
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/favicon.webp +0 -0
- package/dist/dashboard/assets/brand/login_register.webp +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
- package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/og_image.webp +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
- package/dist/dashboard/assets/{card-_HwAEqFF.js → card-DU3Lq5Lm.js} +1 -1
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js +1 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CEL5Sg_J.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-BjFR5UHy.js} +1 -1
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-HvBW0AjI.js} +1 -1
- package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
- package/dist/dashboard/assets/{entry-COaC-irN.js → entry-CECVELyy.js} +1 -1
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js +1 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-DLW4i8zK.js} +1 -1
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/{index-CgKmFngv.js → index-1-fCqe64.js} +2 -2
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2BirKry.js → inline-DGvU2oLK.js} +1 -1
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/{input-D-qV9dKY.js → input-iNnO1X2K.js} +1 -1
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-0q8UGypb.js} +1 -1
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-C0w2bQjJ.js} +1 -1
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/{live-DGizF06-.js → live-M5aJ_xCa.js} +1 -1
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
- package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
- package/dist/dashboard/assets/{network-C9UE-bOr.js → network-BKeQGT3K.js} +1 -1
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-CivAk3da.js} +1 -1
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-CpzvI5ep.js} +1 -1
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
- package/dist/dashboard/assets/providers/devin.svg +9 -0
- package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
- package/dist/dashboard/assets/providers/windsurf.svg +10 -0
- package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
- package/dist/dashboard/assets/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
- package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js +1 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/{select-BCOcv8WK.js → select-CXbck0jG.js} +2 -2
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js +1 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DggRiCxE.js} +1 -1
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/{state-DXbGTx7N.js → state-ClFzfbYp.js} +1 -1
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/story/25817331.webp +0 -0
- package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
- package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
- package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
- package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
- package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
- package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
- package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
- package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-CTIFxKqS.js} +1 -1
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BClG3yWj.js} +1 -1
- package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.webp +0 -0
- package/dist/dashboard/console-backgrounds_initial.webp +0 -0
- package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +59 -56
- package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
- package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
- package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/package.json +1 -1
- package/src/boot/app.ts +10 -13
- package/src/boot/dependencies.ts +12 -4
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/cooldowns.ts +1 -1
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +20 -6
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +50 -31
- package/src/console/auth/account-reset.ts +7 -9
- package/src/console/backup/contracts.ts +4 -0
- package/src/console/backup/export-endpoint.ts +7 -5
- package/src/console/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -2
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +93 -0
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -0
- package/src/console/providers/catalog/discover-constants.ts +6 -0
- package/src/console/providers/catalog/model-operations.ts +122 -10
- package/src/console/providers/catalog/provider-operations.ts +116 -8
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +65 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +249 -4
- package/src/console/quota/account-quota-global.ts +97 -19
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +6 -18
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/operations.ts +31 -46
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/shared/errors.ts +33 -6
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +3 -3
- package/src/network/pool/byte-accounting.ts +3 -2
- package/src/network/pool/selector.ts +182 -39
- package/src/network/pool-cooldown-store.ts +89 -14
- package/src/network/pool-health.ts +49 -61
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/ssrf.ts +6 -6
- package/src/observability/event-log.ts +7 -361
- package/src/observability/event-query.ts +18 -4
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +15 -25
- package/src/observability/telemetry-reconciliation.ts +9 -37
- package/src/observability/telemetry-row.ts +125 -0
- package/src/persistence/db-mode.ts +4 -1
- package/src/persistence/dialect.ts +30 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +28 -2
- package/src/persistence/ttl-cache.ts +20 -2
- package/src/protocol/codecs.ts +1 -1
- package/src/protocol/dsml.ts +35 -87
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +14 -14
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +4 -5
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/stream-error-frames.ts +13 -3
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/protocol/translation/quirks.ts +1 -1
- package/src/protocol/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +15 -4
- package/src/protocol/upstream-parsing.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +78 -17
- package/src/providers/authentication/oauth-client.ts +1 -1
- package/src/providers/authentication/oauth-device-flow.ts +98 -8
- package/src/providers/authentication/oauth-refresh-service.ts +153 -59
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +117 -27
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +74 -33
- package/src/providers/discovery/probing-service.ts +293 -165
- package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
- package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +121 -2
- package/src/providers/integrations/antigravity/antigravity.ts +13 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +191 -35
- package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
- package/src/providers/integrations/buddy/buddy-identity.ts +89 -6
- package/src/providers/integrations/buddy/buddy-media.ts +4 -5
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
- package/src/providers/integrations/buddy/capabilities.ts +6 -43
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +23 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +38 -27
- package/src/providers/integrations/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline.ts +2 -2
- package/src/providers/integrations/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
- package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +745 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +35 -20
- package/src/providers/integrations/cursor/cursor-shared.ts +26 -5
- package/src/providers/integrations/cursor/cursor.ts +139 -70
- package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
- package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
- package/src/providers/integrations/devin/devin-auth.ts +353 -0
- package/src/providers/integrations/devin/devin-catalog.ts +207 -0
- package/src/providers/integrations/devin/devin-http.ts +91 -0
- package/src/providers/integrations/devin/devin-quota.ts +214 -0
- package/src/providers/integrations/devin/devin-shared.ts +111 -0
- package/src/providers/integrations/devin/devin.ts +539 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +558 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory-oauth.ts +17 -44
- package/src/providers/integrations/factory/factory.ts +2 -2
- package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
- package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
- package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
- package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
- package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
- package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
- package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
- package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
- package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
- package/src/providers/integrations/freebuff/freebuff.ts +682 -0
- package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
- package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
- package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
- package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
- package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
- package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
- package/src/providers/integrations/freepi/freepi.ts +292 -0
- package/src/providers/integrations/gemini.ts +2 -2
- package/src/providers/integrations/grok/grok-oauth.ts +48 -45
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
- package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
- package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +13 -21
- package/src/providers/integrations/hermes/index.ts +2 -0
- package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
- package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
- package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- package/src/providers/integrations/ollamacloud/index.ts +2 -0
- package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
- package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +208 -0
- package/src/providers/integrations/perch/perch-quota.ts +123 -0
- package/src/providers/integrations/perch/perch-realm.ts +82 -0
- package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
- package/src/providers/integrations/perch/perch.ts +305 -0
- package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
- package/src/providers/integrations/qoder/chat-proto.ts +328 -328
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +90 -40
- package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +26 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
- package/src/providers/integrations/qoder/qoder-checkin.ts +6 -6
- package/src/providers/integrations/qoder/qoder-grpc.ts +210 -7
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -108
- package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
- package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
- package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
- package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
- package/src/providers/integrations/qoder/qoder.ts +3 -7
- package/src/providers/integrations/trae/trae-catalog.ts +9 -14
- package/src/providers/integrations/trae/trae-headers.ts +36 -11
- package/src/providers/integrations/trae/trae-oauth.ts +14 -17
- package/src/providers/integrations/trae/trae-protocol.ts +12 -11
- package/src/providers/integrations/trae/trae-quota.ts +96 -36
- package/src/providers/integrations/trae/trae-realm.ts +74 -15
- package/src/providers/integrations/trae/trae.ts +99 -26
- package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
- package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
- package/src/providers/integrations/zro/zro.ts +204 -0
- package/src/providers/operations/account-error-classification.ts +235 -278
- package/src/providers/operations/account-health-service.ts +273 -26
- package/src/providers/operations/account-refresh-status.ts +269 -0
- package/src/providers/operations/account-tier.ts +66 -0
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +42 -150
- package/src/providers/operations/cooldown-rules.ts +56 -0
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +102 -32
- package/src/providers/operations/provider-rules/github.ts +48 -0
- package/src/providers/operations/provider-rules/grok.ts +33 -0
- package/src/providers/operations/provider-rules/inception.ts +49 -0
- package/src/providers/operations/provider-rules/index.ts +32 -0
- package/src/providers/operations/provider-rules/perch.ts +37 -0
- package/src/providers/operations/provider-rules/qoder.ts +70 -0
- package/src/providers/operations/provider-rules/trae.ts +81 -0
- package/src/providers/provider-contracts.ts +33 -2
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +99 -14
- package/src/providers/provider-model-definition.ts +36 -0
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- package/src/providers/quota/quota-limit-windows.ts +16 -5
- package/src/providers/quota/quota-support.ts +1 -1
- package/src/providers/quota/quota-window-parser.ts +90 -18
- package/src/providers/quota/refresh.ts +96 -18
- package/src/security/abuse.ts +71 -0
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/transport/dispatch/attempt-finalize.ts +14 -13
- package/src/transport/dispatch/attempt-loop.ts +71 -17
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +9 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/streaming/finalize.ts +9 -6
- package/src/transport/dispatch/streaming/stream.ts +9 -36
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +67 -1
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +129 -37
- package/src/transport/guards/body-policy.ts +7 -1
- package/src/transport/guards/error-lifecycle.ts +13 -1
- package/src/transport/guards/request-context.ts +22 -14
- package/src/transport/request/preparer.ts +81 -71
- package/src/transport/routing/bounded-lru.ts +14 -0
- package/src/transport/routing/drain-reserve.ts +129 -0
- package/src/transport/routing/plan-gate.ts +7 -1
- package/src/transport/routing/round-robin.ts +60 -0
- package/src/transport/routing/route-catalog.ts +123 -111
- package/src/transport/routing/route-model.ts +51 -9
- package/src/transport/routing/router.ts +418 -33
- package/src/transport/surface/adapters.ts +11 -2
- package/src/transport/surface/chat/adapter.ts +2 -2
- package/src/transport/surface/chat/encode.ts +47 -4
- package/src/transport/surface/chat/parse.ts +2 -12
- package/src/transport/surface/completion.ts +44 -33
- package/src/transport/surface/content-parts.ts +21 -0
- package/src/transport/surface/messages/adapter.ts +9 -2
- package/src/transport/surface/messages/encode.ts +11 -3
- package/src/transport/surface/messages/parse.ts +2 -1
- package/src/transport/surface/messages/stream.ts +33 -17
- package/src/transport/surface/responses/encode.ts +34 -6
- package/src/transport/surface/responses/parse.ts +3 -11
- package/src/workers/daily-checkin.ts +1 -1
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/scheduler.ts +22 -4
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js +0 -1
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
- package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
- package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.png +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/favicon.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
- package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
- package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
- package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
- package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
- package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
- package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
- package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
- package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
- package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
- package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
- package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
- package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
- package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
- package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
- package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
- package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
- package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
- package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
- package/src/providers/operations/model-discovery-cache.ts +0 -34
- package/src/providers/operations/probe-fetch.ts +0 -15
- package/src/providers/operations/provider-version-cache.ts +0 -30
- package/src/providers/provider-capabilities.ts +0 -33
- package/src/providers/provider-credential-envelope.ts +0 -43
- /package/dist/dashboard/assets/{app-CTxoqyYB.js → app-DIXLjojg.js} +0 -0
- /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-DIXLjojg.js.gz} +0 -0
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
firstNonEmptyString as firstString,
|
|
11
11
|
tryParseJson as safeParseJson,
|
|
12
12
|
} from "../../../shared/coerce";
|
|
13
|
-
import type { CredentialMode, ModelCatalogEntry } from "./contracts";
|
|
13
|
+
import type { CredentialMode, ModelCatalogEntry, ModelWarning } from "./contracts";
|
|
14
14
|
import {
|
|
15
15
|
FALLBACK_CONTEXT_LIMIT,
|
|
16
16
|
FALLBACK_OUTPUT_LIMIT,
|
|
@@ -285,6 +285,32 @@ export function liveModelCooldowns(
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Reads the display-only warning blob. Postgres returns `jsonb` already
|
|
290
|
+
* decoded; SQLite returns the JSON string its `text` column carries, so both
|
|
291
|
+
* shapes are accepted. A value that is not `{ warnings: [...] }` is ignored —
|
|
292
|
+
* a malformed row must not break the entire model list.
|
|
293
|
+
*/
|
|
294
|
+
function parseWarningMetadata(raw: unknown): { warnings: ModelWarning[] } | undefined {
|
|
295
|
+
let value: unknown = raw;
|
|
296
|
+
if (typeof value === "string") {
|
|
297
|
+
try {
|
|
298
|
+
value = JSON.parse(value) as unknown;
|
|
299
|
+
} catch {
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
304
|
+
const warnings = (value as Record<string, unknown>)["warnings"];
|
|
305
|
+
if (!Array.isArray(warnings)) return undefined;
|
|
306
|
+
const projected = warnings.filter((warning): warning is ModelWarning => {
|
|
307
|
+
if (warning === null || typeof warning !== "object" || Array.isArray(warning)) return false;
|
|
308
|
+
const item = warning as Record<string, unknown>;
|
|
309
|
+
return typeof item["kind"] === "string" && typeof item["message"] === "string";
|
|
310
|
+
});
|
|
311
|
+
return projected.length > 0 ? { warnings: projected } : undefined;
|
|
312
|
+
}
|
|
313
|
+
|
|
288
314
|
/**
|
|
289
315
|
* Projects one persisted `models` row into the console's catalog entry.
|
|
290
316
|
*
|
|
@@ -344,6 +370,9 @@ export function mapModelRow(
|
|
|
344
370
|
// Same three-valued rule, from the provider's own directory. Qoder states
|
|
345
371
|
// `is_free` per row, so a plan change moves the pill without a code change.
|
|
346
372
|
...(row.freeTier === true ? { freeTier: true } : {}),
|
|
373
|
+
// Display-only: a `NULL` column (or a malformed legacy value) projects no
|
|
374
|
+
// warnings at all rather than leaking the raw JSON shape to the dashboard.
|
|
375
|
+
...(parseWarningMetadata(row.warningMetadata) ?? {}),
|
|
347
376
|
sourceUpdatedAt: row.sourceUpdatedAt?.toISOString() ?? null,
|
|
348
377
|
} satisfies ModelCatalogEntry;
|
|
349
378
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Drizzle-backed provider, account, model, routing, and API-key repositories.
|
|
2
|
-
import { and, asc, eq, gte, inArray, isNull, or, sql, type SQL } from "drizzle-orm";
|
|
2
|
+
import { and, asc, eq, gte, inArray, isNull, ne, or, sql, type SQL } from "drizzle-orm";
|
|
3
|
+
import { liveModelCooldownExistsSql } from "../../../persistence/dialect";
|
|
3
4
|
import { ConsoleDomainError } from "../../shared/errors";
|
|
4
5
|
import { parseCustomProviderId, isBundledProviderId } from "../../../providers/provider-contracts";
|
|
5
6
|
import { providerRateLimitIsIpScoped } from "../../../providers/provider-metadata";
|
|
@@ -13,11 +14,13 @@ import { buddyTaskLedger, models, networkPools, providerAccounts, providerOauthS
|
|
|
13
14
|
import { IS_GATEWAY_ERROR_SQL } from "../../../observability/event-query";
|
|
14
15
|
import type { WireFamily } from "../../../protocol/canonical-model";
|
|
15
16
|
import { listAccountHealthEvents, recoverAccount, deleteAccountHealthEvents, type AccountHealthEventRecord } from "../../../providers/operations/account-health-service";
|
|
16
|
-
import { invalidateCredentialCache } from "../../../providers/operations/provider-credential-service";
|
|
17
|
+
import { invalidateCredentialCache, invalidateCredentialCaches } from "../../../providers/operations/provider-credential-service";
|
|
18
|
+
import { invalidateQuotaCaches } from "../../../providers/quota/cache";
|
|
17
19
|
import { encryptCredential, hashSecret } from "../../../security/crypto";
|
|
18
20
|
import type { TelemetryBatchBuffer } from "../../../observability/telemetry-buffer";
|
|
21
|
+
import type { RedisClient } from "../../../persistence/redis";
|
|
19
22
|
import { bumpCatalogRevision, type BundledProviderCatalog } from "../../../providers/operations/provider-catalog-service";
|
|
20
|
-
import type { AccountInflightReading, BuddyGrowthTaskLedgerEntry, ByokConnectionTestRequest, ByokConnectionTestResult, CreateProviderAccountBatchItemResult, CreateProviderAccountBatchResult, CreateProviderAccountRequest, CredentialMode, ModelCatalogEntry, ProbeAllAccountsResult, ProbeAllModelsResult, ProbeModelRequest, ProbeModelResult, ProviderAccountResponse, ProviderAccountTokenUsage, ProviderCatalogStore, ProviderRecord, SetModelEnabledRequest, UpdateModelMetadataRequest, UpdateProviderAccountRequest, ModelMetadataOverride } from "./contracts";
|
|
23
|
+
import type { AccountInflightReading, BuddyGrowthTaskLedgerEntry, ByokConnectionTestRequest, ByokConnectionTestResult, CreateProviderAccountBatchItemResult, CreateProviderAccountBatchResult, CreateProviderAccountRequest, CredentialMode, DiscoverModelCandidate, ModelCatalogEntry, ProbeAllAccountsResult, ProbeAllModelsResult, ProbeModelRequest, ProbeModelResult, ProviderAccountBulkActionResult, ProviderAccountResponse, ProviderAccountTokenUsage, ProviderCatalogStore, ProviderRecord, SetModelEnabledRequest, UpdateModelMetadataRequest, UpdateProviderAccountRequest, ModelMetadataOverride } from "./contracts";
|
|
21
24
|
import { isServiceKind, validateCompatibilityProfile } from "./contracts";
|
|
22
25
|
import { ProviderProbingService, type ProbeOutboundResolver } from "../../../providers/discovery/probing-service";
|
|
23
26
|
import {
|
|
@@ -75,6 +78,7 @@ export class DrizzleProviderCatalogStore implements ProviderCatalogStore {
|
|
|
75
78
|
private readonly probing: ProviderProbingService;
|
|
76
79
|
private readonly bundledModelCatalog: ReadonlyMap<string, readonly ModelDefinition[]>;
|
|
77
80
|
private readonly providerRegistry: ProviderRegistry;
|
|
81
|
+
private readonly redis: RedisClient | undefined;
|
|
78
82
|
|
|
79
83
|
private readonly readAccountInflight:
|
|
80
84
|
| ((
|
|
@@ -97,11 +101,14 @@ export class DrizzleProviderCatalogStore implements ProviderCatalogStore {
|
|
|
97
101
|
tenantId: string,
|
|
98
102
|
) => Promise<readonly AccountInflightReading[]>)
|
|
99
103
|
| undefined;
|
|
104
|
+
/** Shared Redis handle for quota-cache invalidation on bulk delete. */
|
|
105
|
+
readonly redis?: RedisClient | undefined;
|
|
100
106
|
},
|
|
101
107
|
) {
|
|
102
108
|
this.bundledModelCatalog = options.bundledModelCatalog.modelsByProvider;
|
|
103
109
|
this.providerRegistry = options.providerRegistry;
|
|
104
110
|
this.readAccountInflight = options.readAccountInflight;
|
|
111
|
+
this.redis = options.redis;
|
|
105
112
|
this.probing = new ProviderProbingService({
|
|
106
113
|
db,
|
|
107
114
|
telemetryBuffer: options.telemetryBuffer,
|
|
@@ -430,12 +437,17 @@ export class DrizzleProviderCatalogStore implements ProviderCatalogStore {
|
|
|
430
437
|
* (warms OAuth token / connection pools), the rest run concurrently bounded
|
|
431
438
|
* to 5. Registered model ids come from this store's own catalog read.
|
|
432
439
|
*/
|
|
433
|
-
async probeAllModels(
|
|
440
|
+
async probeAllModels(
|
|
441
|
+
tenantId: string,
|
|
442
|
+
providerId: string,
|
|
443
|
+
mode?: ProbeModelRequest["mode"],
|
|
444
|
+
): Promise<ProbeAllModelsResult> {
|
|
434
445
|
const entries = await this.listModels(tenantId, providerId);
|
|
435
446
|
const requests = entries.map((entry) => ({
|
|
436
447
|
modelId: entry.modelId,
|
|
437
448
|
route: entry.route,
|
|
438
449
|
serviceKind: isServiceKind(entry.serviceKind) ? entry.serviceKind : ("llm" as const),
|
|
450
|
+
...(mode === undefined ? {} : { mode }),
|
|
439
451
|
}));
|
|
440
452
|
const uniqueRequests = requests.filter(
|
|
441
453
|
(request, index, all) =>
|
|
@@ -559,6 +571,8 @@ export class DrizzleProviderCatalogStore implements ProviderCatalogStore {
|
|
|
559
571
|
const findRow = (ownership: SQL) =>
|
|
560
572
|
this.db
|
|
561
573
|
.select({ id: models.id, modalities: models.modalities, source: models.source })
|
|
574
|
+
// source is read for both the global builtin guard above and the new
|
|
575
|
+
// non-builtin-only warning edit.
|
|
562
576
|
.from(models)
|
|
563
577
|
.innerJoin(providers, eq(models.providerId, providers.id))
|
|
564
578
|
.where(
|
|
@@ -594,6 +608,29 @@ export class DrizzleProviderCatalogStore implements ProviderCatalogStore {
|
|
|
594
608
|
// per-flag field: the point of the flag is that the operator does not know
|
|
595
609
|
// which capabilities the model has, so an explicit `false` alongside it is
|
|
596
610
|
// either stale or a mistake.
|
|
611
|
+
// Builtin rows are re-seeded from the static catalog on every boot, so a
|
|
612
|
+
// warning edit there would be reverted. Non-builtin rows (including ones a
|
|
613
|
+
// discovery commit created) are operator-owned and may set or clear them.
|
|
614
|
+
if (override?.warnings !== undefined) {
|
|
615
|
+
if (row.source === "builtin") {
|
|
616
|
+
throw new ConsoleDomainError(
|
|
617
|
+
"builtin_model_immutable",
|
|
618
|
+
403,
|
|
619
|
+
`Built-in model ${providerId}/${request.modelId} warnings cannot be edited — the bundle re-seeds them on every boot`,
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
set.warningMetadata =
|
|
623
|
+
override.warnings === null
|
|
624
|
+
? null
|
|
625
|
+
: {
|
|
626
|
+
warnings: override.warnings.map((warning) => ({
|
|
627
|
+
kind: warning.kind,
|
|
628
|
+
message: warning.message,
|
|
629
|
+
...(warning.severity === undefined ? {} : { severity: warning.severity }),
|
|
630
|
+
...(warning.updatedAt === undefined ? {} : { updatedAt: warning.updatedAt }),
|
|
631
|
+
})),
|
|
632
|
+
};
|
|
633
|
+
}
|
|
597
634
|
if (override?.passthrough === true) {
|
|
598
635
|
set.reasoning = true;
|
|
599
636
|
set.toolCall = true;
|
|
@@ -1655,8 +1692,216 @@ export class DrizzleProviderCatalogStore implements ProviderCatalogStore {
|
|
|
1655
1692
|
return recoverAccount(this.db, accountId, "manual_operator_recovery");
|
|
1656
1693
|
}
|
|
1657
1694
|
|
|
1695
|
+
/**
|
|
1696
|
+
* One `UPDATE ... WHERE <cooldown predicate>` (D07/D08/D09). The predicate
|
|
1697
|
+
* is evaluated in SQL, so the whole provider is handled in one statement
|
|
1698
|
+
* without reading rows into the process. `cooldown_until` and
|
|
1699
|
+
* `model_cooldowns` are left untouched: they are the evidence the operator
|
|
1700
|
+
* sees on a disabled row until an explicit Recover clears them. A row that
|
|
1701
|
+
* is already `disabled` is not a target — those columns surviving the
|
|
1702
|
+
* status change would otherwise keep matching the predicate forever.
|
|
1703
|
+
*
|
|
1704
|
+
* Pool-as-account rows live in `provider_pool_accounts`, not this table, so
|
|
1705
|
+
* they are excluded by construction. Shared (`tenant_id IS NULL`) rows are
|
|
1706
|
+
* counted in `globalSkipped` but never written by a tenant; the global-admin
|
|
1707
|
+
* pair of routes owns those.
|
|
1708
|
+
*/
|
|
1709
|
+
async disableCooldowns(
|
|
1710
|
+
tenantId: string,
|
|
1711
|
+
providerId: string,
|
|
1712
|
+
): Promise<ProviderAccountBulkActionResult> {
|
|
1713
|
+
const nowIso = new Date().toISOString();
|
|
1714
|
+
const [globalRows, updatedRows] = await Promise.all([
|
|
1715
|
+
this.db
|
|
1716
|
+
.select({ id: providerAccounts.id })
|
|
1717
|
+
.from(providerAccounts)
|
|
1718
|
+
.where(
|
|
1719
|
+
and(
|
|
1720
|
+
eq(providerAccounts.providerId, providerId),
|
|
1721
|
+
isNull(providerAccounts.tenantId),
|
|
1722
|
+
this.liveCooldownPredicate(nowIso),
|
|
1723
|
+
),
|
|
1724
|
+
),
|
|
1725
|
+
this.db
|
|
1726
|
+
.update(providerAccounts)
|
|
1727
|
+
.set({ status: "disabled" })
|
|
1728
|
+
.where(
|
|
1729
|
+
and(
|
|
1730
|
+
eq(providerAccounts.providerId, providerId),
|
|
1731
|
+
eq(providerAccounts.tenantId, tenantId),
|
|
1732
|
+
this.liveCooldownPredicate(nowIso),
|
|
1733
|
+
),
|
|
1734
|
+
)
|
|
1735
|
+
.returning({ id: providerAccounts.id }),
|
|
1736
|
+
]);
|
|
1737
|
+
// Status is part of the cached credential's admission decision, so every
|
|
1738
|
+
// newly disabled id must be dropped here — not only on delete.
|
|
1739
|
+
invalidateCredentialCaches(updatedRows.map((row) => row.id));
|
|
1740
|
+
return { ok: true, updated: updatedRows.length, globalSkipped: globalRows.length };
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
/**
|
|
1744
|
+
* One `DELETE ... WHERE <strict banned predicate> RETURNING id` (D12).
|
|
1745
|
+
* Credential and quota caches are dropped for the deleted ids in one pass
|
|
1746
|
+
* each, exactly like the existing batch-delete route. A delete is strictly
|
|
1747
|
+
* `status='disabled' AND last_error_category='policy_blocked_banned'`;
|
|
1748
|
+
* `auth_invalidated` is remediated by replacing the credential, not here.
|
|
1749
|
+
*/
|
|
1750
|
+
async deleteBanned(
|
|
1751
|
+
tenantId: string,
|
|
1752
|
+
providerId: string,
|
|
1753
|
+
): Promise<ProviderAccountBulkActionResult> {
|
|
1754
|
+
const globalRows = await this.db
|
|
1755
|
+
.select({ id: providerAccounts.id })
|
|
1756
|
+
.from(providerAccounts)
|
|
1757
|
+
.where(
|
|
1758
|
+
and(
|
|
1759
|
+
eq(providerAccounts.providerId, providerId),
|
|
1760
|
+
isNull(providerAccounts.tenantId),
|
|
1761
|
+
this.bannedPredicate(),
|
|
1762
|
+
),
|
|
1763
|
+
);
|
|
1764
|
+
const deletedRows = await this.db
|
|
1765
|
+
.delete(providerAccounts)
|
|
1766
|
+
.where(
|
|
1767
|
+
and(
|
|
1768
|
+
eq(providerAccounts.providerId, providerId),
|
|
1769
|
+
eq(providerAccounts.tenantId, tenantId),
|
|
1770
|
+
this.bannedPredicate(),
|
|
1771
|
+
),
|
|
1772
|
+
)
|
|
1773
|
+
.returning({ id: providerAccounts.id });
|
|
1774
|
+
const deletedIds = deletedRows.map((row) => row.id);
|
|
1775
|
+
invalidateCredentialCaches(deletedIds);
|
|
1776
|
+
await invalidateQuotaCaches(tenantId, deletedIds, this.redis);
|
|
1777
|
+
return {
|
|
1778
|
+
ok: true,
|
|
1779
|
+
updated: 0,
|
|
1780
|
+
deleted: deletedIds,
|
|
1781
|
+
missing: [],
|
|
1782
|
+
globalSkipped: globalRows.length,
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
/** Strict banned definition shared by the tenant and global DELETE paths. */
|
|
1787
|
+
private bannedPredicate(): SQL {
|
|
1788
|
+
return and(
|
|
1789
|
+
eq(providerAccounts.status, "disabled"),
|
|
1790
|
+
eq(providerAccounts.lastErrorCategory, "policy_blocked_banned"),
|
|
1791
|
+
) as SQL;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* Not already disabled, and either `status='cooldown'`, `cooldown_until` is
|
|
1796
|
+
* still in the future, or `model_cooldowns` carries a live entry. Scoped to
|
|
1797
|
+
* the caller's own rows (the tenant path never touches `tenant_id IS NULL`
|
|
1798
|
+
* rows). The shared JSON subquery lives in the dialect module; only the
|
|
1799
|
+
* surrounding scoping is the store's.
|
|
1800
|
+
*
|
|
1801
|
+
* The disabled exclusion is load-bearing. Disable preserves the deadline
|
|
1802
|
+
* columns as evidence, so without it a row this action just wrote matches
|
|
1803
|
+
* the predicate again and the operator's count never drops to zero.
|
|
1804
|
+
*/
|
|
1805
|
+
private liveCooldownPredicate(nowIso: string): SQL {
|
|
1806
|
+
return and(
|
|
1807
|
+
ne(providerAccounts.status, "disabled"),
|
|
1808
|
+
or(
|
|
1809
|
+
eq(providerAccounts.status, "cooldown"),
|
|
1810
|
+
sql`${providerAccounts.cooldownUntil} > ${nowIso}`,
|
|
1811
|
+
liveModelCooldownExistsSql(this.db, nowIso),
|
|
1812
|
+
),
|
|
1813
|
+
) as SQL;
|
|
1814
|
+
}
|
|
1658
1815
|
|
|
1659
1816
|
async syncModels(tenantId: string, providerId: string): Promise<{ synced: number }> {
|
|
1660
1817
|
return this.probing.syncModels(tenantId, providerId);
|
|
1661
1818
|
}
|
|
1819
|
+
|
|
1820
|
+
/** Read-only discovery; delegates to the same fetch path as sync (area 05). */
|
|
1821
|
+
async listDiscoveryCandidates(
|
|
1822
|
+
tenantId: string,
|
|
1823
|
+
providerId: string,
|
|
1824
|
+
): Promise<{
|
|
1825
|
+
readonly supported: boolean;
|
|
1826
|
+
readonly candidates: readonly DiscoverModelCandidate[];
|
|
1827
|
+
readonly accountLabel?: string;
|
|
1828
|
+
}> {
|
|
1829
|
+
const discovery = await this.probing.discoverModels(tenantId, providerId);
|
|
1830
|
+
if (!discovery.supported) return { supported: false, candidates: [] };
|
|
1831
|
+
const registered = await this.listModelsForTenant(tenantId, providerId);
|
|
1832
|
+
const ids = new Set(
|
|
1833
|
+
(registered.get(providerId) ?? []).map((entry) => entry.modelId),
|
|
1834
|
+
);
|
|
1835
|
+
// The picker exists to add models, so rows already on the roster are
|
|
1836
|
+
// excluded rather than listed disabled — a fully-registered catalog would
|
|
1837
|
+
// otherwise render an all-"Registered" list that reads as empty.
|
|
1838
|
+
const candidates: DiscoverModelCandidate[] = discovery.models
|
|
1839
|
+
.filter((definition) => !ids.has(definition.modelId))
|
|
1840
|
+
.map((definition) => ({
|
|
1841
|
+
modelId: definition.modelId,
|
|
1842
|
+
wireFamily: definition.wireFamily,
|
|
1843
|
+
serviceKind: definition.serviceKind ?? "llm",
|
|
1844
|
+
endpointPath: definition.endpointPath,
|
|
1845
|
+
contextLimit: definition.contextLimit,
|
|
1846
|
+
outputLimit: definition.outputLimit,
|
|
1847
|
+
modalities: definition.modalities,
|
|
1848
|
+
reasoning: definition.reasoning,
|
|
1849
|
+
toolCall: definition.toolCall,
|
|
1850
|
+
...(definition.freeTier === undefined ? {} : { freeTier: definition.freeTier }),
|
|
1851
|
+
...(definition.proOnly === undefined ? {} : { proOnly: definition.proOnly }),
|
|
1852
|
+
alreadyRegistered: false,
|
|
1853
|
+
}));
|
|
1854
|
+
return {
|
|
1855
|
+
supported: true,
|
|
1856
|
+
candidates,
|
|
1857
|
+
...(discovery.accountLabel === undefined ? {} : { accountLabel: discovery.accountLabel }),
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
/**
|
|
1862
|
+
* Re-runs discovery on the server, then persists only ids the fresh roster
|
|
1863
|
+
* actually advertised (D28/D29/D30/D31). This is the anti-forgery gate: a
|
|
1864
|
+
* caller cannot attach a hand-built metadata object to an arbitrary id.
|
|
1865
|
+
*/
|
|
1866
|
+
async commitDiscoveredModels(
|
|
1867
|
+
tenantId: string,
|
|
1868
|
+
providerId: string,
|
|
1869
|
+
modelIds: readonly string[],
|
|
1870
|
+
): Promise<{
|
|
1871
|
+
readonly committed: readonly string[];
|
|
1872
|
+
readonly skipped: readonly string[];
|
|
1873
|
+
readonly synced: number;
|
|
1874
|
+
}> {
|
|
1875
|
+
const discovery = await this.probing.discoverModels(tenantId, providerId);
|
|
1876
|
+
if (!discovery.supported) {
|
|
1877
|
+
throw new ConsoleDomainError(
|
|
1878
|
+
"model_discovery_unsupported",
|
|
1879
|
+
400,
|
|
1880
|
+
`${providerId} publishes no model-listing endpoint; add the model manually`,
|
|
1881
|
+
);
|
|
1882
|
+
}
|
|
1883
|
+
const byId = new Map(discovery.models.map((definition) => [definition.modelId, definition]));
|
|
1884
|
+
const wanted = [...new Set(modelIds)];
|
|
1885
|
+
const unknown = wanted.filter((id) => !byId.has(id));
|
|
1886
|
+
if (unknown.length > 0) {
|
|
1887
|
+
throw new ConsoleDomainError(
|
|
1888
|
+
"invalid_request",
|
|
1889
|
+
400,
|
|
1890
|
+
`Models are no longer in the provider's listing: ${unknown.join(", ")} (request {requestId}). Fetch the model list again 🪞`,
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1893
|
+
const registered = await this.listModelsForTenant(tenantId, providerId);
|
|
1894
|
+
const existing = new Set(
|
|
1895
|
+
(registered.get(providerId) ?? []).map((entry) => entry.modelId),
|
|
1896
|
+
);
|
|
1897
|
+
const selected = wanted.filter((id) => !existing.has(id));
|
|
1898
|
+
const skipped = wanted.filter((id) => existing.has(id));
|
|
1899
|
+
if (selected.length === 0) return { committed: [], skipped, synced: 0 };
|
|
1900
|
+
const { synced } = await this.probing.commitDiscoveredModels(
|
|
1901
|
+
tenantId,
|
|
1902
|
+
providerId,
|
|
1903
|
+
selected,
|
|
1904
|
+
);
|
|
1905
|
+
return { committed: selected, skipped, synced };
|
|
1906
|
+
}
|
|
1662
1907
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t } from "elysia";
|
|
2
2
|
import type { Elysia } from "elysia";
|
|
3
|
-
import { and, eq, inArray, isNull } from "drizzle-orm";
|
|
3
|
+
import { and, eq, inArray, isNull, sql } from "drizzle-orm";
|
|
4
|
+
import { liveModelCooldownExistsSql } from "../../persistence/dialect";
|
|
4
5
|
import { adminAuditLog, providerAccounts } from "../../persistence/schema";
|
|
5
6
|
import { requireGlobalAdmin } from "../shared/errors";
|
|
6
7
|
import {
|
|
@@ -22,10 +23,29 @@ import {
|
|
|
22
23
|
} from "../../providers/quota/refresh";
|
|
23
24
|
import { sanitizeGlobalAccount } from "./quota-view";
|
|
24
25
|
import {
|
|
25
|
-
|
|
26
|
+
resolveNextAccountStatus,
|
|
26
27
|
type AccountQuotaRoutesDeps,
|
|
27
|
-
type AccountStatus,
|
|
28
28
|
} from "./account-quota-shared";
|
|
29
|
+
import type { ProviderAccountBulkActionResult } from "../providers/catalog/contracts";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The same live-cooldown predicate as the tenant-scoped store, evaluated in
|
|
33
|
+
* SQL so one statement covers the whole shared-account set. `status='cooldown'`
|
|
34
|
+
* counts even when its deadline elapsed; a live `model_cooldowns` entry counts
|
|
35
|
+
* even while `status` is still `active` (D07). A row already `disabled` does
|
|
36
|
+
* not: disable keeps the deadline columns as evidence, and counting them would
|
|
37
|
+
* make a second pass look like it still has work. Scoped strictly to shared
|
|
38
|
+
* rows (`tenant_id IS NULL`); the shared JSON subquery lives in the dialect
|
|
39
|
+
* module.
|
|
40
|
+
*/
|
|
41
|
+
function globalLiveCooldownPredicate(database: unknown): ReturnType<typeof and> {
|
|
42
|
+
const nowIso = new Date().toISOString();
|
|
43
|
+
return and(
|
|
44
|
+
isNull(providerAccounts.tenantId),
|
|
45
|
+
sql`${providerAccounts.status} <> 'disabled'`,
|
|
46
|
+
sql`${providerAccounts.status} = 'cooldown' OR ${providerAccounts.cooldownUntil} > ${nowIso} OR ${liveModelCooldownExistsSql(database, nowIso)}`,
|
|
47
|
+
)!;
|
|
48
|
+
}
|
|
29
49
|
|
|
30
50
|
/**
|
|
31
51
|
* Registers the platform-admin account/quota routes onto `app`. Every route here
|
|
@@ -38,6 +58,47 @@ export function registerAccountQuotaGlobalRoutes(
|
|
|
38
58
|
refreshDeps: QuotaRefreshDeps,
|
|
39
59
|
): void {
|
|
40
60
|
const { db, accessResolver, redis, snapshotInvalidator } = deps;
|
|
61
|
+
/**
|
|
62
|
+
* The two bulk actions are one statement each, scoped strictly to shared
|
|
63
|
+
* rows. Neither action takes a provider filter: the global-admin surface
|
|
64
|
+
* lists shared accounts across providers, and the same health predicate
|
|
65
|
+
* defines the target set everywhere.
|
|
66
|
+
*/
|
|
67
|
+
const runGlobalBulk = async (
|
|
68
|
+
kind: "disable" | "delete",
|
|
69
|
+
): Promise<ProviderAccountBulkActionResult> => {
|
|
70
|
+
if (kind === "disable") {
|
|
71
|
+
const updated = await db
|
|
72
|
+
.update(providerAccounts)
|
|
73
|
+
.set({ status: "disabled" })
|
|
74
|
+
.where(globalLiveCooldownPredicate(db))
|
|
75
|
+
.returning({ id: providerAccounts.id });
|
|
76
|
+
invalidateCredentialCaches(updated.map((row) => row.id));
|
|
77
|
+
await snapshotInvalidator?.invalidate();
|
|
78
|
+
return { ok: true, updated: updated.length, globalSkipped: 0 };
|
|
79
|
+
}
|
|
80
|
+
const deleted = await db
|
|
81
|
+
.delete(providerAccounts)
|
|
82
|
+
.where(
|
|
83
|
+
and(
|
|
84
|
+
isNull(providerAccounts.tenantId),
|
|
85
|
+
eq(providerAccounts.status, "disabled"),
|
|
86
|
+
eq(providerAccounts.lastErrorCategory, "policy_blocked_banned"),
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
.returning({ id: providerAccounts.id });
|
|
90
|
+
const deletedIds = deleted.map((row) => row.id);
|
|
91
|
+
invalidateCredentialCaches(deletedIds);
|
|
92
|
+
await invalidateQuotaCaches(GLOBAL_QUOTA_LENS, deletedIds, redis);
|
|
93
|
+
await snapshotInvalidator?.invalidate();
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
updated: 0,
|
|
97
|
+
deleted: deletedIds,
|
|
98
|
+
missing: [],
|
|
99
|
+
globalSkipped: 0,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
41
102
|
app
|
|
42
103
|
.get("/global/accounts", async ({ request }) => {
|
|
43
104
|
requireGlobalAdmin(accessResolver(request));
|
|
@@ -57,23 +118,12 @@ export function registerAccountQuotaGlobalRoutes(
|
|
|
57
118
|
},
|
|
58
119
|
async ({ request, params, body, set }) => {
|
|
59
120
|
const access = requireGlobalAdmin(accessResolver(request));
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
code: "invalid_status",
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
const nextStatus: AccountStatus | undefined =
|
|
68
|
-
body.active !== undefined
|
|
69
|
-
? body.active
|
|
70
|
-
? "active"
|
|
71
|
-
: "disabled"
|
|
72
|
-
: (body.status as AccountStatus | undefined);
|
|
73
|
-
if (!nextStatus) {
|
|
74
|
-
set.status = 400;
|
|
75
|
-
return { error: "active or status is required", code: "invalid_request" };
|
|
121
|
+
const resolved = resolveNextAccountStatus(body);
|
|
122
|
+
if (!resolved.ok) {
|
|
123
|
+
set.status = resolved.status;
|
|
124
|
+
return resolved.body;
|
|
76
125
|
}
|
|
126
|
+
const nextStatus = resolved.nextStatus;
|
|
77
127
|
const updated = await db
|
|
78
128
|
.update(providerAccounts)
|
|
79
129
|
.set({ status: nextStatus })
|
|
@@ -157,6 +207,34 @@ export function registerAccountQuotaGlobalRoutes(
|
|
|
157
207
|
});
|
|
158
208
|
return { ok: true, deleted: deletedIds, missing };
|
|
159
209
|
})
|
|
210
|
+
/**
|
|
211
|
+
* Global counterparts of the provider-detail bulk actions (D10): shared
|
|
212
|
+
* accounts carry `tenant_id IS NULL`, so the tenant-scoped pair can never
|
|
213
|
+
* match them. Both are one statement, one audit row, one snapshot
|
|
214
|
+
* invalidation, and authorize through `requireGlobalAdmin`.
|
|
215
|
+
*/
|
|
216
|
+
.post("/global/accounts/actions/disable-cooldowns", async ({ request }) => {
|
|
217
|
+
const access = requireGlobalAdmin(accessResolver(request));
|
|
218
|
+
const { ok, updated, globalSkipped } = await runGlobalBulk("disable");
|
|
219
|
+
await db.insert(adminAuditLog).values({
|
|
220
|
+
actor: access.admissionIdentity,
|
|
221
|
+
action: "provider_account.bulk_disabled_cooldowns",
|
|
222
|
+
target: "global",
|
|
223
|
+
detail: { updated, globalSkipped },
|
|
224
|
+
});
|
|
225
|
+
return { ok, updated, globalSkipped };
|
|
226
|
+
})
|
|
227
|
+
.post("/global/accounts/actions/delete-banned", async ({ request }) => {
|
|
228
|
+
const access = requireGlobalAdmin(accessResolver(request));
|
|
229
|
+
const { ok, updated, deleted, missing, globalSkipped } = await runGlobalBulk("delete");
|
|
230
|
+
await db.insert(adminAuditLog).values({
|
|
231
|
+
actor: access.admissionIdentity,
|
|
232
|
+
action: "provider_account.bulk_deleted_banned",
|
|
233
|
+
target: "global",
|
|
234
|
+
detail: { deleted: deleted ?? [], missing: missing ?? [], globalSkipped },
|
|
235
|
+
});
|
|
236
|
+
return { ok, updated, deleted: deleted ?? [], missing: missing ?? [], globalSkipped };
|
|
237
|
+
})
|
|
160
238
|
.post("/global/accounts/:id/quota/refresh", async ({ request, params, set }) => {
|
|
161
239
|
const access = requireGlobalAdmin(accessResolver(request));
|
|
162
240
|
const target = await loadQuotaTarget(db, params.id, null);
|
|
@@ -20,6 +20,55 @@ export interface AccountQuotaRoutesDeps extends QuotaRefreshDeps {
|
|
|
20
20
|
export const ACCOUNT_STATUSES = ["active", "cooldown", "disabled"] as const;
|
|
21
21
|
export type AccountStatus = (typeof ACCOUNT_STATUSES)[number];
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* The merged result of interpreting a PATCH account-status body. On success it
|
|
25
|
+
* carries the resolved `AccountStatus`; on failure it carries the exact 400
|
|
26
|
+
* response the tenant and global routes both return.
|
|
27
|
+
*/
|
|
28
|
+
export type ResolveNextAccountStatusResult =
|
|
29
|
+
| { readonly ok: true; readonly nextStatus: AccountStatus }
|
|
30
|
+
| {
|
|
31
|
+
readonly ok: false;
|
|
32
|
+
readonly status: 400;
|
|
33
|
+
readonly body: { readonly error: string; readonly code: string };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolves the next account status from a PATCH body: an explicit `active`
|
|
38
|
+
* boolean wins over `status`, and a missing `status` (or an invalid one) is
|
|
39
|
+
* rejected with the shared 400 shape. Both account PATCH handlers interpret
|
|
40
|
+
* the same input contract, so it lives here once.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveNextAccountStatus(body: {
|
|
43
|
+
readonly active?: boolean;
|
|
44
|
+
readonly status?: string;
|
|
45
|
+
}): ResolveNextAccountStatusResult {
|
|
46
|
+
if (body.status !== undefined && !ACCOUNT_STATUSES.includes(body.status as AccountStatus)) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
status: 400,
|
|
50
|
+
body: {
|
|
51
|
+
error: `status must be one of: ${ACCOUNT_STATUSES.join(", ")}`,
|
|
52
|
+
code: "invalid_status",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const nextStatus: AccountStatus | undefined =
|
|
57
|
+
body.active !== undefined
|
|
58
|
+
? body.active
|
|
59
|
+
? "active"
|
|
60
|
+
: "disabled"
|
|
61
|
+
: (body.status as AccountStatus | undefined);
|
|
62
|
+
if (nextStatus === undefined) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
status: 400,
|
|
66
|
+
body: { error: "active or status is required", code: "invalid_request" },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return { ok: true, nextStatus };
|
|
70
|
+
}
|
|
71
|
+
|
|
23
72
|
/**
|
|
24
73
|
* Wraps a stored api_key credential in the envelope its collector expects.
|
|
25
74
|
*
|
|
@@ -72,9 +72,8 @@ import {
|
|
|
72
72
|
toQuotaView,
|
|
73
73
|
} from "./quota-view";
|
|
74
74
|
import {
|
|
75
|
-
|
|
75
|
+
resolveNextAccountStatus,
|
|
76
76
|
type AccountQuotaRoutesDeps,
|
|
77
|
-
type AccountStatus,
|
|
78
77
|
} from "./account-quota-shared";
|
|
79
78
|
import {
|
|
80
79
|
DAILY_GROWTH_LEG_TIMEOUT_MS,
|
|
@@ -745,23 +744,12 @@ export function registerAccountQuotaTenantRoutes(
|
|
|
745
744
|
},
|
|
746
745
|
async ({ request, params, body, set }) => {
|
|
747
746
|
const access = requireTenantScope(accessResolver(request), "dashboard:write");
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
code: "invalid_status",
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
const nextStatus: AccountStatus | undefined =
|
|
756
|
-
body.active !== undefined
|
|
757
|
-
? body.active
|
|
758
|
-
? "active"
|
|
759
|
-
: "disabled"
|
|
760
|
-
: (body.status as AccountStatus | undefined);
|
|
761
|
-
if (!nextStatus) {
|
|
762
|
-
set.status = 400;
|
|
763
|
-
return { error: "active or status is required", code: "invalid_request" };
|
|
747
|
+
const resolved = resolveNextAccountStatus(body);
|
|
748
|
+
if (!resolved.ok) {
|
|
749
|
+
set.status = resolved.status;
|
|
750
|
+
return resolved.body;
|
|
764
751
|
}
|
|
752
|
+
const nextStatus = resolved.nextStatus;
|
|
765
753
|
const updated = await db
|
|
766
754
|
.update(providerAccounts)
|
|
767
755
|
.set({ status: nextStatus })
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
DrizzleQoderNativeIdentityStore,
|
|
17
17
|
type QoderNativeIdentityStore,
|
|
18
18
|
} from "../../providers/integrations/qoder/qoder-native-identity-store";
|
|
19
|
+
import { qoderRealmForProvider } from "../../providers/integrations/qoder/qoder-realm";
|
|
19
20
|
import { ConsoleDomainError, requireGlobalAdmin, requireTenantScope } from "../shared/errors";
|
|
20
21
|
import type { ConsoleAccessResolver } from "../auth/access";
|
|
21
22
|
|
|
@@ -38,6 +39,7 @@ export type NativeIdentityAllocator = (args: {
|
|
|
38
39
|
function defaultAllocator(store: QoderNativeIdentityStore): NativeIdentityAllocator {
|
|
39
40
|
return ({ providerId, accountId, avoidToken }) =>
|
|
40
41
|
allocateQoderNativeIdentity(store, providerId, accountId, {
|
|
42
|
+
realm: qoderRealmForProvider(providerId),
|
|
41
43
|
...(avoidToken !== undefined ? { avoidToken } : {}),
|
|
42
44
|
});
|
|
43
45
|
}
|