cartethyia 2.7.8 → 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-BRWKPQPf.js → CliToolDetailPage-ixwgKk53.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.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-Cv-2Ol_K.js → InjectSystemPromptPanel-BOoO8rpz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-ChJuNNVN.js} +1 -1
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.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-emlTuxz6.js → ProxyPage-BJ87Pp0U.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-CMJKNYWb.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-CUZrKs9r.js → SetupPage-CFNcATJJ.js} +1 -1
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-jWmUdDpy.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-DyI_mJfi.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-B-yXtCqb.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-_HwAEqFF.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-ByjCk0o9.js → clipboard-button-CJ4Zty89.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-B5L2jPQ_.js} +1 -1
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-DkjUouAj.js} +1 -1
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/{entry-COaC-irN.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-DcHJyRiu.js → formatters-CrsdLVsn.js} +1 -1
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/{index-CgKmFngv.js → index-BU64Lqvp.js} +2 -2
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2BirKry.js → inline-B2aDLyyg.js} +1 -1
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/{input-D-qV9dKY.js → input-BCVkdR9d.js} +1 -1
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-DNcAdMK-.js} +1 -1
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-DgrIH0hw.js} +1 -1
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/{live-DGizF06-.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-C9UE-bOr.js → network-CRib7H_2.js} +1 -1
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-BKQE8Xpz.js} +1 -1
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-BAywyMzS.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-BCOcv8WK.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-ByKt3Q1g.js → stack-DWCwU3fh.js} +1 -1
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/{state-DXbGTx7N.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-ZeTsW623.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 +210 -6
- 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/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-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.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 +0 -1
- 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/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/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-CTxoqyYB.js → app-B7kIWFm5.js} +0 -0
- /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-B7kIWFm5.js.gz} +0 -0
|
@@ -10,6 +10,7 @@ import { applyRouteUserAgent, type ProviderDispatchContext } from "../../provide
|
|
|
10
10
|
import { abortGatewayError, createUpstreamDeadlineLifecycle } from '../../providers/operations/upstream-deadline';
|
|
11
11
|
import { mapClaudeHttpError } from '../messages-errors';
|
|
12
12
|
import { isRecord } from '../codecs';
|
|
13
|
+
import { upstreamMessageAsIs, MAX_UPSTREAM_ERROR_BYTES } from '../../transport/failure-policy';
|
|
13
14
|
import { parseClaudeSseStream, claudeResponseToEvents } from '../response/messages';
|
|
14
15
|
|
|
15
16
|
export async function* sendClaudeMessagesRequest(
|
|
@@ -67,20 +68,30 @@ export async function* sendClaudeMessagesRequest(
|
|
|
67
68
|
try {
|
|
68
69
|
json = JSON.parse(body) as unknown;
|
|
69
70
|
} catch {
|
|
71
|
+
const fallback = "Claude response is not valid JSON (request {requestId}). The upstream came back empty-handed — retry 🌙";
|
|
72
|
+
const upstreamText = upstreamMessageAsIs(body, "");
|
|
70
73
|
throw new GatewayError(
|
|
71
74
|
"platform_unavailable",
|
|
72
75
|
502,
|
|
73
|
-
|
|
74
|
-
{
|
|
76
|
+
upstreamText.message || fallback,
|
|
77
|
+
{
|
|
78
|
+
raw: body.slice(0, MAX_UPSTREAM_ERROR_BYTES),
|
|
79
|
+
...(upstreamText.hadUpstreamText ? { upstreamMessageAsIs: true } : {}),
|
|
80
|
+
},
|
|
75
81
|
"upstream",
|
|
76
82
|
);
|
|
77
83
|
}
|
|
78
84
|
if (!isRecord(json)) {
|
|
85
|
+
const fallback = "Claude response must be an object (request {requestId}). The upstream came back empty-handed — retry 🌙";
|
|
86
|
+
const upstreamText = upstreamMessageAsIs(body, "");
|
|
79
87
|
throw new GatewayError(
|
|
80
88
|
"platform_unavailable",
|
|
81
89
|
502,
|
|
82
|
-
|
|
83
|
-
{
|
|
90
|
+
upstreamText.message || fallback,
|
|
91
|
+
{
|
|
92
|
+
raw: body.slice(0, MAX_UPSTREAM_ERROR_BYTES),
|
|
93
|
+
...(upstreamText.hadUpstreamText ? { upstreamMessageAsIs: true } : {}),
|
|
94
|
+
},
|
|
84
95
|
"upstream",
|
|
85
96
|
);
|
|
86
97
|
}
|
|
@@ -71,7 +71,7 @@ export function parseUpstreamBackoff(headers: {
|
|
|
71
71
|
*
|
|
72
72
|
* - **Relative** — "quota will reset in 3 hours", "try again in 30s".
|
|
73
73
|
* - **Absolute** — "your usage will reset at 2026-09-24 02:12:51 UTC+8"
|
|
74
|
-
* (
|
|
74
|
+
* (CodeBuddy `6004`). An absolute stamp is *not* a duration, and
|
|
75
75
|
* the relative-only pattern silently missed it, so the account fell back to
|
|
76
76
|
* the generic 15-minute default while the provider had actually parked it
|
|
77
77
|
* for the rest of the window — the account kept re-entering rotation and
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
isContextLengthFailure,
|
|
7
7
|
throwIfHtmlResponse,
|
|
8
8
|
mapUpstreamHttpError,
|
|
9
|
-
extractUpstreamMessage,
|
|
10
9
|
extractHtmlTitle,
|
|
11
10
|
upstreamErrorIdentifier,
|
|
11
|
+
upstreamMessageAsIs,
|
|
12
12
|
} from "../../transport/failure-policy";
|
|
13
13
|
import { applyRouteUserAgent, type ProviderDispatchTarget, type ProviderId, type ProviderDispatchContext } from "../provider-contracts";
|
|
14
14
|
import { BaseProviderAdapter } from "./base-adapter";
|
|
@@ -439,11 +439,12 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
|
|
|
439
439
|
}
|
|
440
440
|
json = parsed as Record<string, unknown>;
|
|
441
441
|
} catch {
|
|
442
|
-
const
|
|
442
|
+
const fallback = `Upstream returned a non-JSON response: ${extractHtmlTitle(raw) || raw.trim() || `HTTP ${res.status}`} (request {requestId}). The connection ghosted us — come back in a moment 👻`;
|
|
443
|
+
const upstreamText = upstreamMessageAsIs(raw, "");
|
|
443
444
|
throw new GatewayError(
|
|
444
445
|
"transport_unavailable",
|
|
445
446
|
502,
|
|
446
|
-
|
|
447
|
+
`${upstreamText.message || fallback}`.slice(0, 500),
|
|
447
448
|
{ upstreamStatus: res.status, contentType },
|
|
448
449
|
"upstream",
|
|
449
450
|
);
|
|
@@ -485,15 +486,16 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
|
|
|
485
486
|
const classification = isContextLengthFailure(json)
|
|
486
487
|
? { code: "context_length_exceeded" as const, status: 413, origin: "upstream" as const, credentialEvidence: false, rateLimitScope: undefined }
|
|
487
488
|
: classifyUpstreamError(providerStatus, identifier);
|
|
488
|
-
const
|
|
489
|
+
const upstreamText = upstreamMessageAsIs(json, "");
|
|
489
490
|
throw new GatewayError(
|
|
490
491
|
classification.code,
|
|
491
492
|
classification.status,
|
|
492
|
-
message,
|
|
493
|
+
upstreamText.message || "Upstream reported an error",
|
|
493
494
|
{
|
|
494
495
|
providerId: this.provider_id,
|
|
495
496
|
...(providerStatus === undefined ? {} : { providerStatus }),
|
|
496
497
|
...(identifier === undefined ? {} : { providerCode: identifier }),
|
|
498
|
+
...(upstreamText.hadUpstreamText ? { upstreamMessageAsIs: true } : {}),
|
|
497
499
|
...(classification.credentialEvidence ? { credentialEvidence: true } : {}),
|
|
498
500
|
...(classification.rateLimitScope
|
|
499
501
|
? { rateLimitScope: classification.rateLimitScope }
|
|
@@ -169,6 +169,22 @@ async function releaseLease(
|
|
|
169
169
|
);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/**
|
|
173
|
+
* D21 invariant guard: a refresh is not a health verdict, so mutating the
|
|
174
|
+
* account's `status` here would silently re-enable an account a refresh was
|
|
175
|
+
* never meant to resurrect. The `.set()` calls in `persistRefreshed` accept
|
|
176
|
+
* `status` at the type level (it is a column on `provider_accounts`), so the
|
|
177
|
+
* guard is a runtime assertion that fails loudly in development instead of
|
|
178
|
+
* letting a future edit change health state from the refresh path.
|
|
179
|
+
*/
|
|
180
|
+
function assertRefreshNeverMutatesStatus(set: Record<string, unknown>): void {
|
|
181
|
+
if ("status" in set) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
"persistRefreshed must never write `status`; a refresh is not a health verdict",
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
172
188
|
/** Persists a successful refresh and releases the lease, fenced on still holding it. */
|
|
173
189
|
async function persistRefreshed(
|
|
174
190
|
db: CartethyiaDatabase,
|
|
@@ -177,16 +193,21 @@ async function persistRefreshed(
|
|
|
177
193
|
result: OAuthTokenRefreshResult,
|
|
178
194
|
): Promise<boolean> {
|
|
179
195
|
return db.transaction(async (tx) => {
|
|
196
|
+
// This function must never write `status`; a refresh is not a health
|
|
197
|
+
// verdict. The guard below makes an accidental status write a loud dev
|
|
198
|
+
// failure rather than a silent re-enable.
|
|
199
|
+
const oauthSet = {
|
|
200
|
+
...(result.refresh === undefined
|
|
201
|
+
? {}
|
|
202
|
+
: { refreshCiphertext: encryptCredential(result.refresh) }),
|
|
203
|
+
expiresAt: result.expiresAt,
|
|
204
|
+
leaseOwner: null,
|
|
205
|
+
leaseExpiresAt: null,
|
|
206
|
+
};
|
|
207
|
+
assertRefreshNeverMutatesStatus(oauthSet);
|
|
180
208
|
const leaseRows = await tx
|
|
181
209
|
.update(providerOauthStates)
|
|
182
|
-
.set(
|
|
183
|
-
...(result.refresh === undefined
|
|
184
|
-
? {}
|
|
185
|
-
: { refreshCiphertext: encryptCredential(result.refresh) }),
|
|
186
|
-
expiresAt: result.expiresAt,
|
|
187
|
-
leaseOwner: null,
|
|
188
|
-
leaseExpiresAt: null,
|
|
189
|
-
})
|
|
210
|
+
.set(oauthSet)
|
|
190
211
|
.where(
|
|
191
212
|
and(
|
|
192
213
|
eq(providerOauthStates.providerAccountId, accountId),
|
|
@@ -195,23 +216,27 @@ async function persistRefreshed(
|
|
|
195
216
|
)
|
|
196
217
|
.returning({ id: providerOauthStates.providerAccountId });
|
|
197
218
|
if (leaseRows.length === 0) return false;
|
|
219
|
+
const accountSet = {
|
|
220
|
+
credentialCiphertext: encryptCredential(result.access),
|
|
221
|
+
// Only what the refresh reported: a refresher that learned nothing new
|
|
222
|
+
// leaves the stored configuration alone rather than blanking it.
|
|
223
|
+
// A learned identity only replaces a default label — an operator's
|
|
224
|
+
// custom rename is never overwritten. The CASE keeps the read and the
|
|
225
|
+
// write in one statement, so a concurrent rename cannot lose to the
|
|
226
|
+
// refresh's learned identity.
|
|
227
|
+
...(result.accountLabel === undefined
|
|
228
|
+
? {}
|
|
229
|
+
: {
|
|
230
|
+
label: sql`CASE WHEN lower(trim(${providerAccounts.label})) = lower(trim(${providerAccounts.providerId})) THEN ${result.accountLabel} ELSE ${providerAccounts.label} END`,
|
|
231
|
+
}),
|
|
232
|
+
...(result.auth_state === undefined ? {} : { authState: result.auth_state }),
|
|
233
|
+
};
|
|
234
|
+
// The second half of the D21 invariant: the credential write must never
|
|
235
|
+
// touch the account's health state either.
|
|
236
|
+
assertRefreshNeverMutatesStatus(accountSet);
|
|
198
237
|
await tx
|
|
199
238
|
.update(providerAccounts)
|
|
200
|
-
.set(
|
|
201
|
-
credentialCiphertext: encryptCredential(result.access),
|
|
202
|
-
// Only what the refresh reported: a refresher that learned nothing new
|
|
203
|
-
// leaves the stored configuration alone rather than blanking it.
|
|
204
|
-
// A learned identity only replaces a default label — an operator's
|
|
205
|
-
// custom rename is never overwritten. The CASE keeps the read and the
|
|
206
|
-
// write in one statement, so a concurrent rename cannot lose to the
|
|
207
|
-
// refresh's learned identity.
|
|
208
|
-
...(result.accountLabel === undefined
|
|
209
|
-
? {}
|
|
210
|
-
: {
|
|
211
|
-
label: sql`CASE WHEN lower(trim(${providerAccounts.label})) = lower(trim(${providerAccounts.providerId})) THEN ${result.accountLabel} ELSE ${providerAccounts.label} END`,
|
|
212
|
-
}),
|
|
213
|
-
...(result.auth_state === undefined ? {} : { authState: result.auth_state }),
|
|
214
|
-
})
|
|
239
|
+
.set(accountSet)
|
|
215
240
|
.where(eq(providerAccounts.id, accountId));
|
|
216
241
|
invalidateCredentialCache(accountId);
|
|
217
242
|
return true;
|
|
@@ -240,12 +265,21 @@ function buildRefreshContext(row: AccountRow, includeAccessToken: boolean): OAut
|
|
|
240
265
|
/**
|
|
241
266
|
* Marks an account permanently unusable after a definitive refresh failure,
|
|
242
267
|
* fenced on the lease.
|
|
268
|
+
*
|
|
269
|
+
* @param preserveOperatorDisable When true, an account that is already
|
|
270
|
+
* `disabled` with no `cooldownUntil` — the operator's own disable — keeps its
|
|
271
|
+
* lack of a recovery deadline (D23). A machine disable (this function being
|
|
272
|
+
* the machine) always writes a deadline for itself, but it must not convert
|
|
273
|
+
* an operator's terminal decision into a recoverable one: adding a deadline
|
|
274
|
+
* to an operator-disabled row is what made the Recover button appear on an
|
|
275
|
+
* account the operator disabled on purpose.
|
|
243
276
|
*/
|
|
244
277
|
async function disableAccount(
|
|
245
278
|
db: CartethyiaDatabase,
|
|
246
279
|
accountId: string,
|
|
247
280
|
owner: string,
|
|
248
281
|
message: string,
|
|
282
|
+
options: { readonly preserveOperatorDisable?: boolean } = {},
|
|
249
283
|
): Promise<void> {
|
|
250
284
|
await db.transaction(async (tx) => {
|
|
251
285
|
const leaseRows = await tx
|
|
@@ -260,6 +294,22 @@ async function disableAccount(
|
|
|
260
294
|
.returning({ id: providerOauthStates.providerAccountId });
|
|
261
295
|
if (leaseRows.length === 0) return;
|
|
262
296
|
const disabledRecoveryMs = resolveAccountDisabledRecoveryMs();
|
|
297
|
+
// D23: read the row before writing so an operator disable (status `disabled`
|
|
298
|
+
// with no deadline) is not upgraded with a machine recovery deadline. The
|
|
299
|
+
// read is cheap and the lease already serializes refresh writers.
|
|
300
|
+
let isOperatorDisable = false;
|
|
301
|
+
if (options.preserveOperatorDisable === true) {
|
|
302
|
+
const rows = await tx
|
|
303
|
+
.select({
|
|
304
|
+
status: providerAccounts.status,
|
|
305
|
+
cooldownUntil: providerAccounts.cooldownUntil,
|
|
306
|
+
})
|
|
307
|
+
.from(providerAccounts)
|
|
308
|
+
.where(eq(providerAccounts.id, accountId))
|
|
309
|
+
.limit(1);
|
|
310
|
+
const row = rows[0];
|
|
311
|
+
isOperatorDisable = row?.status === "disabled" && row.cooldownUntil == null;
|
|
312
|
+
}
|
|
263
313
|
await tx
|
|
264
314
|
.update(providerAccounts)
|
|
265
315
|
.set({
|
|
@@ -277,8 +327,10 @@ async function disableAccount(
|
|
|
277
327
|
// parks the credential for good. The deadline is also what distinguishes
|
|
278
328
|
// this (machine-disabled) from an operator's own disable, which leaves
|
|
279
329
|
// `cooldownUntil` null and is therefore never swept. See
|
|
280
|
-
// `resolveAccountDisabledRecoveryMs`.
|
|
281
|
-
|
|
330
|
+
// `resolveAccountDisabledRecoveryMs`. An operator-disabled row keeps its
|
|
331
|
+
// `null` deadline (D23): the machine must not make the operator's
|
|
332
|
+
// terminal disable look recoverable.
|
|
333
|
+
...(!isOperatorDisable && disabledRecoveryMs > 0
|
|
282
334
|
? { cooldownUntil: new Date(Date.now() + disabledRecoveryMs) }
|
|
283
335
|
: {}),
|
|
284
336
|
})
|
|
@@ -299,20 +351,33 @@ async function disableAccount(
|
|
|
299
351
|
* operator or an explicit recovery may re-enable it), while an `active` one
|
|
300
352
|
* stays `active`. The static marker and the error clearing are orthogonal to
|
|
301
353
|
* the account's health state.
|
|
354
|
+
*
|
|
355
|
+
* D24: for a `disabled` row the error fields are deliberately kept. Once the
|
|
356
|
+
* account is parked, `lastErrorCategory` is the only record of *why* (e.g.
|
|
357
|
+
* `auth_invalidated` → "Re-login required"); wiping it on the static
|
|
358
|
+
* annotation would leave a disabled account with no diagnosable reason. An
|
|
359
|
+
* `active` row still clears them — there the error is stale by definition.
|
|
302
360
|
*/
|
|
303
361
|
async function markStaticToken(db: CartethyiaDatabase, accountId: string): Promise<void> {
|
|
362
|
+
const rows = await db
|
|
363
|
+
.select({ status: providerAccounts.status })
|
|
364
|
+
.from(providerAccounts)
|
|
365
|
+
.where(eq(providerAccounts.id, accountId))
|
|
366
|
+
.limit(1);
|
|
367
|
+
const isDisabled = rows[0]?.status === "disabled";
|
|
304
368
|
await db
|
|
305
369
|
.update(providerAccounts)
|
|
306
370
|
.set({
|
|
307
371
|
staticToken: true,
|
|
372
|
+
cooldownUntil: null,
|
|
308
373
|
// A static token is a normal, usable credential, not an error state: any
|
|
309
374
|
// error left by the failed refresh would otherwise keep the row reading
|
|
310
375
|
// "Re-login required" while it is in fact serving. Mirrors the console's
|
|
311
|
-
// static-token toggle.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
376
|
+
// static-token toggle. For a `disabled` row the category is preserved
|
|
377
|
+
// (D24), because there it is the diagnosis, not a stale artifact.
|
|
378
|
+
...(isDisabled
|
|
379
|
+
? {}
|
|
380
|
+
: { lastError: null, lastErrorCategory: null, lastErrorAt: null }),
|
|
316
381
|
})
|
|
317
382
|
.where(eq(providerAccounts.id, accountId));
|
|
318
383
|
}
|
|
@@ -362,7 +427,8 @@ function failureStatus(error: unknown): number | undefined {
|
|
|
362
427
|
* still perfectly usable. This pass re-reads those rows once at startup and
|
|
363
428
|
* moves each to where it now belongs:
|
|
364
429
|
*
|
|
365
|
-
* - access token still usable ⇒ static token
|
|
430
|
+
* - access token still usable ⇒ static token; the account stays `disabled`;
|
|
431
|
+
* only an operator can re-enable it;
|
|
366
432
|
* - otherwise ⇒ the `auth_invalidated` category the console renders as
|
|
367
433
|
* "Re-login required" and the quota sweep excludes.
|
|
368
434
|
*
|
|
@@ -495,15 +561,20 @@ export class OAuthRefreshService {
|
|
|
495
561
|
* that just refreshed cannot cause the caller to reuse a dead cached
|
|
496
562
|
* secret. Returns the current decrypted access token, or `null` when the
|
|
497
563
|
* account isn't OAuth, has no stored refresh token, or refresh failed.
|
|
564
|
+
*
|
|
565
|
+
* `opts.origin` labels who asked for the refresh (`ui`/`sweep`/`dispatch`)
|
|
566
|
+
* and is carried on the `token_refresh` log lines (D26). The origin is
|
|
567
|
+
* observational only — it never changes what a refresh does.
|
|
498
568
|
*/
|
|
499
569
|
async ensureFreshAccessToken(
|
|
500
570
|
accountId: string,
|
|
501
571
|
refresher: OAuthTokenRefresher,
|
|
502
|
-
opts: { force?: boolean; skewMs?: number } = {},
|
|
572
|
+
opts: { force?: boolean; skewMs?: number; origin?: "ui" | "sweep" | "dispatch" } = {},
|
|
503
573
|
): Promise<string | null> {
|
|
504
574
|
const force = opts.force === true;
|
|
505
575
|
// No explicit skew: the per-provider lead is applied at load time.
|
|
506
576
|
const skewMs = opts.skewMs;
|
|
577
|
+
const origin = opts.origin ?? "sweep";
|
|
507
578
|
// Forced refreshes get their own in-flight slot so they don't merge with
|
|
508
579
|
// a raced non-force call that would otherwise short-circuit.
|
|
509
580
|
const key = force ? `${accountId}:force` : accountId;
|
|
@@ -518,7 +589,7 @@ export class OAuthRefreshService {
|
|
|
518
589
|
const oldestKey = this.#inFlight.keys().next().value;
|
|
519
590
|
if (oldestKey !== undefined) this.#inFlight.delete(oldestKey);
|
|
520
591
|
}
|
|
521
|
-
const task = this.#refreshIfDue(accountId, refresher, skewMs, force).finally(() => {
|
|
592
|
+
const task = this.#refreshIfDue(accountId, refresher, skewMs, force, origin).finally(() => {
|
|
522
593
|
this.#inFlight.delete(key);
|
|
523
594
|
});
|
|
524
595
|
this.#inFlight.set(key, task);
|
|
@@ -530,6 +601,7 @@ export class OAuthRefreshService {
|
|
|
530
601
|
refresher: OAuthTokenRefresher,
|
|
531
602
|
skewMs: number | undefined,
|
|
532
603
|
force: boolean,
|
|
604
|
+
origin: "ui" | "sweep" | "dispatch",
|
|
533
605
|
): Promise<string | null> {
|
|
534
606
|
const account = await loadAccountWithFreshness(this.#db, accountId, skewMs);
|
|
535
607
|
if (!account || account.row.credentialKind !== "oauth") return null;
|
|
@@ -547,18 +619,24 @@ export class OAuthRefreshService {
|
|
|
547
619
|
? decryptCredentialToString(row.credentialCiphertext)
|
|
548
620
|
: null;
|
|
549
621
|
}
|
|
550
|
-
return this.#refreshNow(row, refresher, skewMs);
|
|
622
|
+
return this.#refreshNow(row, refresher, skewMs, origin);
|
|
551
623
|
}
|
|
552
624
|
|
|
553
625
|
async #refreshNow(
|
|
554
626
|
row: AccountRow,
|
|
555
627
|
refresher: OAuthTokenRefresher,
|
|
556
628
|
skewMs: number | undefined,
|
|
629
|
+
origin: "ui" | "sweep" | "dispatch",
|
|
557
630
|
): Promise<string | null> {
|
|
631
|
+
// D26: statusBefore is read once up front (the row projection used by the
|
|
632
|
+
// refresh path does not carry status) and statusAfter is re-read after any
|
|
633
|
+
// mutation, so the log proves a refresh never flipped the account.
|
|
634
|
+
const statusBefore = await readAccountStatus(this.#db, row.id);
|
|
558
635
|
pushStructuredConsoleLog("info", "OAuth token refresh started", {
|
|
559
636
|
event: "token_refresh",
|
|
560
637
|
accountId: row.id,
|
|
561
638
|
providerId: row.providerId,
|
|
639
|
+
details: { origin, statusBefore },
|
|
562
640
|
});
|
|
563
641
|
const owner = randomUUID();
|
|
564
642
|
const leased = await acquireLease(this.#db, row.id, owner);
|
|
@@ -583,6 +661,7 @@ export class OAuthRefreshService {
|
|
|
583
661
|
accountId: row.id,
|
|
584
662
|
providerId: row.providerId,
|
|
585
663
|
errorCode: "static_token",
|
|
664
|
+
details: { origin },
|
|
586
665
|
});
|
|
587
666
|
return null;
|
|
588
667
|
}
|
|
@@ -594,12 +673,20 @@ export class OAuthRefreshService {
|
|
|
594
673
|
buildRefreshContext(row, refresher.requiresAccessToken === true),
|
|
595
674
|
);
|
|
596
675
|
const persisted = await persistRefreshed(this.#db, row.id, owner, result);
|
|
676
|
+
// D26: `statusAfter` is re-read from the DB rather than assumed — a
|
|
677
|
+
// successful refresh never changes `status`, and the log proves it.
|
|
678
|
+
const statusAfter = await readAccountStatus(this.#db, row.id);
|
|
597
679
|
pushStructuredConsoleLog(persisted ? "info" : "warn", persisted ? "OAuth token refresh completed" : "OAuth token refresh lost lease", {
|
|
598
680
|
event: "token_refresh",
|
|
599
681
|
accountId: row.id,
|
|
600
682
|
providerId: row.providerId,
|
|
601
683
|
...(persisted ? {} : { errorCode: "refresh_lease_lost" }),
|
|
602
|
-
details: {
|
|
684
|
+
details: {
|
|
685
|
+
expiresAt: result.expiresAt.toISOString(),
|
|
686
|
+
origin,
|
|
687
|
+
statusBefore,
|
|
688
|
+
statusAfter,
|
|
689
|
+
},
|
|
603
690
|
});
|
|
604
691
|
return persisted ? result.access : null;
|
|
605
692
|
} catch (error) {
|
|
@@ -612,6 +699,7 @@ export class OAuthRefreshService {
|
|
|
612
699
|
accountId: row.id,
|
|
613
700
|
providerId: row.providerId,
|
|
614
701
|
errorCode: classification,
|
|
702
|
+
details: { origin, statusBefore },
|
|
615
703
|
});
|
|
616
704
|
if (classification === "definitive") {
|
|
617
705
|
// A definitive refresh failure means the refresh grant is dead — but
|
|
@@ -627,14 +715,29 @@ export class OAuthRefreshService {
|
|
|
627
715
|
if (accessUsability === "usable") {
|
|
628
716
|
await releaseLease(this.#db, row.id, owner);
|
|
629
717
|
await markStaticToken(this.#db, row.id);
|
|
718
|
+
const statusAfter = await readAccountStatus(this.#db, row.id);
|
|
630
719
|
pushStructuredConsoleLog("info", "OAuth refresh failed; access token still valid, marking static", {
|
|
631
720
|
event: "token_refresh",
|
|
632
721
|
accountId: row.id,
|
|
633
722
|
providerId: row.providerId,
|
|
634
723
|
errorCode: "static_token",
|
|
724
|
+
details: { origin, statusBefore, statusAfter },
|
|
635
725
|
});
|
|
636
726
|
} else {
|
|
637
|
-
await disableAccount(this.#db, row.id, owner, failureMessage(error)
|
|
727
|
+
await disableAccount(this.#db, row.id, owner, failureMessage(error), {
|
|
728
|
+
// D23: a definitive refresh failure must not turn an operator's
|
|
729
|
+
// disable into a machine-recoverable one. If the row is already
|
|
730
|
+
// `disabled` without a deadline, leave it that way.
|
|
731
|
+
preserveOperatorDisable: true,
|
|
732
|
+
});
|
|
733
|
+
const statusAfter = await readAccountStatus(this.#db, row.id);
|
|
734
|
+
pushStructuredConsoleLog("warn", "OAuth refresh failed definitively; account disabled for re-auth", {
|
|
735
|
+
event: "token_refresh",
|
|
736
|
+
accountId: row.id,
|
|
737
|
+
providerId: row.providerId,
|
|
738
|
+
errorCode: classification,
|
|
739
|
+
details: { origin, statusBefore, statusAfter },
|
|
740
|
+
});
|
|
638
741
|
}
|
|
639
742
|
} else {
|
|
640
743
|
await releaseLease(this.#db, row.id, owner);
|
|
@@ -643,3 +746,13 @@ export class OAuthRefreshService {
|
|
|
643
746
|
}
|
|
644
747
|
}
|
|
645
748
|
}
|
|
749
|
+
|
|
750
|
+
/** Reads one account's status (used only for logging/observability, D26). */
|
|
751
|
+
async function readAccountStatus(db: CartethyiaDatabase, accountId: string): Promise<string | undefined> {
|
|
752
|
+
const rows = await db
|
|
753
|
+
.select({ status: providerAccounts.status })
|
|
754
|
+
.from(providerAccounts)
|
|
755
|
+
.where(eq(providerAccounts.id, accountId))
|
|
756
|
+
.limit(1);
|
|
757
|
+
return rows[0]?.status;
|
|
758
|
+
}
|
|
@@ -84,12 +84,15 @@ function traeDiscovery(
|
|
|
84
84
|
if (rows.length === 0) return realm === "cn" ? TRAE_CN_MODELS : TRAE_INTL_MODELS;
|
|
85
85
|
const fallback = realm === "cn" ? TRAE_CN_MODELS : TRAE_INTL_MODELS;
|
|
86
86
|
const byId = new Map(fallback.map((model) => [model.modelId, model]));
|
|
87
|
+
// Return everything the upstream picker reports — the live listing is the
|
|
88
|
+
// truth for the account, including configs absent from the static roster.
|
|
89
|
+
// The static roster is only the fallback when discovery fails. A config
|
|
90
|
+
// discovered upstream gets the realm's first row as a template so it is
|
|
91
|
+
// routable with the shape this provider serves, carrying the window
|
|
92
|
+
// upstream reported.
|
|
87
93
|
return rows.map((row) => {
|
|
88
94
|
const existing = byId.get(row.id);
|
|
89
95
|
if (existing === undefined) {
|
|
90
|
-
// Discovered upstream but absent from the static roster: clone the
|
|
91
|
-
// realm's first row so a new model is routable with the shape this
|
|
92
|
-
// provider serves, carrying the window upstream reported.
|
|
93
96
|
const template = fallback[0];
|
|
94
97
|
if (template === undefined) return undefined;
|
|
95
98
|
return { ...template, modelId: row.id, contextLimit: row.ctx ?? null };
|
|
@@ -315,6 +318,20 @@ export const PROVIDER_CAPABILITIES = {
|
|
|
315
318
|
...(fetcher === undefined ? {} : { fetcher }),
|
|
316
319
|
}),
|
|
317
320
|
},
|
|
321
|
+
devin: {
|
|
322
|
+
// Devin's chat wire is a Connect-RPC protobuf stream; the path is pinned
|
|
323
|
+
// here so a probe verifies dispatch actually calls it.
|
|
324
|
+
endpointPathsByWireFamily: { chat: "/exa.api_server_pb.ApiServerService/GetChatMessage" },
|
|
325
|
+
loadAdapter: async () => (await import("./integrations/devin/devin")).devinAdapter,
|
|
326
|
+
loadModels: async () => (await import("./integrations/devin/devin-catalog")).DEVIN_MODELS,
|
|
327
|
+
loadAuthentication: oauthCapability(() => import("./integrations/devin/devin-auth"), "devinOAuthClient"),
|
|
328
|
+
loadQuotaCollector: quotaCapability(() => import("./integrations/devin/devin-quota"), "fetchDevinQuota"),
|
|
329
|
+
loadModelDiscovery: async () => async ({ credential, fetcher }) =>
|
|
330
|
+
(await import("./integrations/devin/devin-catalog")).discoverDevinModels({
|
|
331
|
+
credential,
|
|
332
|
+
...(fetcher === undefined ? {} : { fetcher }),
|
|
333
|
+
}),
|
|
334
|
+
},
|
|
318
335
|
cerebras: {
|
|
319
336
|
endpointPathsByWireFamily: { chat: "/chat/completions" },
|
|
320
337
|
loadAdapter: async () => createApiKeyAdapter((await import("./integrations/cerebras")).CEREBRAS_SPEC),
|
|
@@ -514,6 +531,51 @@ export const PROVIDER_CAPABILITIES = {
|
|
|
514
531
|
loadQuotaCollector: quotaCapability(() => import("./integrations/trae/trae-quota"), "fetchTraeCnQuota"),
|
|
515
532
|
loadModelDiscovery: traeDiscovery("cn", "traecn"),
|
|
516
533
|
},
|
|
534
|
+
freepi: {
|
|
535
|
+
loadAdapter: async () => (await import("./integrations/freepi/freepi")).createFreepiAdapter(),
|
|
536
|
+
loadModels: async () => (await import("./integrations/freepi/freepi-catalog")).FREEPI_MODELS,
|
|
537
|
+
// Free-pi login is a GitHub device flow; the issued JWT is the credential.
|
|
538
|
+
loadAuthentication: oauthCapability(() => import("./integrations/freepi/freepi-oauth"), "freepiOAuthClient"),
|
|
539
|
+
loadQuotaCollector: quotaCapability(() => import("./integrations/freepi/freepi-quota"), "fetchFreepiQuota"),
|
|
540
|
+
// Live `/client-version` (Bearer) is the catalog source; the static roster
|
|
541
|
+
// is the offline fallback.
|
|
542
|
+
loadModelDiscovery: async () => async (request: Parameters<ProviderModelDiscovery>[0]) => {
|
|
543
|
+
const { discoverFreepiModels } = await import("./integrations/freepi/freepi-discovery");
|
|
544
|
+
const { FREEPI_MODELS } = await import("./integrations/freepi/freepi-catalog");
|
|
545
|
+
const secret = request.credential.trim();
|
|
546
|
+
const fetcher = request.fetcher as unknown as typeof fetch | undefined;
|
|
547
|
+
if (secret.length === 0 || fetcher === undefined) return FREEPI_MODELS;
|
|
548
|
+
let token = secret;
|
|
549
|
+
if (secret.startsWith("{")) {
|
|
550
|
+
try {
|
|
551
|
+
const parsed = JSON.parse(secret) as Record<string, unknown>;
|
|
552
|
+
const found = ["accessToken", "access_token", "token"].find(
|
|
553
|
+
(key) => typeof parsed[key] === "string" && (parsed[key] as string).length > 0,
|
|
554
|
+
);
|
|
555
|
+
if (found !== undefined) token = parsed[found] as string;
|
|
556
|
+
} catch {
|
|
557
|
+
// Use the raw secret as the token.
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const rows = await discoverFreepiModels({ token, fetcher });
|
|
561
|
+
if (rows.length === 0) return FREEPI_MODELS;
|
|
562
|
+
return rows;
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
perch: {
|
|
566
|
+
loadAdapter: async () => (await import("./integrations/perch/perch")).createPerchAdapter(),
|
|
567
|
+
loadModels: async () => (await import("./integrations/perch/perch-catalog")).PERCH_MODELS,
|
|
568
|
+
// Perch login is Supabase PKCE (Google/GitHub); the session blob
|
|
569
|
+
// (access/refresh token) is the credential, and the CLI refreshes it.
|
|
570
|
+
loadAuthentication: oauthCapability(() => import("./integrations/perch/perch-oauth"), "perchOAuthClient", { withRefresher: true }),
|
|
571
|
+
loadQuotaCollector: quotaCapability(() => import("./integrations/perch/perch-quota"), "fetchPerchQuota"),
|
|
572
|
+
// The hosted API exposes no public model list — Roost tiers are the only
|
|
573
|
+
// surface the CLI accepts, so discovery returns the static roster.
|
|
574
|
+
loadModelDiscovery: async () => async () => {
|
|
575
|
+
const { PERCH_MODELS } = await import("./integrations/perch/perch-catalog");
|
|
576
|
+
return PERCH_MODELS;
|
|
577
|
+
},
|
|
578
|
+
},
|
|
517
579
|
inferhub: {
|
|
518
580
|
loadAdapter: async () => (await import("./integrations/inferhub")).createInferhubAdapter(),
|
|
519
581
|
// Live `/v1/models` is the primary source: the marketplace republishes its
|
|
@@ -527,6 +589,9 @@ export const PROVIDER_CAPABILITIES = {
|
|
|
527
589
|
hermes: {
|
|
528
590
|
loadAdapter: async () => createApiKeyAdapter((await import("./integrations/hermes")).HERMES_SPEC),
|
|
529
591
|
loadModels: async () => (await import("./integrations/hermes")).HERMES_MODELS,
|
|
592
|
+
// Hermes accepts both API keys and the Nous Portal device-code OAuth flow
|
|
593
|
+
// (subscription route). The OAuth client is refreshable.
|
|
594
|
+
loadAuthentication: oauthCapability(() => import("./integrations/hermes/hermes-oauth"), "hermesOAuthClient", { withRefresher: true }),
|
|
530
595
|
// `/v1/models` is open and states each row's real limits, so the free
|
|
531
596
|
// allowance tracks upstream instead of a hand-kept list. The static list
|
|
532
597
|
// above stays as the offline fallback for a fetch that fails.
|
|
@@ -58,6 +58,11 @@ export const PROBE_REASONING_EFFORTS = ["auto", "minimal", "low", "medium", "hig
|
|
|
58
58
|
/** One accepted probe reasoning effort. `auto` means "send no reasoning intent". */
|
|
59
59
|
export type ProbeReasoningEffort = (typeof PROBE_REASONING_EFFORTS)[number];
|
|
60
60
|
|
|
61
|
+
/** Modelprobe modes as a runtime tuple, so the route schema projects one list. */
|
|
62
|
+
export const PROBE_MODES = ["fast", "detail"] as const;
|
|
63
|
+
/** `detail` is the default when `mode` is absent (D39). */
|
|
64
|
+
export type ProbeMode = (typeof PROBE_MODES)[number];
|
|
65
|
+
|
|
61
66
|
/** Request accepted for a one-shot model connectivity test. `route`
|
|
62
67
|
* disambiguates when a model id is registered under multiple endpoint
|
|
63
68
|
* paths; omit it to probe against the model's already-registered wire
|
|
@@ -85,6 +90,13 @@ export interface ProbeModelRequest {
|
|
|
85
90
|
* result the richest available source of truth.
|
|
86
91
|
*/
|
|
87
92
|
surface?: "default" | "cosy" | "grpc" | "ide";
|
|
93
|
+
/**
|
|
94
|
+
* `fast` is a cheap liveness check: 32 output tokens, a 10s timeout, no
|
|
95
|
+
* retry, no payload-body capture, compact telemetry, and an 80-char sample.
|
|
96
|
+
* `detail` (the default when absent) is the full diagnostic probe.
|
|
97
|
+
* Health recording is identical in both modes.
|
|
98
|
+
*/
|
|
99
|
+
mode?: ProbeMode;
|
|
88
100
|
}
|
|
89
101
|
export interface ProbeModelResult {
|
|
90
102
|
accountId?: string;
|