cartethyia 2.7.7 → 2.7.9
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 +0 -1
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js +1 -0
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-CLUhmjUy.js → CliToolDetailPage-ixwgKk53.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-CQmrqqrk.js → CliToolsPage-BmttNV3U.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js +1 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-C-e2AMz6.js → InjectSystemPromptPanel-BOoO8rpz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-J8wPbmfT.js → LoginPage-ChJuNNVN.js} +1 -1
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-C6WlpU5E.js → ModelPicker-BZAJtPg1.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-BBTRBAQ3.js → ProxyPage-BJ87Pp0U.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-B2WqMSG1.js → QuotaPage-gkSyRAKa.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js +1 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-LND6_Ss7.js → SetupPage-CFNcATJJ.js} +1 -1
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-BzNnm6Bi.js → StudioPage-YoD4tY5l.js} +1 -1
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js +56 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js +1 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +2 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +1 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-BaX2GTrn.js → badge-DdbT_7Am.js} +1 -1
- package/dist/dashboard/assets/badge-DdbT_7Am.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-DFH4Bkte.js → banner-CsGN-nbm.js} +1 -1
- package/dist/dashboard/assets/banner-CsGN-nbm.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-CxDw0lK6.js → card-mgZ_TLLZ.js} +1 -1
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js +1 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DfS1ntiq.js → clipboard-button-CJ4Zty89.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BK-NRr5u.js → dialog-B5L2jPQ_.js} +1 -1
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-QqPul6sC.js → drawer-DkjUouAj.js} +1 -1
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BMlYJVFj.js → entry-B-LVLV6o.js} +1 -1
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js +1 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +4 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DP-5E01-.js → formatters-CrsdLVsn.js} +1 -1
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/{index-D36vCZht.js → index-BU64Lqvp.js} +2 -2
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/{inline-CHJQwxLa.js → inline-B2aDLyyg.js} +1 -1
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/{input-BlmlizVV.js → input-BCVkdR9d.js} +1 -1
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/{layout-Bz93I-yE.js → layout-DNcAdMK-.js} +1 -1
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Ml16KCfu.js → list-row-DgrIH0hw.js} +1 -1
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/{live-7_QpnYGu.js → live-Cae01iu8.js} +1 -1
- package/dist/dashboard/assets/live-Cae01iu8.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-CuMDaTzT.js → network-CRib7H_2.js} +1 -1
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-CoqpaDMx.js → numbers-BKQE8Xpz.js} +1 -1
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-B5aOoweZ.js → pagination-ecPvG_8g.js} +1 -1
- package/dist/dashboard/assets/pagination-ecPvG_8g.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/{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/routing-Ckm1UXuC.js +1 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/{select-C9Ssle9b.js → select-ChoFWyKz.js} +1 -1
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js +1 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/{stack-j7cXjgYX.js → stack-DWCwU3fh.js} +1 -1
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/{state-DHmEnLa2.js → state-Cg3VL5J_.js} +1 -1
- package/dist/dashboard/assets/state-Cg3VL5J_.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-DmZcyTIC.js → switch-Db70BMix.js} +1 -1
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BqdE0LuM.js} +1 -1
- package/dist/dashboard/assets/useMutation-BqdE0LuM.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 +57 -55
- 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/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
- package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/package.json +1 -1
- package/src/boot/dependencies.ts +11 -3
- package/src/config/cooldowns.ts +1 -1
- package/src/config/spec.ts +0 -6
- package/src/console/api-keys/routes.ts +11 -4
- package/src/console/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -0
- package/src/console/ip-bans/routes.ts +99 -0
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -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 +64 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +243 -3
- package/src/console/quota/account-quota-global.ts +89 -1
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +1 -0
- package/src/observability/event-query.ts +18 -4
- package/src/persistence/dialect.ts +12 -0
- package/src/persistence/schema.ts +20 -2
- package/src/protocol/dsml.ts +1 -1
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +4 -4
- package/src/protocol/request/messages.ts +1 -1
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stream-error-frames.ts +13 -3
- 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 +7 -5
- package/src/providers/authentication/oauth-refresh-service.ts +149 -36
- package/src/providers/default-registry.ts +68 -3
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +61 -30
- package/src/providers/discovery/probing-service.ts +279 -164
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +117 -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 +18 -18
- 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 +70 -0
- 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 +4 -41
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +20 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +6 -10
- package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +738 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +9 -8
- package/src/providers/integrations/cursor/cursor-shared.ts +19 -1
- package/src/providers/integrations/cursor/cursor.ts +50 -30
- 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 +584 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory.ts +2 -2
- 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/hermes/hermes-oauth.ts +94 -0
- package/src/providers/integrations/hermes.ts +4 -12
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +211 -0
- package/src/providers/integrations/perch/perch-quota.ts +137 -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 +315 -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 +1 -1
- package/src/providers/integrations/qoder/qoder-adapter.ts +89 -28
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +6 -1
- package/src/providers/integrations/qoder/qoder-checkin.ts +2 -2
- package/src/providers/integrations/qoder/qoder-grpc.ts +209 -5
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -1
- 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 -6
- 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 +3 -6
- package/src/providers/integrations/trae/trae-quota.ts +103 -21
- package/src/providers/integrations/trae/trae-realm.ts +36 -15
- package/src/providers/integrations/trae/trae.ts +97 -7
- package/src/providers/operations/account-error-classification.ts +66 -1
- package/src/providers/operations/account-health-service.ts +192 -26
- package/src/providers/operations/account-refresh-status.ts +284 -0
- package/src/providers/operations/account-tier.ts +67 -0
- package/src/providers/operations/byok-wire-profile.ts +9 -12
- package/src/providers/operations/client-versions.ts +37 -99
- package/src/providers/operations/cooldown-rules.ts +61 -0
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- package/src/providers/provider-contracts.ts +29 -0
- package/src/providers/provider-metadata.ts +59 -4
- package/src/providers/provider-model-definition.ts +36 -0
- package/src/providers/quota/refresh.ts +21 -0
- package/src/security/abuse.ts +71 -0
- package/src/transport/dispatch/proxy-request.ts +1 -0
- package/src/transport/dispatch/streaming/stream.ts +1 -0
- package/src/transport/dispatch/upstream.ts +4 -1
- package/src/transport/failure-policy.ts +113 -7
- package/src/transport/routing/router.ts +25 -1
- package/src/transport/surface/messages/adapter.ts +2 -1
- package/src/transport/surface/responses/encode.ts +12 -1
- 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-B05aF8Dg.js +0 -1
- package/dist/dashboard/assets/BannedPage-B05aF8Dg.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CLUhmjUy.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-CQmrqqrk.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js +0 -1
- package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DPgbklad.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-DPgbklad.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-pnfAqr_-.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-pnfAqr_-.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-C-e2AMz6.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-J8wPbmfT.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js +0 -1
- package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.js +0 -7
- package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.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-D_a3r6Gc.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-D_a3r6Gc.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BBTRBAQ3.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-B2WqMSG1.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-D7xp5Ij_.js +0 -1
- package/dist/dashboard/assets/SettingsPage-D7xp5Ij_.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-LND6_Ss7.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-BzNnm6Bi.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-C1TbL24N.js +0 -56
- package/dist/dashboard/assets/UsagePage-C1TbL24N.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-mweRcsKp.css +0 -2
- package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js +0 -1
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
- package/dist/dashboard/assets/banner-DFH4Bkte.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-CxDw0lK6.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-DfS1ntiq.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
- package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js +0 -1
- package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
- package/dist/dashboard/assets/entry-vqGb8yVz.js +0 -4
- package/dist/dashboard/assets/entry-vqGb8yVz.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DP-5E01-.js.gz +0 -0
- package/dist/dashboard/assets/index-D36vCZht.js.gz +0 -0
- package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
- package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
- package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
- package/dist/dashboard/assets/live-7_QpnYGu.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-CuMDaTzT.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
- package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-YSoQOUh6.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-C9Ssle9b.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-j7cXjgYX.js.gz +0 -0
- package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
- package/dist/dashboard/assets/switch-DmZcyTIC.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/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/dist/dashboard/assets/{app-DRnQLhsc.js → app-B7kIWFm5.js} +0 -0
- /package/dist/dashboard/assets/{app-DRnQLhsc.js.gz → app-B7kIWFm5.js.gz} +0 -0
|
@@ -35,12 +35,18 @@ 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";
|
|
37
37
|
import type { TrustedProxyBoundary } from "../config/network";
|
|
38
|
+
import type { IpAbuseStore } from "../security/abuse";
|
|
38
39
|
import { gzipResponse } from "./response-compression";
|
|
39
40
|
import { beginConsoleActivity } from "../shared/invoke-guard";
|
|
40
41
|
|
|
41
42
|
export interface ConsoleApiCompositionDeps {
|
|
42
43
|
readonly db: CartethyiaDatabase;
|
|
43
44
|
readonly accessResolver: ConsoleAccessResolver;
|
|
45
|
+
/**
|
|
46
|
+
* The IP-abuse store backing manual IP bans and the live ban list. Absent
|
|
47
|
+
* (reduced compositions) means the IP-ban routes are not offered.
|
|
48
|
+
*/
|
|
49
|
+
readonly ipAbuseStore?: IpAbuseStore;
|
|
44
50
|
/**
|
|
45
51
|
* Drops the cached route snapshot after a console write changes the catalog.
|
|
46
52
|
* Structurally satisfied by `RouteSnapshotService`; declared here so the
|
|
@@ -222,6 +228,7 @@ export function createConsoleRouter(deps: ConsoleApiCompositionDeps): AnyElysia
|
|
|
222
228
|
telemetryBuffer,
|
|
223
229
|
...(deps.stateStore ? { stateStore: deps.stateStore } : {}),
|
|
224
230
|
...(deps.shareStore ? { shareStore: deps.shareStore } : {}),
|
|
231
|
+
...(deps.ipAbuseStore ? { ipAbuseStore: deps.ipAbuseStore } : {}),
|
|
225
232
|
providerRegistry: deps.providerRegistry,
|
|
226
233
|
bundledModelCatalog: deps.bundledModelCatalog,
|
|
227
234
|
networkBindingFactory: deps.networkBindingFactory,
|
|
@@ -62,13 +62,13 @@ export const SHARE_SOCIAL_META: SocialMeta = {
|
|
|
62
62
|
title: "Cartethyia — Bansos Token",
|
|
63
63
|
description:
|
|
64
64
|
"Come and save your tokens 💖 Shared Cartethyia API key — Bansos Token with live quota, usage and model allowlist.",
|
|
65
|
-
image: "/assets/brand/og_bansos.
|
|
66
|
-
imageType: "image/
|
|
65
|
+
image: "/assets/brand/og_bansos.webp",
|
|
66
|
+
imageType: "image/webp",
|
|
67
67
|
// Read from the file's own header, not carried over from a previous image:
|
|
68
68
|
// og:image:width/height are what a crawler uses to allocate the card before
|
|
69
69
|
// the bytes arrive, so a copied size makes every preview crop.
|
|
70
|
-
imageWidth:
|
|
71
|
-
imageHeight:
|
|
70
|
+
imageWidth: 1200,
|
|
71
|
+
imageHeight: 393,
|
|
72
72
|
imageAlt: "Cartethyia Bansos Token — Come and save your tokens",
|
|
73
73
|
};
|
|
74
74
|
|
|
@@ -405,7 +405,7 @@ function getMimeType(filePath: string): string {
|
|
|
405
405
|
* and the modern Vite spelling `app-BCnqn3N3.js` — plus `_`, and then required
|
|
406
406
|
* to *look generated*: the segment must contain a digit or mix upper and lower
|
|
407
407
|
* case. The second half is what keeps a plain word out of the set, since
|
|
408
|
-
* `console-backgrounds.
|
|
408
|
+
* `console-backgrounds.webp` and `openai-light.svg` both match the separator
|
|
409
409
|
* alone and are emphatically not immutable.
|
|
410
410
|
*
|
|
411
411
|
* Getting this wrong in either direction costs real money: a hashed asset
|
|
@@ -36,6 +36,8 @@ import { createLiveRoutes } from "./observability/live";
|
|
|
36
36
|
import { createLogRoutes } from "./observability/logs";
|
|
37
37
|
import { createAuditRoutes } from "./audit/routes";
|
|
38
38
|
import { DrizzleAuditReadStore } from "./audit/store";
|
|
39
|
+
import { createIpBanRoutes } from "./ip-bans/routes";
|
|
40
|
+
import type { IpAbuseStore } from "../security/abuse";
|
|
39
41
|
import { createPerformanceRoutes } from "./observability/performance";
|
|
40
42
|
import { createUpdateRoutes } from "./updates/routes";
|
|
41
43
|
import { createRemoteSupportRoutes } from "./remote-support/routes";
|
|
@@ -55,6 +57,7 @@ import {
|
|
|
55
57
|
createAccountExportCredentialsResolver,
|
|
56
58
|
createAccountSecretResolver,
|
|
57
59
|
} from "../providers/operations/provider-credential-service";
|
|
60
|
+
import { refreshAccountStatus } from "../providers/operations/account-refresh-status";
|
|
58
61
|
import { syncByokProvider } from "../providers/operations/provider-catalog-service";
|
|
59
62
|
import { DrizzleApiKeyStore } from "../persistence/stores/api-key-store";
|
|
60
63
|
import {
|
|
@@ -82,6 +85,11 @@ import type { ConsoleAccessResolver } from "./auth/access";
|
|
|
82
85
|
export interface ConsoleDomainContext {
|
|
83
86
|
readonly db: CartethyiaDatabase;
|
|
84
87
|
readonly accessResolver: ConsoleAccessResolver;
|
|
88
|
+
/**
|
|
89
|
+
* The IP-abuse store backing manual IP bans and the live ban list. Absent
|
|
90
|
+
* (reduced compositions) means the IP-ban routes are not offered.
|
|
91
|
+
*/
|
|
92
|
+
readonly ipAbuseStore?: IpAbuseStore;
|
|
85
93
|
readonly providerRegistry: ProviderRegistry;
|
|
86
94
|
readonly bundledModelCatalog: BundledProviderCatalog;
|
|
87
95
|
readonly networkBindingFactory: ValidatedNetworkBindingFactory;
|
|
@@ -159,6 +167,17 @@ export function registerConsoleDomains(
|
|
|
159
167
|
resolveRefresher: (id) => ctx.providerRegistry.resolveRefresher(id),
|
|
160
168
|
refreshService: ctx.oauthRefreshService,
|
|
161
169
|
});
|
|
170
|
+
/** Manual OAuth token refresh for the catalog route (area 04). */
|
|
171
|
+
const refreshAccountStatusForConsole = (accountId: string, opts?: { readonly origin?: "ui" | "sweep" | "dispatch" }) =>
|
|
172
|
+
refreshAccountStatus(
|
|
173
|
+
{
|
|
174
|
+
db: ctx.db,
|
|
175
|
+
refreshService: ctx.oauthRefreshService,
|
|
176
|
+
resolveRefresher: (id) => ctx.providerRegistry.resolveRefresher(id),
|
|
177
|
+
},
|
|
178
|
+
accountId,
|
|
179
|
+
opts,
|
|
180
|
+
);
|
|
162
181
|
const providerCatalogStore = new DrizzleProviderCatalogStore(ctx.db, {
|
|
163
182
|
telemetryBuffer: ctx.telemetryBuffer,
|
|
164
183
|
bundledModelCatalog: ctx.bundledModelCatalog,
|
|
@@ -238,6 +257,7 @@ export function registerConsoleDomains(
|
|
|
238
257
|
};
|
|
239
258
|
},
|
|
240
259
|
snapshotInvalidator: ctx.routeSnapshotService,
|
|
260
|
+
redis: ctx.redis,
|
|
241
261
|
readAccountInflight: async (providerId, tenantId) =>
|
|
242
262
|
ctx.readRoutingAccountInflight ? ctx.readRoutingAccountInflight(providerId, tenantId) : [],
|
|
243
263
|
});
|
|
@@ -271,6 +291,12 @@ export function registerConsoleDomains(
|
|
|
271
291
|
service: createRemoteSupportService({ port: resolvePort() }),
|
|
272
292
|
}));
|
|
273
293
|
console.use(createAuditRoutes({ store: auditReadStore, accessResolver: ctx.accessResolver }));
|
|
294
|
+
if (ctx.ipAbuseStore) {
|
|
295
|
+
console.use(createIpBanRoutes({
|
|
296
|
+
accessResolver: ctx.accessResolver,
|
|
297
|
+
store: ctx.ipAbuseStore,
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
274
300
|
// InferHub market data. On demand only: a route fetches when its snapshot is
|
|
275
301
|
// past its freshness window, so a page nobody opens causes no upstream calls.
|
|
276
302
|
const inferhubKeys = createInferhubKeyStore(ctx.db);
|
|
@@ -287,6 +313,7 @@ export function registerConsoleDomains(
|
|
|
287
313
|
redis: ctx.redis,
|
|
288
314
|
snapshotInvalidator: ctx.routeSnapshotService,
|
|
289
315
|
resolveExportCredentials,
|
|
316
|
+
refreshAccountStatus: refreshAccountStatusForConsole,
|
|
290
317
|
// Without this, a custom provider only becomes dispatchable after a
|
|
291
318
|
// restart: boot registers BYOK rows once, and the catalog routes are the
|
|
292
319
|
syncByokProvider: (providerId) =>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// IP-ban domain: manual IP bans visible in the Banned Users modal.
|
|
2
|
+
//
|
|
3
|
+
// The gateway's abuse store keeps automatic bans (escalation after a burst).
|
|
4
|
+
// This domain exposes the same store to the operator so an address can be
|
|
5
|
+
// banned BY HAND and listed/unbanned from the console. IP bans are instance
|
|
6
|
+
// state, not tenant data, so every route is guarded by `platform:admin` like
|
|
7
|
+
// the other instance-level operations.
|
|
8
|
+
|
|
9
|
+
import { Elysia, t, type AnyElysia } from "elysia";
|
|
10
|
+
import { errorResponse, requireGlobalAdmin } from "../shared/errors";
|
|
11
|
+
import type { IpAbuseStore } from "../../security/abuse";
|
|
12
|
+
import type { ConsoleAccessResolver } from "../auth/access";
|
|
13
|
+
|
|
14
|
+
/** One active IP ban, as the console returns it. */
|
|
15
|
+
export interface IpBanResponse {
|
|
16
|
+
/** The banned address. */
|
|
17
|
+
readonly ip: string;
|
|
18
|
+
/** When the ban lifts, as ISO timestamp; null = permanent. */
|
|
19
|
+
readonly bannedUntil: string | null;
|
|
20
|
+
/** True when the ban was set by hand rather than by abuse escalation. */
|
|
21
|
+
readonly manual: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface IpBanRoutesConfig {
|
|
25
|
+
readonly accessResolver: ConsoleAccessResolver;
|
|
26
|
+
readonly store: IpAbuseStore;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const BAN_DURATION_CHOICES: readonly { readonly minutes: number | null; readonly label: string }[] = [
|
|
30
|
+
{ minutes: 60, label: "1 hour" },
|
|
31
|
+
{ minutes: 6 * 60, label: "6 hours" },
|
|
32
|
+
{ minutes: 12 * 60, label: "12 hours" },
|
|
33
|
+
{ minutes: 24 * 60, label: "1 day" },
|
|
34
|
+
{ minutes: 7 * 24 * 60, label: "7 days" },
|
|
35
|
+
{ minutes: null, label: "Permanent" },
|
|
36
|
+
] as const;
|
|
37
|
+
|
|
38
|
+
/** Parses a duration choice into minutes; null = permanent. */
|
|
39
|
+
export function parseBanDuration(value: unknown): number | null | undefined {
|
|
40
|
+
if (value === null || value === undefined) return value === null ? null : undefined;
|
|
41
|
+
const minutes = Number(value);
|
|
42
|
+
if (!Number.isFinite(minutes) || minutes <= 0) return undefined;
|
|
43
|
+
return Math.round(minutes);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createIpBanRoutes(config: IpBanRoutesConfig): AnyElysia {
|
|
47
|
+
const { accessResolver, store } = config;
|
|
48
|
+
return new Elysia()
|
|
49
|
+
.get("/ip-bans", async ({ request, set }) => {
|
|
50
|
+
try {
|
|
51
|
+
requireGlobalAdmin(accessResolver(request));
|
|
52
|
+
const bans = await store.listBans();
|
|
53
|
+
return bans.map((ban) => ({
|
|
54
|
+
ip: ban.identity,
|
|
55
|
+
bannedUntil: ban.bannedUntil === null ? null : new Date(ban.bannedUntil).toISOString(),
|
|
56
|
+
manual: true,
|
|
57
|
+
}));
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return errorResponse(e, set, "Could not list IP bans");
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
.post(
|
|
63
|
+
"/ip-bans",
|
|
64
|
+
{ body: t.Object({ ip: t.String(), minutes: t.Optional(t.Nullable(t.Number())) }) },
|
|
65
|
+
async ({ request, body, set }) => {
|
|
66
|
+
try {
|
|
67
|
+
requireGlobalAdmin(accessResolver(request));
|
|
68
|
+
const ip = body.ip.trim();
|
|
69
|
+
if (ip.length === 0)
|
|
70
|
+
return errorResponse(new Error("ip is required"), set, "Invalid IP ban request");
|
|
71
|
+
const minutes = body.minutes === undefined || body.minutes === null ? null : Math.round(body.minutes);
|
|
72
|
+
if (minutes !== null && (!Number.isFinite(minutes) || minutes <= 0))
|
|
73
|
+
return errorResponse(new Error("minutes must be a positive integer or null"), set, "Invalid IP ban request");
|
|
74
|
+
await store.ban(ip, minutes === null ? 0 : minutes * 60_000);
|
|
75
|
+
return {
|
|
76
|
+
ip,
|
|
77
|
+
bannedUntil: minutes === null ? null : new Date(Date.now() + minutes * 60_000).toISOString(),
|
|
78
|
+
manual: true,
|
|
79
|
+
};
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return errorResponse(e, set, "Could not ban IP");
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
.delete("/ip-bans/:ip", async ({ request, params, set }) => {
|
|
86
|
+
try {
|
|
87
|
+
requireGlobalAdmin(accessResolver(request));
|
|
88
|
+
const ip = typeof params?.["ip"] === "string" ? decodeURIComponent(params["ip"]) : "";
|
|
89
|
+
if (ip.length === 0)
|
|
90
|
+
return errorResponse(new Error("ip is required"), set, "Invalid unban request");
|
|
91
|
+
await store.unban(ip);
|
|
92
|
+
return { success: true, ip };
|
|
93
|
+
} catch (e) {
|
|
94
|
+
return errorResponse(e, set, "Could not unban IP");
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { BAN_DURATION_CHOICES };
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { isBundledProviderId } from "../../../providers/provider-contracts";
|
|
12
12
|
import { BUILTIN_DEFAULT_ENDPOINTS } from "../../../protocol/codecs";
|
|
13
13
|
|
|
14
|
-
import { providerDisplayName, providerServiceKinds } from "../../../providers/provider-metadata";
|
|
14
|
+
import { providerDisplayName, providerServiceKinds, customProviderServiceKinds } from "../../../providers/provider-metadata";
|
|
15
15
|
import type { CompatibilityProfile } from "../../../providers/provider-metadata";
|
|
16
16
|
import { supportedWireFamiliesForProvider } from "../../../providers/discovery/probe-wire";
|
|
17
17
|
import { providers } from "../../../persistence/schema";
|
|
@@ -74,6 +74,7 @@ export function mapProviderRow(
|
|
|
74
74
|
row.wireFamilyDefault,
|
|
75
75
|
row.compatibilityProfile,
|
|
76
76
|
);
|
|
77
|
+
const compatibilityProfile = row.compatibilityProfile as CompatibilityProfile | null;
|
|
77
78
|
return {
|
|
78
79
|
providerId: row.id,
|
|
79
80
|
displayName: providerDisplayName(row.id),
|
|
@@ -82,7 +83,9 @@ export function mapProviderRow(
|
|
|
82
83
|
isBuiltIn: isBundledProviderId(row.id),
|
|
83
84
|
configured: false,
|
|
84
85
|
requiresAccount: row.requiresAccount,
|
|
85
|
-
serviceKinds:
|
|
86
|
+
serviceKinds: isBundledProviderId(row.id)
|
|
87
|
+
? providerServiceKinds(row.id)
|
|
88
|
+
: customProviderServiceKinds(compatibilityProfile),
|
|
86
89
|
// No model advertises search; see the note in provider-metadata.ts.
|
|
87
90
|
supportsWebSearch: false,
|
|
88
91
|
supportsModelDiscovery:
|
|
@@ -62,6 +62,72 @@ export const ACCOUNT_STATUSES = ["active", "disabled"] as const;
|
|
|
62
62
|
/** One operator-settable account status. */
|
|
63
63
|
export type AccountStatus = (typeof ACCOUNT_STATUSES)[number];
|
|
64
64
|
|
|
65
|
+
/** Display-only warning type shared with the dashboard contract mirror. */
|
|
66
|
+
export type { ModelWarning, ModelWarningSeverity } from "../../../providers/provider-model-definition";
|
|
67
|
+
import type { ModelWarning } from "../../../providers/provider-model-definition";
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Outcome of one provider-scoped bulk account action (area 02).
|
|
71
|
+
*
|
|
72
|
+
* A tenant-scoped action never touches shared (`tenant_id IS NULL`) rows; the
|
|
73
|
+
* global-admin pair of routes does. `globalSkipped` counts those rows on the
|
|
74
|
+
* tenant path so the console can say why its visible list and the DB disagree.
|
|
75
|
+
* `deleted`/`missing` are present on delete; `updated` on disable. Both actions
|
|
76
|
+
* are idempotent: a second call finds no matching row and reports zero.
|
|
77
|
+
*/
|
|
78
|
+
export interface ProviderAccountBulkActionResult {
|
|
79
|
+
readonly ok: boolean;
|
|
80
|
+
readonly updated: number;
|
|
81
|
+
readonly deleted?: readonly string[];
|
|
82
|
+
readonly missing?: readonly string[];
|
|
83
|
+
readonly globalSkipped: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Cap on one discovery-commit request (D29). */
|
|
87
|
+
export const DISCOVER_MODEL_COMMIT_MAX = 100;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* One model the provider's own listing advertised, projected for the Add-Model
|
|
91
|
+
* picker. Read-only: `GET .../models/discover` writes nothing. `models`
|
|
92
|
+
* metadata here is the fetched `ModelDefinition`, not the static catalog's
|
|
93
|
+
* guess, so the operator can see exactly what the commit will store.
|
|
94
|
+
*/
|
|
95
|
+
export interface DiscoverModelCandidate {
|
|
96
|
+
readonly modelId: string;
|
|
97
|
+
readonly wireFamily: string;
|
|
98
|
+
readonly serviceKind: string;
|
|
99
|
+
readonly endpointPath: string;
|
|
100
|
+
readonly contextLimit: number | null;
|
|
101
|
+
readonly outputLimit: number | null;
|
|
102
|
+
readonly modalities: { readonly input: readonly string[]; readonly output: readonly string[] };
|
|
103
|
+
readonly reasoning: boolean;
|
|
104
|
+
readonly toolCall: boolean;
|
|
105
|
+
readonly freeTier?: boolean;
|
|
106
|
+
readonly proOnly?: boolean;
|
|
107
|
+
readonly alreadyRegistered: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Result of `GET .../models/discover`. */
|
|
111
|
+
export interface DiscoverModelCandidatesResponse {
|
|
112
|
+
readonly supported: boolean;
|
|
113
|
+
readonly candidates: readonly DiscoverModelCandidate[];
|
|
114
|
+
/** Label of the usable account whose credential fetched the listing (D27). */
|
|
115
|
+
readonly accountLabel?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Body of `POST .../models/discover/commit`. */
|
|
119
|
+
export interface DiscoverModelCommitRequest {
|
|
120
|
+
readonly modelIds: readonly string[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Outcome of a discovery commit: the ids persisted and the ones skipped. */
|
|
124
|
+
export interface DiscoverModelCommitResult {
|
|
125
|
+
readonly ok: boolean;
|
|
126
|
+
readonly committed: readonly string[];
|
|
127
|
+
readonly skipped: readonly string[];
|
|
128
|
+
readonly synced: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
65
131
|
const VALID_WIRE_FAMILIES: Record<WireFamily, true> = {
|
|
66
132
|
chat: true,
|
|
67
133
|
responses: true,
|
|
@@ -121,6 +187,16 @@ const ADAPTER_OWNED: Record<string, true> = {
|
|
|
121
187
|
};
|
|
122
188
|
|
|
123
189
|
export function validateCompatibilityProfile(profile: CompatibilityProfile): void {
|
|
190
|
+
if (profile.service_kinds !== undefined) {
|
|
191
|
+
if (!Array.isArray(profile.service_kinds)) {
|
|
192
|
+
throw new GatewayError("invalid_request", 400, "service_kinds must be an array (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
193
|
+
}
|
|
194
|
+
for (const kind of profile.service_kinds) {
|
|
195
|
+
if (!isServiceKind(kind)) {
|
|
196
|
+
throw new GatewayError("invalid_request", 400, `unknown service kind ${String(kind)} (request {requestId}). Check the shape of what you sent — then resend 🪞`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
124
200
|
if (profile.cli_identity !== undefined) {
|
|
125
201
|
if (typeof profile.cli_identity !== "boolean") {
|
|
126
202
|
throw new GatewayError("invalid_request", 400, "cli_identity must be boolean (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
@@ -358,6 +434,8 @@ export interface ModelCatalogEntry {
|
|
|
358
434
|
* provider's own model directory states it.
|
|
359
435
|
*/
|
|
360
436
|
freeTier?: boolean;
|
|
437
|
+
/** Display-only warnings; `null` in storage means none. */
|
|
438
|
+
warnings?: readonly ModelWarning[];
|
|
361
439
|
}
|
|
362
440
|
export interface FlatModelCatalogEntry {
|
|
363
441
|
readonly providerId: string;
|
|
@@ -407,6 +485,11 @@ export interface ModelMetadataOverride {
|
|
|
407
485
|
readonly image?: boolean;
|
|
408
486
|
readonly reasoning?: boolean;
|
|
409
487
|
readonly toolCall?: boolean;
|
|
488
|
+
/**
|
|
489
|
+
* Display-only warnings, editable only on non-builtin rows. `null` clears
|
|
490
|
+
* the stored value; absence leaves it alone.
|
|
491
|
+
*/
|
|
492
|
+
readonly warnings?: readonly ModelWarning[] | null;
|
|
410
493
|
}
|
|
411
494
|
|
|
412
495
|
/** Register payload: model ids plus optional operator metadata. */
|
|
@@ -484,6 +567,27 @@ export interface UpdateProviderAccountRequest {
|
|
|
484
567
|
*/
|
|
485
568
|
staticToken?: boolean;
|
|
486
569
|
}
|
|
570
|
+
/**
|
|
571
|
+
* Response of `POST /providers/:providerId/accounts/:accountId/refresh-token`
|
|
572
|
+
* (area 04). The refresh never changes `status`: `statusAfter` is re-read from
|
|
573
|
+
* the database after the attempt and is never `"active"` by fiat — a disabled
|
|
574
|
+
* account stays disabled, only an operator can re-enable it.
|
|
575
|
+
*/
|
|
576
|
+
export interface RefreshAccountTokenResponse {
|
|
577
|
+
readonly ok: boolean;
|
|
578
|
+
readonly statusAfter: string;
|
|
579
|
+
/** True when an upstream token refresh actually minted a fresh token. */
|
|
580
|
+
readonly tokenRefreshed?: boolean;
|
|
581
|
+
/** ISO timestamp of the fresh access-token expiry, when known. */
|
|
582
|
+
readonly expiresAt?: string;
|
|
583
|
+
/** ISO timestamp of the refresh attempt. */
|
|
584
|
+
readonly refreshedAt?: string;
|
|
585
|
+
/** Structured reason when `ok` is false (`not_oauth`, `static_token`, …). */
|
|
586
|
+
readonly error?: string;
|
|
587
|
+
/** Human detail (failure message) when available. */
|
|
588
|
+
readonly category?: string;
|
|
589
|
+
}
|
|
590
|
+
|
|
487
591
|
export interface ProviderAccountTokenUsage {
|
|
488
592
|
readonly requests: number;
|
|
489
593
|
readonly errors: number;
|
|
@@ -786,6 +890,7 @@ export interface ProviderCatalogStore {
|
|
|
786
890
|
probeAllModels(
|
|
787
891
|
tenantId: string,
|
|
788
892
|
providerId: string,
|
|
893
|
+
mode?: ProbeModelRequest["mode"],
|
|
789
894
|
): Promise<ProbeAllModelsResult>;
|
|
790
895
|
probeAllAccounts(
|
|
791
896
|
tenantId: string,
|
|
@@ -817,6 +922,20 @@ export interface ProviderCatalogStore {
|
|
|
817
922
|
request: UpdateModelMetadataRequest,
|
|
818
923
|
): Promise<boolean>;
|
|
819
924
|
syncModels(tenantId: string, providerId: string): Promise<{ synced: number }>;
|
|
925
|
+
/**
|
|
926
|
+
* Read-only `/models` discovery. Never persists. `supported: false` names a
|
|
927
|
+
* provider with no listing endpoint so the UI can fall back to manual add.
|
|
928
|
+
*/
|
|
929
|
+
listDiscoveryCandidates(
|
|
930
|
+
tenantId: string,
|
|
931
|
+
providerId: string,
|
|
932
|
+
): Promise<{ readonly supported: boolean; readonly candidates: readonly DiscoverModelCandidate[]; readonly accountLabel?: string }>;
|
|
933
|
+
/** Persists a verified subset of a fresh discovery as `source="manual"`. */
|
|
934
|
+
commitDiscoveredModels(
|
|
935
|
+
tenantId: string,
|
|
936
|
+
providerId: string,
|
|
937
|
+
modelIds: readonly string[],
|
|
938
|
+
): Promise<{ readonly committed: readonly string[]; readonly skipped: readonly string[]; readonly synced: number }>;
|
|
820
939
|
listAccounts(tenantId: string, providerId: string): Promise<readonly ProviderAccountResponse[]>;
|
|
821
940
|
listAllAccounts(tenantId: string): Promise<readonly ProviderAccountResponse[]>;
|
|
822
941
|
/**
|
|
@@ -891,6 +1010,10 @@ export interface ProviderCatalogStore {
|
|
|
891
1010
|
dayKey: string,
|
|
892
1011
|
): Promise<readonly BuddyGrowthTaskLedgerEntry[]>;
|
|
893
1012
|
recoverAccount(tenantId: string, providerId: string, accountId: string): Promise<boolean>;
|
|
1013
|
+
/** One UPDATE over this tenant's cooling accounts (D07/D08); evidence kept. */
|
|
1014
|
+
disableCooldowns(tenantId: string, providerId: string): Promise<ProviderAccountBulkActionResult>;
|
|
1015
|
+
/** One DELETE over this tenant's banned accounts (D12); idempotent. */
|
|
1016
|
+
deleteBanned(tenantId: string, providerId: string): Promise<ProviderAccountBulkActionResult>;
|
|
894
1017
|
}
|
|
895
1018
|
|
|
896
1019
|
/**
|
|
@@ -11,7 +11,17 @@
|
|
|
11
11
|
import type { AccessDecision } from "../../../security/access-control";
|
|
12
12
|
import { ConsoleDomainError, requireScope, requireTenantAnyScope } from "../../shared/errors";
|
|
13
13
|
import { GatewayError } from "../../../protocol/gateway-error";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
DISCOVER_MODEL_COMMIT_MAX,
|
|
16
|
+
MODEL_READ_SCOPES,
|
|
17
|
+
MODEL_WRITE_SCOPES,
|
|
18
|
+
} from "./contracts";
|
|
19
|
+
import type { DiscoverModelCandidatesResponse, DiscoverModelCommitResult } from "./contracts";
|
|
20
|
+
import {
|
|
21
|
+
MODEL_WARNING_KIND_MAX_LENGTH,
|
|
22
|
+
MODEL_WARNING_MESSAGE_MAX_LENGTH,
|
|
23
|
+
MODEL_WARNING_SEVERITIES,
|
|
24
|
+
} from "../../../providers/provider-model-definition";
|
|
15
25
|
import type {
|
|
16
26
|
ByokConnectionTestRequest,
|
|
17
27
|
ByokConnectionTestResult,
|
|
@@ -47,6 +57,60 @@ function validateModelMetadata(metadata: ModelMetadataOverride | undefined): voi
|
|
|
47
57
|
`${field} must be a positive integer, or null for unknown`,
|
|
48
58
|
);
|
|
49
59
|
}
|
|
60
|
+
// `null` clears the stored blob; an absent field leaves it alone. Only a
|
|
61
|
+
// non-builtin row can actually carry an edit — the store enforces that
|
|
62
|
+
// invariant. Bounds here stop a hostile payload from persisting a multi-MB
|
|
63
|
+
// display banner; severity is a closed whitelist.
|
|
64
|
+
if (metadata.warnings === null || metadata.warnings === undefined) return;
|
|
65
|
+
if (!Array.isArray(metadata.warnings)) {
|
|
66
|
+
throw new ConsoleDomainError("invalid_request", 400, "warnings must be an array or null");
|
|
67
|
+
}
|
|
68
|
+
for (const [index, warning] of metadata.warnings.entries()) {
|
|
69
|
+
if (warning === null || typeof warning !== "object" || Array.isArray(warning)) {
|
|
70
|
+
throw new ConsoleDomainError("invalid_request", 400, `warnings[${index}] must be an object`);
|
|
71
|
+
}
|
|
72
|
+
const kind = (warning as { kind?: unknown }).kind;
|
|
73
|
+
const message = (warning as { message?: unknown }).message;
|
|
74
|
+
const severity = (warning as { severity?: unknown }).severity;
|
|
75
|
+
if (typeof kind !== "string" || kind.trim().length === 0 || kind.length > MODEL_WARNING_KIND_MAX_LENGTH) {
|
|
76
|
+
throw new ConsoleDomainError(
|
|
77
|
+
"invalid_request",
|
|
78
|
+
400,
|
|
79
|
+
`warnings[${index}].kind must be a non-empty string up to ${MODEL_WARNING_KIND_MAX_LENGTH} chars`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (typeof message !== "string" || message.trim().length === 0 || message.length > MODEL_WARNING_MESSAGE_MAX_LENGTH) {
|
|
83
|
+
throw new ConsoleDomainError(
|
|
84
|
+
"invalid_request",
|
|
85
|
+
400,
|
|
86
|
+
`warnings[${index}].message must be a non-empty string up to ${MODEL_WARNING_MESSAGE_MAX_LENGTH} chars`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (
|
|
90
|
+
severity !== undefined &&
|
|
91
|
+
(typeof severity !== "string" ||
|
|
92
|
+
!(MODEL_WARNING_SEVERITIES as readonly string[]).includes(severity))
|
|
93
|
+
) {
|
|
94
|
+
throw new ConsoleDomainError(
|
|
95
|
+
"invalid_request",
|
|
96
|
+
400,
|
|
97
|
+
`warnings[${index}].severity must be one of ${MODEL_WARNING_SEVERITIES.join(", ")}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Discovery lives in the provider layer and raises `GatewayError`; the console
|
|
105
|
+
* speaks `ConsoleDomainError`. Keeping the exact code/status/message lets the
|
|
106
|
+
* dashboard toast name the real upstream reason rather than a generic failure.
|
|
107
|
+
*/
|
|
108
|
+
function translateDiscoveryError(error: unknown): unknown {
|
|
109
|
+
if (error instanceof ConsoleDomainError) return error;
|
|
110
|
+
if (error instanceof GatewayError) {
|
|
111
|
+
return new ConsoleDomainError(error.code, error.status, error.message);
|
|
112
|
+
}
|
|
113
|
+
return error;
|
|
50
114
|
}
|
|
51
115
|
|
|
52
116
|
export function createModelCatalogOperations(config: ProviderCatalogConfig) {
|
|
@@ -164,14 +228,7 @@ export function createModelCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
164
228
|
await config.snapshotInvalidator?.invalidate();
|
|
165
229
|
return result;
|
|
166
230
|
} catch (error) {
|
|
167
|
-
|
|
168
|
-
// console speaks `ConsoleDomainError`. Translating here keeps the
|
|
169
|
-
// upstream-vs-operator distinction and the status code intact, so the
|
|
170
|
-
// dashboard toast names the real reason instead of a generic failure.
|
|
171
|
-
if (error instanceof GatewayError) {
|
|
172
|
-
throw new ConsoleDomainError(error.code, error.status, error.message);
|
|
173
|
-
}
|
|
174
|
-
throw error;
|
|
231
|
+
throw translateDiscoveryError(error);
|
|
175
232
|
}
|
|
176
233
|
}
|
|
177
234
|
/** Manually registers additional model ids on an existing provider (tenant-bound, no upstream call). */,
|
|
@@ -247,9 +304,10 @@ export function createModelCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
247
304
|
async probeAllModels(
|
|
248
305
|
access: AccessDecision | undefined,
|
|
249
306
|
providerId: string,
|
|
307
|
+
mode?: ProbeModelRequest["mode"],
|
|
250
308
|
): Promise<ProbeAllModelsResult> {
|
|
251
309
|
const a = requireTenantAnyScope(access, MODEL_WRITE_SCOPES);
|
|
252
|
-
return config.store.probeAllModels(a.tenantId, providerId);
|
|
310
|
+
return config.store.probeAllModels(a.tenantId, providerId, mode);
|
|
253
311
|
},
|
|
254
312
|
async probeAllAccounts(
|
|
255
313
|
access: AccessDecision | undefined,
|
|
@@ -299,6 +357,60 @@ export function createModelCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
299
357
|
await config.snapshotInvalidator?.invalidate();
|
|
300
358
|
return { success: true };
|
|
301
359
|
},
|
|
360
|
+
/**
|
|
361
|
+
* `GET .../models/discover`: fetch the provider's own listing without any
|
|
362
|
+
* write (D30). A provider with no listing endpoint answers `supported:false`
|
|
363
|
+
* so the UI can fall back to manual add instead of showing an error.
|
|
364
|
+
*/
|
|
365
|
+
async listDiscoveryCandidates(
|
|
366
|
+
access: AccessDecision | undefined,
|
|
367
|
+
providerId: string,
|
|
368
|
+
): Promise<DiscoverModelCandidatesResponse> {
|
|
369
|
+
const a = requireTenantAnyScope(access, MODEL_READ_SCOPES);
|
|
370
|
+
try {
|
|
371
|
+
return await config.store.listDiscoveryCandidates(a.tenantId, providerId);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
throw translateDiscoveryError(error);
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
/**
|
|
377
|
+
* `POST .../models/discover/commit`: the store re-fetches the roster and
|
|
378
|
+
* rejects ids outside it (D31), so no hand-forged id can be persisted.
|
|
379
|
+
*/
|
|
380
|
+
async commitDiscoveredModels(
|
|
381
|
+
access: AccessDecision | undefined,
|
|
382
|
+
providerId: string,
|
|
383
|
+
modelIds: readonly string[],
|
|
384
|
+
): Promise<DiscoverModelCommitResult> {
|
|
385
|
+
const a = requireTenantAnyScope(access, MODEL_WRITE_SCOPES);
|
|
386
|
+
if (modelIds.some((id) => typeof id !== "string" || id.trim().length === 0)) {
|
|
387
|
+
throw new ConsoleDomainError(
|
|
388
|
+
"invalid_request",
|
|
389
|
+
400,
|
|
390
|
+
"modelIds must be non-empty model ids",
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
if (modelIds.length === 0 || modelIds.length > DISCOVER_MODEL_COMMIT_MAX) {
|
|
394
|
+
throw new ConsoleDomainError(
|
|
395
|
+
"invalid_request",
|
|
396
|
+
400,
|
|
397
|
+
`modelIds must contain 1-${DISCOVER_MODEL_COMMIT_MAX} entries`,
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
const result = await config.store.commitDiscoveredModels(a.tenantId, providerId, modelIds);
|
|
402
|
+
await config.auditSink?.record({
|
|
403
|
+
access: a,
|
|
404
|
+
action: "provider.models.registered",
|
|
405
|
+
target: providerId,
|
|
406
|
+
detail: { modelIds: result.committed, source: "discover-commit" },
|
|
407
|
+
});
|
|
408
|
+
await config.snapshotInvalidator?.invalidate();
|
|
409
|
+
return { ok: true, ...result };
|
|
410
|
+
} catch (error) {
|
|
411
|
+
throw translateDiscoveryError(error);
|
|
412
|
+
}
|
|
413
|
+
},
|
|
302
414
|
async deleteModelsBulk(
|
|
303
415
|
access: AccessDecision | undefined,
|
|
304
416
|
providerId: string,
|