cartethyia 2.7.8 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -3
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js +1 -0
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-CXsaOAO2.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js +1 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-CgoPc5B9.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-D9bFQern.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js +1 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BwNC_-Cx.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-febLU4r1.js} +1 -1
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-M6fvR5Wz.js} +1 -1
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js +56 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js +1 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
- package/dist/dashboard/assets/{assert-CidQVoNo.js → assert-CqTLw5e7.js} +1 -1
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-C83EqLB0.js} +1 -1
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
- package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-D1xn7pIe.js} +1 -1
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/favicon.webp +0 -0
- package/dist/dashboard/assets/brand/login_register.webp +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
- package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/og_image.webp +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
- package/dist/dashboard/assets/{card-_HwAEqFF.js → card-DU3Lq5Lm.js} +1 -1
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js +1 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CEL5Sg_J.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-BjFR5UHy.js} +1 -1
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-HvBW0AjI.js} +1 -1
- package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
- package/dist/dashboard/assets/{entry-COaC-irN.js → entry-CECVELyy.js} +1 -1
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js +1 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-DLW4i8zK.js} +1 -1
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/{index-CgKmFngv.js → index-1-fCqe64.js} +2 -2
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2BirKry.js → inline-DGvU2oLK.js} +1 -1
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/{input-D-qV9dKY.js → input-iNnO1X2K.js} +1 -1
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-0q8UGypb.js} +1 -1
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-C0w2bQjJ.js} +1 -1
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/{live-DGizF06-.js → live-M5aJ_xCa.js} +1 -1
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
- package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
- package/dist/dashboard/assets/{network-C9UE-bOr.js → network-BKeQGT3K.js} +1 -1
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-CivAk3da.js} +1 -1
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-CpzvI5ep.js} +1 -1
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
- package/dist/dashboard/assets/providers/devin.svg +9 -0
- package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
- package/dist/dashboard/assets/providers/windsurf.svg +10 -0
- package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
- package/dist/dashboard/assets/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
- package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js +1 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/{select-BCOcv8WK.js → select-CXbck0jG.js} +2 -2
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js +1 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DggRiCxE.js} +1 -1
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/{state-DXbGTx7N.js → state-ClFzfbYp.js} +1 -1
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/story/25817331.webp +0 -0
- package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
- package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
- package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
- package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
- package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
- package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
- package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
- package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-CTIFxKqS.js} +1 -1
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BClG3yWj.js} +1 -1
- package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.webp +0 -0
- package/dist/dashboard/console-backgrounds_initial.webp +0 -0
- package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +59 -56
- package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
- package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
- package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/package.json +1 -1
- package/src/boot/app.ts +10 -13
- package/src/boot/dependencies.ts +12 -4
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/cooldowns.ts +1 -1
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +20 -6
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +50 -31
- package/src/console/auth/account-reset.ts +7 -9
- package/src/console/backup/contracts.ts +4 -0
- package/src/console/backup/export-endpoint.ts +7 -5
- package/src/console/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -2
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +93 -0
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -0
- package/src/console/providers/catalog/discover-constants.ts +6 -0
- package/src/console/providers/catalog/model-operations.ts +122 -10
- package/src/console/providers/catalog/provider-operations.ts +116 -8
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +65 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +249 -4
- package/src/console/quota/account-quota-global.ts +97 -19
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +6 -18
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/operations.ts +31 -46
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/shared/errors.ts +33 -6
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +3 -3
- package/src/network/pool/byte-accounting.ts +3 -2
- package/src/network/pool/selector.ts +182 -39
- package/src/network/pool-cooldown-store.ts +89 -14
- package/src/network/pool-health.ts +49 -61
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/ssrf.ts +6 -6
- package/src/observability/event-log.ts +7 -361
- package/src/observability/event-query.ts +18 -4
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +15 -25
- package/src/observability/telemetry-reconciliation.ts +9 -37
- package/src/observability/telemetry-row.ts +125 -0
- package/src/persistence/db-mode.ts +4 -1
- package/src/persistence/dialect.ts +30 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +28 -2
- package/src/persistence/ttl-cache.ts +20 -2
- package/src/protocol/codecs.ts +1 -1
- package/src/protocol/dsml.ts +35 -87
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +14 -14
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +4 -5
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/stream-error-frames.ts +13 -3
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/protocol/translation/quirks.ts +1 -1
- package/src/protocol/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +15 -4
- package/src/protocol/upstream-parsing.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +78 -17
- package/src/providers/authentication/oauth-client.ts +1 -1
- package/src/providers/authentication/oauth-device-flow.ts +98 -8
- package/src/providers/authentication/oauth-refresh-service.ts +153 -59
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +117 -27
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +74 -33
- package/src/providers/discovery/probing-service.ts +293 -165
- package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
- package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +121 -2
- package/src/providers/integrations/antigravity/antigravity.ts +13 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +191 -35
- package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
- package/src/providers/integrations/buddy/buddy-identity.ts +89 -6
- package/src/providers/integrations/buddy/buddy-media.ts +4 -5
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
- package/src/providers/integrations/buddy/capabilities.ts +6 -43
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +23 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +38 -27
- package/src/providers/integrations/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline.ts +2 -2
- package/src/providers/integrations/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
- package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +745 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +35 -20
- package/src/providers/integrations/cursor/cursor-shared.ts +26 -5
- package/src/providers/integrations/cursor/cursor.ts +139 -70
- package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
- package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
- package/src/providers/integrations/devin/devin-auth.ts +353 -0
- package/src/providers/integrations/devin/devin-catalog.ts +207 -0
- package/src/providers/integrations/devin/devin-http.ts +91 -0
- package/src/providers/integrations/devin/devin-quota.ts +214 -0
- package/src/providers/integrations/devin/devin-shared.ts +111 -0
- package/src/providers/integrations/devin/devin.ts +539 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +558 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory-oauth.ts +17 -44
- package/src/providers/integrations/factory/factory.ts +2 -2
- package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
- package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
- package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
- package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
- package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
- package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
- package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
- package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
- package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
- package/src/providers/integrations/freebuff/freebuff.ts +682 -0
- package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
- package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
- package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
- package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
- package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
- package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
- package/src/providers/integrations/freepi/freepi.ts +292 -0
- package/src/providers/integrations/gemini.ts +2 -2
- package/src/providers/integrations/grok/grok-oauth.ts +48 -45
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
- package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
- package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +13 -21
- package/src/providers/integrations/hermes/index.ts +2 -0
- package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
- package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
- package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- package/src/providers/integrations/ollamacloud/index.ts +2 -0
- package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
- package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +208 -0
- package/src/providers/integrations/perch/perch-quota.ts +123 -0
- package/src/providers/integrations/perch/perch-realm.ts +82 -0
- package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
- package/src/providers/integrations/perch/perch.ts +305 -0
- package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
- package/src/providers/integrations/qoder/chat-proto.ts +328 -328
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +90 -40
- package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +26 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
- package/src/providers/integrations/qoder/qoder-checkin.ts +6 -6
- package/src/providers/integrations/qoder/qoder-grpc.ts +210 -7
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -108
- package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
- package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
- package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
- package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
- package/src/providers/integrations/qoder/qoder.ts +3 -7
- package/src/providers/integrations/trae/trae-catalog.ts +9 -14
- package/src/providers/integrations/trae/trae-headers.ts +36 -11
- package/src/providers/integrations/trae/trae-oauth.ts +14 -17
- package/src/providers/integrations/trae/trae-protocol.ts +12 -11
- package/src/providers/integrations/trae/trae-quota.ts +96 -36
- package/src/providers/integrations/trae/trae-realm.ts +74 -15
- package/src/providers/integrations/trae/trae.ts +99 -26
- package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
- package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
- package/src/providers/integrations/zro/zro.ts +204 -0
- package/src/providers/operations/account-error-classification.ts +235 -278
- package/src/providers/operations/account-health-service.ts +273 -26
- package/src/providers/operations/account-refresh-status.ts +269 -0
- package/src/providers/operations/account-tier.ts +66 -0
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +42 -150
- package/src/providers/operations/cooldown-rules.ts +56 -0
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +102 -32
- package/src/providers/operations/provider-rules/github.ts +48 -0
- package/src/providers/operations/provider-rules/grok.ts +33 -0
- package/src/providers/operations/provider-rules/inception.ts +49 -0
- package/src/providers/operations/provider-rules/index.ts +32 -0
- package/src/providers/operations/provider-rules/perch.ts +37 -0
- package/src/providers/operations/provider-rules/qoder.ts +70 -0
- package/src/providers/operations/provider-rules/trae.ts +81 -0
- package/src/providers/provider-contracts.ts +33 -2
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +99 -14
- package/src/providers/provider-model-definition.ts +36 -0
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- package/src/providers/quota/quota-limit-windows.ts +16 -5
- package/src/providers/quota/quota-support.ts +1 -1
- package/src/providers/quota/quota-window-parser.ts +90 -18
- package/src/providers/quota/refresh.ts +96 -18
- package/src/security/abuse.ts +71 -0
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/transport/dispatch/attempt-finalize.ts +14 -13
- package/src/transport/dispatch/attempt-loop.ts +71 -17
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +9 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/streaming/finalize.ts +9 -6
- package/src/transport/dispatch/streaming/stream.ts +9 -36
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +67 -1
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +129 -37
- package/src/transport/guards/body-policy.ts +7 -1
- package/src/transport/guards/error-lifecycle.ts +13 -1
- package/src/transport/guards/request-context.ts +22 -14
- package/src/transport/request/preparer.ts +81 -71
- package/src/transport/routing/bounded-lru.ts +14 -0
- package/src/transport/routing/drain-reserve.ts +129 -0
- package/src/transport/routing/plan-gate.ts +7 -1
- package/src/transport/routing/round-robin.ts +60 -0
- package/src/transport/routing/route-catalog.ts +123 -111
- package/src/transport/routing/route-model.ts +51 -9
- package/src/transport/routing/router.ts +418 -33
- package/src/transport/surface/adapters.ts +11 -2
- package/src/transport/surface/chat/adapter.ts +2 -2
- package/src/transport/surface/chat/encode.ts +47 -4
- package/src/transport/surface/chat/parse.ts +2 -12
- package/src/transport/surface/completion.ts +44 -33
- package/src/transport/surface/content-parts.ts +21 -0
- package/src/transport/surface/messages/adapter.ts +9 -2
- package/src/transport/surface/messages/encode.ts +11 -3
- package/src/transport/surface/messages/parse.ts +2 -1
- package/src/transport/surface/messages/stream.ts +33 -17
- package/src/transport/surface/responses/encode.ts +34 -6
- package/src/transport/surface/responses/parse.ts +3 -11
- package/src/workers/daily-checkin.ts +1 -1
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/scheduler.ts +22 -4
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js +0 -1
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
- package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
- package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.png +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/favicon.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
- package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
- package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
- package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
- package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
- package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
- package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
- package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
- package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
- package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
- package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
- package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
- package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
- package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
- package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
- package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
- package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
- package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
- package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
- package/src/providers/operations/model-discovery-cache.ts +0 -34
- package/src/providers/operations/probe-fetch.ts +0 -15
- package/src/providers/operations/provider-version-cache.ts +0 -30
- package/src/providers/provider-capabilities.ts +0 -33
- package/src/providers/provider-credential-envelope.ts +0 -43
- /package/dist/dashboard/assets/{app-CTxoqyYB.js → app-DIXLjojg.js} +0 -0
- /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-DIXLjojg.js.gz} +0 -0
|
@@ -6,7 +6,7 @@ import { providerAccounts, providerOauthStates } from "../../persistence/schema"
|
|
|
6
6
|
import { decryptCredentialToString } from "../../security/crypto";
|
|
7
7
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
8
8
|
import type { OAuthRefreshService, OAuthTokenRefresher } from "../authentication/oauth-refresh-service";
|
|
9
|
-
import { CredentialResolver, type CredentialAlternative } from "../provider-
|
|
9
|
+
import { CredentialResolver, type CredentialAlternative } from "../provider-credentials";
|
|
10
10
|
import { parseProviderId, type CredentialKind } from "../provider-contracts";
|
|
11
11
|
import type { ResolvedCredential } from "../provider-contracts";
|
|
12
12
|
import { refreshLeadMs } from "./oauth-refresh-lead";
|
|
@@ -145,19 +145,59 @@ export interface AccountWithFreshness {
|
|
|
145
145
|
readonly dueAt: number | undefined;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/** Columns every account+OAuth-state reader selects; narrower readers pick from it. */
|
|
149
|
+
export interface AccountWithOauthStateRow {
|
|
150
|
+
readonly id: string;
|
|
151
|
+
readonly providerId: string;
|
|
152
|
+
readonly credentialKind: CredentialKind;
|
|
153
|
+
readonly credentialCiphertext: Buffer | null;
|
|
154
|
+
readonly refreshCiphertext: Buffer | null;
|
|
155
|
+
readonly authState: unknown;
|
|
156
|
+
readonly clientSecretCiphertext: Buffer | null;
|
|
157
|
+
readonly staticToken: boolean;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Which optional columns a reader wants on top of the shared base. */
|
|
161
|
+
export interface LoadAccountWithOauthStateOptions {
|
|
162
|
+
/** Select `status` from providerAccounts. */
|
|
163
|
+
readonly includeStatus?: boolean;
|
|
164
|
+
/** Select `expiresAt` from providerOauthStates. */
|
|
165
|
+
readonly includeExpiry?: boolean;
|
|
166
|
+
/** Also compute the skew-adjusted OAuth due time (see `loadAccountWithFreshness`). */
|
|
167
|
+
readonly includeDueAt?: boolean;
|
|
168
|
+
/** Refresh lead override for the `dueAt` skew; defaults to `refreshLeadMs(providerId)`. */
|
|
169
|
+
readonly skewMs?: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** One loaded account row plus whatever optional columns were requested. */
|
|
173
|
+
export interface LoadedAccountWithOauthState {
|
|
174
|
+
readonly id: string;
|
|
175
|
+
readonly providerId: string;
|
|
176
|
+
readonly credentialKind: CredentialKind;
|
|
177
|
+
readonly credentialCiphertext: Buffer | null;
|
|
178
|
+
readonly refreshCiphertext: Buffer | null;
|
|
179
|
+
readonly authState: unknown;
|
|
180
|
+
readonly clientSecretCiphertext: Buffer | null;
|
|
181
|
+
readonly staticToken: boolean;
|
|
182
|
+
readonly status?: string;
|
|
183
|
+
readonly expiresAt?: Date | null;
|
|
184
|
+
readonly dueAt?: number;
|
|
185
|
+
}
|
|
186
|
+
|
|
148
187
|
/**
|
|
149
|
-
*
|
|
188
|
+
* Shared account+OAuth-state loader behind every status/health reader.
|
|
150
189
|
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
190
|
+
* Builds the same `select(...).from(providerAccounts)` with the conditional
|
|
191
|
+
* `leftJoin` on `providerOauthStates` (the memory/Postgres dialect guard), so
|
|
192
|
+
* a schema or dialect change is fixed once instead of in four copies. The
|
|
193
|
+
* `dueAt` skew logic lives here too; `loadAccountWithFreshness` is a thin
|
|
194
|
+
* wrapper that returns it beside the row.
|
|
155
195
|
*/
|
|
156
|
-
export async function
|
|
196
|
+
export async function loadAccountWithOauthState(
|
|
157
197
|
db: CartethyiaDatabase,
|
|
158
198
|
accountId: string,
|
|
159
|
-
|
|
160
|
-
): Promise<
|
|
199
|
+
options: LoadAccountWithOauthStateOptions = {},
|
|
200
|
+
): Promise<LoadedAccountWithOauthState | undefined> {
|
|
161
201
|
const selectBuilder = db
|
|
162
202
|
.select({
|
|
163
203
|
id: providerAccounts.id,
|
|
@@ -167,8 +207,11 @@ export async function loadAccountWithFreshness(
|
|
|
167
207
|
refreshCiphertext: providerOauthStates.refreshCiphertext,
|
|
168
208
|
authState: providerAccounts.authState,
|
|
169
209
|
clientSecretCiphertext: providerOauthStates.clientSecretCiphertext,
|
|
170
|
-
expiresAt: providerOauthStates.expiresAt,
|
|
171
210
|
staticToken: providerAccounts.staticToken,
|
|
211
|
+
...(options.includeStatus === true ? { status: providerAccounts.status } : {}),
|
|
212
|
+
...(options.includeExpiry === true || options.includeDueAt === true
|
|
213
|
+
? { expiresAt: providerOauthStates.expiresAt }
|
|
214
|
+
: {}),
|
|
172
215
|
})
|
|
173
216
|
.from(providerAccounts);
|
|
174
217
|
const query = "leftJoin" in selectBuilder
|
|
@@ -180,35 +223,62 @@ export async function loadAccountWithFreshness(
|
|
|
180
223
|
const rows = await query.where(eq(providerAccounts.id, accountId)).limit(1);
|
|
181
224
|
const row = rows[0];
|
|
182
225
|
if (!row) return undefined;
|
|
183
|
-
|
|
226
|
+
if (options.includeDueAt !== true) return row;
|
|
227
|
+
const lead = options.skewMs ?? refreshLeadMs(row.providerId);
|
|
184
228
|
// A refresh-capable OAuth account with no stored expiry is unknown-liveness:
|
|
185
|
-
// we have no evidence the access token still works.
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
// had already expired on another installation (a 9router/Omniroute import
|
|
189
|
-
// carrying `accessToken` + `refreshToken` + a past `expiresAt` that was
|
|
190
|
-
// dropped by validation, a hand-pasted OAuth bundle with no expiry field,
|
|
191
|
-
// or an upstream that stopped minting expiries) that produced the exact
|
|
192
|
-
// "re-authenticate" refusal the refresh grant exists to prevent. Marking the
|
|
193
|
-
// token due immediately refreshes it on first use; the sweep handles the
|
|
194
|
-
// normal case where `expires_at` is populated.
|
|
229
|
+
// we have no evidence the access token still works. Marking the token due
|
|
230
|
+
// immediately refreshes it on first use; the sweep handles the normal case
|
|
231
|
+
// where `expires_at` is populated.
|
|
195
232
|
const hasRefreshGrant = row.refreshCiphertext != null;
|
|
196
233
|
const kindOauth = row.credentialKind === "oauth";
|
|
197
234
|
const unknownExpiryButRefreshable =
|
|
198
235
|
kindOauth && !row.staticToken && hasRefreshGrant && row.expiresAt == null;
|
|
236
|
+
// A static token is never "due": it is used as issued and there is no
|
|
237
|
+
// refresh to run, so the dispatch path must not treat it as stale.
|
|
238
|
+
const dueAt =
|
|
239
|
+
row.staticToken
|
|
240
|
+
? undefined
|
|
241
|
+
: row.expiresAt != null
|
|
242
|
+
? row.expiresAt.getTime() - lead
|
|
243
|
+
: unknownExpiryButRefreshable
|
|
244
|
+
? 0
|
|
245
|
+
: undefined;
|
|
199
246
|
return {
|
|
200
|
-
row,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
247
|
+
...row,
|
|
248
|
+
...(dueAt === undefined ? {} : { dueAt }),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Loads an account and its optional OAuth state in one query.
|
|
254
|
+
*
|
|
255
|
+
* `dueAt` is the OAuth expiry minus the provider's refresh lead
|
|
256
|
+
* (`refreshLeadMs(providerId)`), so a token becomes due at the point that
|
|
257
|
+
* provider re-mints its own (Claude ~4h, Codex ~5 days, Antigravity ~5 min).
|
|
258
|
+
* Non-OAuth accounts and accounts without OAuth state have no due time.
|
|
259
|
+
*/
|
|
260
|
+
export async function loadAccountWithFreshness(
|
|
261
|
+
db: CartethyiaDatabase,
|
|
262
|
+
accountId: string,
|
|
263
|
+
skewMs?: number,
|
|
264
|
+
): Promise<AccountWithFreshness | undefined> {
|
|
265
|
+
const loaded = await loadAccountWithOauthState(db, accountId, {
|
|
266
|
+
includeDueAt: true,
|
|
267
|
+
...(skewMs === undefined ? {} : { skewMs }),
|
|
268
|
+
});
|
|
269
|
+
if (!loaded) return undefined;
|
|
270
|
+
const row: AccountWithFreshnessRow = {
|
|
271
|
+
id: loaded.id,
|
|
272
|
+
providerId: loaded.providerId,
|
|
273
|
+
credentialKind: loaded.credentialKind,
|
|
274
|
+
credentialCiphertext: loaded.credentialCiphertext,
|
|
275
|
+
refreshCiphertext: loaded.refreshCiphertext,
|
|
276
|
+
authState: loaded.authState,
|
|
277
|
+
clientSecretCiphertext: loaded.clientSecretCiphertext,
|
|
278
|
+
expiresAt: loaded.expiresAt ?? null,
|
|
279
|
+
staticToken: loaded.staticToken,
|
|
211
280
|
};
|
|
281
|
+
return { row, dueAt: loaded.dueAt };
|
|
212
282
|
}
|
|
213
283
|
|
|
214
284
|
/** OAuth refresh collaborators, injected by the composition root. */
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AccountErrorRule,
|
|
3
|
+
} from "../account-error-classification";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Checks whether an upstream body indicates Copilot is not licensed.
|
|
7
|
+
* Copilot answers 401 for a bad credential and 403 for a
|
|
8
|
+
* license/entitlement refusal, so forbidden/unauthorized on this surface
|
|
9
|
+
* is the same verdict.
|
|
10
|
+
*/
|
|
11
|
+
export function copilotNotLicensed(lower: string): boolean {
|
|
12
|
+
return (
|
|
13
|
+
lower.includes("not licensed") ||
|
|
14
|
+
lower.includes("forbidden") ||
|
|
15
|
+
lower.includes("unauthorized") ||
|
|
16
|
+
lower.includes("license")
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const githubRules: readonly AccountErrorRule[] = [
|
|
21
|
+
{
|
|
22
|
+
// GitHub Copilot answers a license/entitlement refusal with 403 and
|
|
23
|
+
// "not licensed to use Copilot" / "forbidden" / "unauthorized". That is a
|
|
24
|
+
// verdict on the account: the subscription cannot serve this credential,
|
|
25
|
+
// and no amount of waiting or refreshing changes it. Cooldown cycling
|
|
26
|
+
// (auth_invalidated + 5m backoff) kept a dead account in rotation where a
|
|
27
|
+
// client's retry budget burned sibling accounts — the same failure the
|
|
28
|
+
// buddy ban exists to prevent. Disable immediately. Must run before
|
|
29
|
+
// `credential-rejected`, which would claim the bare 403 as a refreshable
|
|
30
|
+
// auth rejection.
|
|
31
|
+
name: "github-copilot-not-licensed",
|
|
32
|
+
match: (s) =>
|
|
33
|
+
s.origin === "upstream" &&
|
|
34
|
+
s.providerId === "github" &&
|
|
35
|
+
copilotNotLicensed(s.lower),
|
|
36
|
+
decide: (s) =>
|
|
37
|
+
s.result(
|
|
38
|
+
"policy_blocked_banned",
|
|
39
|
+
"disabled",
|
|
40
|
+
0,
|
|
41
|
+
null,
|
|
42
|
+
s.reason("GitHub Copilot account not licensed"),
|
|
43
|
+
// A license refusal is evidence about the account even at provider
|
|
44
|
+
// scope, for the same reason a named revocation or a buddy ban is.
|
|
45
|
+
true,
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AccountErrorSignals } from "../account-error-classification";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* xAI Grok Build's free tier resets on a rolling 24-hour window, so its
|
|
5
|
+
* exhaustion is a full-day quota cooldown — never the generic 1h fallback,
|
|
6
|
+
* which let the account re-enter rotation inside the window.
|
|
7
|
+
*/
|
|
8
|
+
export const GROK_QUOTA_COOLDOWN_MS = 24 * 60 * 60 * 1000;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detects whether a quota failure belongs to Grok's free tier or spending limit.
|
|
12
|
+
*/
|
|
13
|
+
export function isGrokFreeTierExhaustion(s: AccountErrorSignals): boolean {
|
|
14
|
+
if (s.providerId !== undefined && s.providerId !== "grok") {
|
|
15
|
+
// If providerId is known and not grok, only match if the providerCode is explicitly grok subscription
|
|
16
|
+
if (!s.providerCode.startsWith("subscription:")) return false;
|
|
17
|
+
}
|
|
18
|
+
return (
|
|
19
|
+
s.providerCode === "subscription:free-usage-exhausted" ||
|
|
20
|
+
s.providerCode.startsWith("subscription:") ||
|
|
21
|
+
s.providerCode.includes("spending") ||
|
|
22
|
+
s.providerCode.includes("credits") ||
|
|
23
|
+
s.lower.includes("free-usage") ||
|
|
24
|
+
s.lower.includes("free usage") ||
|
|
25
|
+
s.lower.includes("included free usage") ||
|
|
26
|
+
s.lower.includes("rolling 24-hour") ||
|
|
27
|
+
s.lower.includes("rolling 24 hour") ||
|
|
28
|
+
s.lower.includes("spending limit") ||
|
|
29
|
+
s.lower.includes("out of credits") ||
|
|
30
|
+
s.lower.includes("run out of credits") ||
|
|
31
|
+
s.lower.includes("need a grok subscription")
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AccountErrorRule,
|
|
3
|
+
} from "../account-error-classification";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Inception's per-minute limit window (1 minute).
|
|
7
|
+
*
|
|
8
|
+
* Inception's Free tier throttles per minute (1 000 requests / 1M input
|
|
9
|
+
* tokens / 100K output tokens) and the window resets every minute. The generic
|
|
10
|
+
* 15-minute rate-limit fallback would park a healthy key long after the window
|
|
11
|
+
* refilled; 1 minute is the provider's own cadence, hardcoded because it is a
|
|
12
|
+
* fact about the upstream, not a deployment knob.
|
|
13
|
+
*/
|
|
14
|
+
export const INCEPTION_RATE_LIMIT_COOLDOWN_MS = 60_000;
|
|
15
|
+
|
|
16
|
+
export const inceptionRules: readonly AccountErrorRule[] = [
|
|
17
|
+
{
|
|
18
|
+
// Inception's Free tier throttles per minute (1 000 requests / 1M input
|
|
19
|
+
// tokens / 100K output tokens) and the window resets every minute. A
|
|
20
|
+
// generic 429 would cool the account for the 15-minute default, leaving a
|
|
21
|
+
// healthy key parked long after the window refilled — the source of
|
|
22
|
+
// "limitasiya lumayan keras". This arm must run before `rate-limit`: it
|
|
23
|
+
// matches the same signals and shortens the window to 1 minute so routing
|
|
24
|
+
// moves to the next account and the flagged one returns almost at once.
|
|
25
|
+
name: "inception-per-minute-limit",
|
|
26
|
+
match: (s) =>
|
|
27
|
+
s.origin === "upstream" &&
|
|
28
|
+
s.providerId === "inception" &&
|
|
29
|
+
(s.statusCode === 429 ||
|
|
30
|
+
s.providerCode === "rate_limit_reached" ||
|
|
31
|
+
s.providerCode === "rate_limit_exceeded" ||
|
|
32
|
+
s.lower.includes("rate_limit") ||
|
|
33
|
+
s.lower.includes("rate limit") ||
|
|
34
|
+
s.lower.includes("too many requests") ||
|
|
35
|
+
s.lower.includes("tokens per minute") ||
|
|
36
|
+
s.lower.includes("input token")),
|
|
37
|
+
decide: (s) => {
|
|
38
|
+
const cooldownMs = s.statedCooldownMs ?? INCEPTION_RATE_LIMIT_COOLDOWN_MS;
|
|
39
|
+
return s.result(
|
|
40
|
+
"rate_limit_transient",
|
|
41
|
+
"cooldown",
|
|
42
|
+
cooldownMs,
|
|
43
|
+
new Date(Date.now() + cooldownMs),
|
|
44
|
+
s.reason("Inception per-minute input-token limit"),
|
|
45
|
+
s.origin === "upstream",
|
|
46
|
+
);
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export {
|
|
2
|
+
TRAE_REGION_COOLDOWN_MS,
|
|
3
|
+
TRAE_QUOTA_COOLDOWN_MS,
|
|
4
|
+
traeRules,
|
|
5
|
+
} from "./trae";
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
QODER_QUOTA_COOLDOWN_MS,
|
|
9
|
+
qoderPlanGatedRule,
|
|
10
|
+
qoderModelQuotaRule,
|
|
11
|
+
qoderRules,
|
|
12
|
+
} from "./qoder";
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
githubRules,
|
|
16
|
+
copilotNotLicensed,
|
|
17
|
+
} from "./github";
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
INCEPTION_RATE_LIMIT_COOLDOWN_MS,
|
|
21
|
+
inceptionRules,
|
|
22
|
+
} from "./inception";
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
PERCH_TURN_RATE_LIMIT_COOLDOWN_MS,
|
|
26
|
+
perchRules,
|
|
27
|
+
} from "./perch";
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
GROK_QUOTA_COOLDOWN_MS,
|
|
31
|
+
isGrokFreeTierExhaustion,
|
|
32
|
+
} from "./grok";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AccountErrorRule,
|
|
3
|
+
} from "../account-error-classification";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Perch's `turn_rate_limited` window. The upstream does not state a reset and
|
|
7
|
+
* answers the same on every ticket mint while the window is exhausted, so a
|
|
8
|
+
* day matches the practical cadence and stops the redial loop.
|
|
9
|
+
*/
|
|
10
|
+
export const PERCH_TURN_RATE_LIMIT_COOLDOWN_MS = 24 * 60 * 60 * 1000;
|
|
11
|
+
|
|
12
|
+
export const perchRules: readonly AccountErrorRule[] = [
|
|
13
|
+
{
|
|
14
|
+
// Perch's `turn_rate_limited` is a per-turn API window, not a per-request
|
|
15
|
+
// 429: the account keeps answering but every call mints another ticket
|
|
16
|
+
// into the same exhausted window. The stated remedy ("contact us for
|
|
17
|
+
// direct API access") implies the window is long — park the account for a
|
|
18
|
+
// day so routing picks a sibling instead of hammering the same key.
|
|
19
|
+
name: "perch-turn-rate-limit",
|
|
20
|
+
match: (s) =>
|
|
21
|
+
s.origin === "upstream" &&
|
|
22
|
+
s.providerId === "perch" &&
|
|
23
|
+
(s.providerCode === "turn_rate_limited" ||
|
|
24
|
+
(s.statusCode === 429 && s.lower.includes("turn rate"))),
|
|
25
|
+
decide: (s) => {
|
|
26
|
+
const cooldownMs = s.statedCooldownMs ?? PERCH_TURN_RATE_LIMIT_COOLDOWN_MS;
|
|
27
|
+
return s.result(
|
|
28
|
+
"rate_limit_transient",
|
|
29
|
+
"cooldown",
|
|
30
|
+
cooldownMs,
|
|
31
|
+
new Date(Date.now() + cooldownMs),
|
|
32
|
+
s.reason("Perch turn rate limit"),
|
|
33
|
+
s.origin === "upstream",
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AccountErrorRule,
|
|
3
|
+
AccountErrorSignals,
|
|
4
|
+
} from "../account-error-classification";
|
|
5
|
+
import { unsupportedModelCooldownMs } from "../account-error-classification";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Qoder's quota verdicts (`110` billing-daily-count, pricing-url refusals)
|
|
9
|
+
* are per-model: one model's spend cap does not drain another. A day matches
|
|
10
|
+
* the daily billing window the provider names.
|
|
11
|
+
*/
|
|
12
|
+
export const QODER_QUOTA_COOLDOWN_MS = 24 * 60 * 60 * 1000;
|
|
13
|
+
|
|
14
|
+
function isQoder(signals: AccountErrorSignals): boolean {
|
|
15
|
+
return signals.providerId === "qoder" || signals.providerId === "qodercn";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Qoder's code-112 pricing refusal (`{"code":"112","message":"{\"pricingUrl\":...}"}`)
|
|
20
|
+
* is a "this model is not included in this account's plan, buy more" verdict
|
|
21
|
+
* for one pair — the account keeps serving its other models. The probe path
|
|
22
|
+
* does not always carry the code in its evidence (only the message does), so
|
|
23
|
+
* the match keys on the provider id plus the pricing-page wording as well.
|
|
24
|
+
* It must run before `quota-exhausted`, which would park the whole account.
|
|
25
|
+
*/
|
|
26
|
+
export const qoderPlanGatedRule: AccountErrorRule = {
|
|
27
|
+
name: "qoder-plan-gated",
|
|
28
|
+
match: (s) =>
|
|
29
|
+
isQoder(s) &&
|
|
30
|
+
(s.providerCode === "112" || s.lower.includes("pricingurl")),
|
|
31
|
+
decide: (s) => {
|
|
32
|
+
const cooldownMs = unsupportedModelCooldownMs(s.modelBlockHits);
|
|
33
|
+
return s.result(
|
|
34
|
+
"model_unreachable",
|
|
35
|
+
"cooldown",
|
|
36
|
+
cooldownMs,
|
|
37
|
+
new Date(Date.now() + cooldownMs),
|
|
38
|
+
s.reason("Model not included in this account's plan"),
|
|
39
|
+
s.origin === "upstream" && s.modelId !== undefined,
|
|
40
|
+
true,
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Qoder's quota verdicts — `110` "Billing daily count exceeded" — are per-model:
|
|
47
|
+
* one model's spend cap does not drain another. Scoping them to the model keeps
|
|
48
|
+
* the account serving its other models. Must run before `quota-exhausted`, which
|
|
49
|
+
* parks the whole account.
|
|
50
|
+
*/
|
|
51
|
+
export const qoderModelQuotaRule: AccountErrorRule = {
|
|
52
|
+
name: "qoder-model-quota",
|
|
53
|
+
match: (s) => isQoder(s) && s.quotaSignal,
|
|
54
|
+
decide: (s) => {
|
|
55
|
+
const cooldownMs = s.statedCooldownMs ?? QODER_QUOTA_COOLDOWN_MS;
|
|
56
|
+
return s.result(
|
|
57
|
+
"model_unreachable",
|
|
58
|
+
"cooldown",
|
|
59
|
+
cooldownMs,
|
|
60
|
+
new Date(Date.now() + cooldownMs),
|
|
61
|
+
s.reason("Qoder quota exhausted for this model"),
|
|
62
|
+
s.origin === "upstream" && s.modelId !== undefined,
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const qoderRules: readonly AccountErrorRule[] = [
|
|
68
|
+
qoderPlanGatedRule,
|
|
69
|
+
qoderModelQuotaRule,
|
|
70
|
+
];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AccountErrorRule,
|
|
3
|
+
AccountErrorSignals,
|
|
4
|
+
} from "../account-error-classification";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Trae answers `4013 "AI service is not available in the current region now."`
|
|
8
|
+
* when the upstream does not serve the account's region. A day because the
|
|
9
|
+
* verdict is durable: it reproduces on every request until the region opens,
|
|
10
|
+
* and the account must not be re-dialled every thirty seconds for the whole
|
|
11
|
+
* window.
|
|
12
|
+
*/
|
|
13
|
+
export const TRAE_REGION_COOLDOWN_MS = 24 * 60 * 60 * 1000;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Trae's free-tier quota (`4008` / "exceeded the quota") resets daily, so its
|
|
17
|
+
* exhaustion is a full-day model backoff — never the generic 1h fallback,
|
|
18
|
+
* which let the account re-enter rotation inside the window.
|
|
19
|
+
*/
|
|
20
|
+
export const TRAE_QUOTA_COOLDOWN_MS = 24 * 60 * 60 * 1000;
|
|
21
|
+
|
|
22
|
+
function isTrae(signals: AccountErrorSignals): boolean {
|
|
23
|
+
return signals.providerId === "trae" || signals.providerId === "traecn";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const traeRules: readonly AccountErrorRule[] = [
|
|
27
|
+
{
|
|
28
|
+
// Trae answers `4013 "AI service is not available in the current region
|
|
29
|
+
// now."` when the upstream does not serve the account's region. That is a
|
|
30
|
+
// durable verdict on the account, not a transient transport fault: it
|
|
31
|
+
// reproduces on every request until the region opens, so it must cool hard
|
|
32
|
+
// for a day rather than be re-dialled every thirty seconds. Must run
|
|
33
|
+
// before `transport-fault`, which would read the 502 as a short
|
|
34
|
+
// `server_error`.
|
|
35
|
+
name: "trae-region-unavailable",
|
|
36
|
+
match: (s) =>
|
|
37
|
+
isTrae(s) &&
|
|
38
|
+
(s.providerCode === "4013" || s.lower.includes("not available in the current region")),
|
|
39
|
+
decide: (s) => {
|
|
40
|
+
const cooldownMs = TRAE_REGION_COOLDOWN_MS;
|
|
41
|
+
return s.result(
|
|
42
|
+
"quota_exhausted",
|
|
43
|
+
"cooldown",
|
|
44
|
+
cooldownMs,
|
|
45
|
+
new Date(Date.now() + cooldownMs),
|
|
46
|
+
s.reason("Trae service unavailable in the current region"),
|
|
47
|
+
// Account-scoped provider evidence still has to reach the writer: a
|
|
48
|
+
// stream frame carries no credential detail, so `canMutate` is false
|
|
49
|
+
// and without this the account stays in rotation to reproduce the
|
|
50
|
+
// refusal for the whole window the region is closed for.
|
|
51
|
+
true,
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
// Trae's free-tier quota (`4008` / "exceeded the quota") resets daily.
|
|
57
|
+
// Treating it like the generic quota cooldown (1h account-wide fallback)
|
|
58
|
+
// let the account re-enter rotation inside the window and reproduce the
|
|
59
|
+
// refusal every few minutes — the "cooldown not holding" report. The
|
|
60
|
+
// verdict is a full-day backoff on *any* model (the account's quota is
|
|
61
|
+
// spent, so every model on it shares the wall). Scoped to the model so
|
|
62
|
+
// the account is not disabled wholesale; the 1-day window means a model
|
|
63
|
+
// stays parked for the day instead of flapping. Must run before
|
|
64
|
+
// `quota-exhausted`, which would apply the generic shorter fallback.
|
|
65
|
+
name: "trae-quota-exhausted",
|
|
66
|
+
match: (s) =>
|
|
67
|
+
isTrae(s) &&
|
|
68
|
+
(s.providerCode === "4008" || s.lower.includes("have exceeded the quota")),
|
|
69
|
+
decide: (s) => {
|
|
70
|
+
const cooldownMs = s.statedCooldownMs ?? TRAE_QUOTA_COOLDOWN_MS;
|
|
71
|
+
return s.result(
|
|
72
|
+
"model_unreachable",
|
|
73
|
+
"cooldown",
|
|
74
|
+
cooldownMs,
|
|
75
|
+
new Date(Date.now() + cooldownMs),
|
|
76
|
+
s.reason("Trae quota exhausted for this model"),
|
|
77
|
+
s.origin === "upstream" && s.modelId !== undefined,
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
];
|
|
@@ -8,7 +8,9 @@ import { BUNDLED_PROVIDER_METADATA, type BundledProviderId } from "./provider-me
|
|
|
8
8
|
import type { ValidatedOutboundHttp2Bidi } from "../network/http2-bidi";
|
|
9
9
|
|
|
10
10
|
/** Closed union of every shipped provider ID. */
|
|
11
|
-
export const BUNDLED_PROVIDER_IDS =
|
|
11
|
+
export const BUNDLED_PROVIDER_IDS: ReadonlySet<BundledProviderId> = new Set(
|
|
12
|
+
BUNDLED_PROVIDER_METADATA.map((definition) => definition.id),
|
|
13
|
+
);
|
|
12
14
|
|
|
13
15
|
/** Normalized builtin or validated custom provider identifier. */
|
|
14
16
|
declare const customProviderIdBrand: unique symbol;
|
|
@@ -187,6 +189,13 @@ export interface ProviderDispatchContext {
|
|
|
187
189
|
readonly outbound_attachment_fetch?: ValidatedOutboundFetch | undefined;
|
|
188
190
|
/** Validated bidirectional HTTP/2 egress bound to the same selected network pool. */
|
|
189
191
|
readonly outbound_http2?: ValidatedOutboundHttp2Bidi | undefined;
|
|
192
|
+
/**
|
|
193
|
+
* Capture sink for one attempt's provider exchange, filled by HTTP fetch
|
|
194
|
+
* wrapping. Adapters whose real upstream transport is not a `fetch`
|
|
195
|
+
* (Qoder's gRPC) write it directly so Request Detail shows the actual
|
|
196
|
+
* conversation call rather than an auth exchange or an empty panel.
|
|
197
|
+
*/
|
|
198
|
+
readonly provider_capture?: ProviderCaptureExchange | undefined;
|
|
190
199
|
/**
|
|
191
200
|
* Route-selected fallback User-Agent from Routing Strategy; set when the
|
|
192
201
|
* operator configured one (bundled API-key providers default to the Claude
|
|
@@ -261,6 +270,20 @@ export function applyRouteUserAgent(
|
|
|
261
270
|
}
|
|
262
271
|
|
|
263
272
|
/** ProviderDispatchTarget identity already resolved by routing before credential leasing. */
|
|
273
|
+
/**
|
|
274
|
+
* Capture sink for one attempt's provider exchange.
|
|
275
|
+
*
|
|
276
|
+
* HTTP transports fill this automatically by wrapping `outbound_fetch` (see
|
|
277
|
+
* `captureProviderExchange` in `transport/dispatch/attempt-finalize.ts`).
|
|
278
|
+
* Adapters whose real upstream call is not a fetch — Qoder's gRPC surface —
|
|
279
|
+
* write it directly so Request Detail shows the actual conversation call
|
|
280
|
+
* instead of an auth exchange or nothing.
|
|
281
|
+
*/
|
|
282
|
+
export interface ProviderCaptureExchange {
|
|
283
|
+
request: unknown;
|
|
284
|
+
response: Promise<unknown>;
|
|
285
|
+
}
|
|
286
|
+
|
|
264
287
|
export interface ProviderDispatchTarget {
|
|
265
288
|
readonly provider_id: ProviderId;
|
|
266
289
|
readonly model_id: string;
|
|
@@ -287,6 +310,14 @@ export interface ProviderAdapter {
|
|
|
287
310
|
candidate: ProviderDispatchTarget,
|
|
288
311
|
context: ProviderDispatchContext,
|
|
289
312
|
): AsyncIterable<CanonicalEvent>;
|
|
313
|
+
/**
|
|
314
|
+
* Optional provider-mandated system prompt, returned for probes and any
|
|
315
|
+
* caller that needs the upstream's required identity prompt. A provider
|
|
316
|
+
* whose wire refuses requests without a specific system prompt (Perch's
|
|
317
|
+
* Saffron fingerprint) declares it here so the probe path can carry it —
|
|
318
|
+
* the probe builder otherwise sends only the user message.
|
|
319
|
+
*/
|
|
320
|
+
systemPrompt?(): string | undefined;
|
|
290
321
|
/**
|
|
291
322
|
* Optional native-protocol capability for the public `POST /v1/systemone`
|
|
292
323
|
* route (the dispatch leg of the System One service, alongside the probing
|
|
@@ -422,7 +453,7 @@ function normalizeProviderSlug(id: string): string {
|
|
|
422
453
|
/** Returns true for one of Cartethyia's reserved builtin IDs, case-insensitively. */
|
|
423
454
|
export function isBundledProviderId(value: string): value is BundledProviderId {
|
|
424
455
|
const normalized = value.trim().toLowerCase();
|
|
425
|
-
return (
|
|
456
|
+
return BUNDLED_PROVIDER_IDS.has(normalized as BundledProviderId);
|
|
426
457
|
}
|
|
427
458
|
|
|
428
459
|
/** Normalizes a provider identifier before persistence or registry lookup. */
|
|
@@ -1,10 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Provider credential envelope and resolution handling.
|
|
3
|
+
*
|
|
4
|
+
* Strips token envelopes so adapters never emit `Bearer Bearer`, and resolves
|
|
5
|
+
* ordered upstream credential alternatives for the control plane.
|
|
6
|
+
*/
|
|
3
7
|
|
|
4
8
|
import { GatewayError } from "../protocol/gateway-error";
|
|
5
|
-
import { unwrapProviderToken } from "./provider-credential-envelope";
|
|
6
9
|
import type { CredentialKind, ProviderId, ResolvedCredential } from "./provider-contracts";
|
|
7
10
|
|
|
11
|
+
/** Envelope labels a stored credential may carry. */
|
|
12
|
+
export type TokenEnvelope = "bearer" | "oauth" | "provider";
|
|
13
|
+
|
|
14
|
+
const ENVELOPE_PREFIXES: Record<TokenEnvelope, string> = {
|
|
15
|
+
bearer: "Bearer ",
|
|
16
|
+
oauth: "OAuth ",
|
|
17
|
+
provider: "provider:",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Removes one explicitly identified envelope so adapters never emit
|
|
22
|
+
* `Bearer Bearer`. The bearer branch strips case-insensitively with
|
|
23
|
+
* surrounding-whitespace normalization; oauth/provider envelopes keep their
|
|
24
|
+
* exact byte slices. Input without the identified envelope passes through
|
|
25
|
+
* untouched.
|
|
26
|
+
*/
|
|
27
|
+
export function unwrapProviderToken(
|
|
28
|
+
value: string | Uint8Array,
|
|
29
|
+
envelope?: TokenEnvelope,
|
|
30
|
+
): Uint8Array {
|
|
31
|
+
const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
|
|
32
|
+
const text = new TextDecoder().decode(bytes);
|
|
33
|
+
if (envelope === "bearer") {
|
|
34
|
+
const trimmed = text.trim();
|
|
35
|
+
const match = /^bearer\s+(.+)$/is.exec(trimmed);
|
|
36
|
+
return new TextEncoder().encode((match?.[1] ?? trimmed).trim());
|
|
37
|
+
}
|
|
38
|
+
const prefix = envelope === undefined ? undefined : ENVELOPE_PREFIXES[envelope];
|
|
39
|
+
if (prefix !== undefined && text.toLowerCase().startsWith(prefix.toLowerCase())) {
|
|
40
|
+
return new TextEncoder().encode(text.slice(prefix.length));
|
|
41
|
+
}
|
|
42
|
+
return new Uint8Array(bytes);
|
|
43
|
+
}
|
|
44
|
+
|
|
8
45
|
/** One control-plane credential alternative, ordered by operator preference. */
|
|
9
46
|
export interface CredentialAlternative {
|
|
10
47
|
readonly provider_id: ProviderId;
|