cartethyia 2.7.9 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +6 -5
- package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Dq530awD.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-DaZZ3cqP.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BlC_xdkX.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-7xT5yg2E.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-6Teoq9Se.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CZuI0MAR.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-C1hNTlkr.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-0yfhSI1C.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-0yfhSI1C.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js.gz +0 -0
- package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-Ddhm1InL.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js +9 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js +1 -0
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-BzZAvYSX.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage--s-EfEIL.js} +1 -1
- package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-BjXFGSK4.js} +1 -1
- package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-CP8F8BN7.js} +1 -1
- package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-2hc12Uz2.js} +1 -1
- package/dist/dashboard/assets/api-keys-2hc12Uz2.js.gz +0 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css +2 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css.gz +0 -0
- package/dist/dashboard/assets/assert-BMcj1xZE.js +1 -0
- package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-DkRUuVfz.js} +1 -1
- package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-CDjW841Q.js} +1 -1
- package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
- package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-Bto5UlL9.js} +1 -1
- package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-DaH6g9hL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-BYKBCzjk.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-DL0Zftwv.js} +1 -1
- package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-Dpx_UXaS.js} +1 -1
- package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
- package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-BV7-1NoM.js} +1 -1
- package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-CGvdNU6r.js} +1 -1
- package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js +4 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-BS3DZlZj.js} +1 -1
- package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
- package/dist/dashboard/assets/{index-BU64Lqvp.js → index-SQnvvTWX.js} +2 -2
- package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-Dl-cuLK0.js} +1 -1
- package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
- package/dist/dashboard/assets/{input-BCVkdR9d.js → input-Bfx0EzRc.js} +1 -1
- package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-BxhbQ_JS.js} +1 -1
- package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-D49Y4ptn.js} +1 -1
- package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
- package/dist/dashboard/assets/{live-Cae01iu8.js → live-CbVTdX9V.js} +1 -1
- package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
- package/dist/dashboard/assets/{network-CRib7H_2.js → network-JGvDveof.js} +1 -1
- package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-Dw6kiew-.js} +1 -1
- package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-iUoI8GeE.js} +1 -1
- package/dist/dashboard/assets/pagination-iUoI8GeE.js.gz +0 -0
- package/dist/dashboard/assets/providers/sensenova-intl.png +0 -0
- package/dist/dashboard/assets/providers/sensenova.png +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/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-8jZr-3WE.js} +1 -1
- package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
- package/dist/dashboard/assets/{select-ChoFWyKz.js → select-oO1FxMVb.js} +2 -2
- package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-CLr3kdIx.js} +1 -1
- package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-Y3pDteZr.js} +1 -1
- package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
- package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-CArX_Iak.js} +1 -1
- package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
- package/dist/dashboard/assets/{switch-Db70BMix.js → switch-DwlwmDO-.js} +1 -1
- package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-DJ9GYEgf.js} +1 -1
- package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +54 -53
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
- package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
- package/package.json +1 -1
- package/src/boot/app.ts +11 -14
- package/src/boot/dependencies.ts +5 -7
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/providers.ts +6 -5
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +28 -8
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +40 -28
- 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/backup/nine-router.ts +71 -10
- package/src/console/backup/service.ts +8 -1
- package/src/console/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/console-router.ts +1 -1
- package/src/console/domain-registration.ts +7 -4
- package/src/console/inferhub/client.ts +1 -1
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +7 -13
- package/src/console/observability/live.ts +2 -2
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/contracts.ts +9 -3
- package/src/console/providers/catalog/model-operations.ts +2 -3
- package/src/console/providers/catalog/provider-operations.ts +1 -1
- package/src/console/providers/catalog/routes.ts +3 -2
- package/src/console/providers/catalog/store.ts +1914 -1902
- package/src/console/providers/detail/routes.ts +1 -0
- package/src/console/providers/detail/store.ts +4 -0
- package/src/console/providers/oauth/callback-listener.ts +30 -11
- package/src/console/providers/oauth/device-egress.ts +123 -0
- package/src/console/providers/oauth/routes.ts +123 -16
- package/src/console/quota/account-quota-global.ts +14 -24
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +11 -23
- package/src/console/quota/account-test-egress.ts +2 -2
- package/src/console/quota/daily-growth-job.ts +1 -1
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/contracts.ts +1 -1
- package/src/console/routing/pools/operations.ts +32 -47
- package/src/console/routing/pools/probe-verdict.ts +2 -2
- package/src/console/routing/pools/relay-deploy.ts +1 -1
- package/src/console/routing/pools/store.ts +4 -4
- package/src/console/shared/errors.ts +33 -6
- package/src/main.ts +2 -3
- package/src/network/client.ts +67 -0
- package/src/network/egress/bidi-session.ts +322 -0
- package/src/network/egress/dial.ts +201 -0
- package/src/network/egress/factory.ts +191 -0
- package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
- package/src/network/egress/tls-profile.ts +123 -0
- package/src/network/egress/ws.ts +175 -0
- package/src/network/pool/{byte-accounting.ts → accounting.ts} +3 -2
- package/src/network/{types.ts → pool/agent-config.ts} +1 -1
- package/src/network/pool/agent.ts +2 -2
- package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +95 -20
- package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
- package/src/network/pool/resolver.ts +10 -849
- package/src/network/pool/selector.ts +184 -41
- package/src/network/ssrf.ts +7 -7
- package/src/network/{retry.ts → util/retry.ts} +1 -1
- package/src/network/{rate-limit-verdict.ts → verdict.ts} +1 -1
- package/src/observability/event-log.ts +7 -361
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +16 -26
- 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 +18 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +17 -0
- package/src/persistence/ttl-cache.ts +21 -3
- package/src/protocol/codecs.ts +59 -2
- package/src/protocol/dsml.ts +34 -86
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/request/chat.ts +10 -10
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +34 -22
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/messages.ts +34 -7
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/protocol/translation/quirks.ts +15 -0
- package/src/protocol/upstream/send-claude-messages.ts +2 -2
- package/src/providers/adapters/compatible-adapter.ts +76 -12
- package/src/providers/authentication/oauth-client.ts +40 -6
- package/src/providers/authentication/oauth-device-flow.ts +105 -11
- package/src/providers/authentication/oauth-flow-store.ts +82 -1
- package/src/providers/authentication/oauth-refresh-service.ts +766 -758
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +137 -36
- package/src/providers/discovery/discovery-types.ts +7 -0
- package/src/providers/discovery/probe-phases.ts +20 -6
- package/src/providers/discovery/probing-service.ts +22 -2
- 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 +16 -1
- package/src/providers/integrations/buddy/account.ts +969 -0
- package/src/providers/integrations/buddy/capabilities.ts +12 -12
- package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
- package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +176 -20
- package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +41 -26
- package/src/providers/integrations/buddy/headers.ts +311 -0
- package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
- package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
- package/src/providers/integrations/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline-oauth.ts +10 -5
- package/src/providers/integrations/cline/cline.ts +5 -6
- package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
- package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
- package/src/providers/integrations/clixad/provider.ts +145 -0
- package/src/providers/integrations/codex/codex-oauth.ts +8 -5
- package/src/providers/integrations/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +1 -9
- package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
- package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
- package/src/providers/integrations/cursor/cursor-request.ts +26 -12
- package/src/providers/integrations/cursor/cursor-shared.ts +8 -5
- package/src/providers/integrations/cursor/cursor.ts +574 -110
- 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/deepai/deepai-catalog.ts +149 -0
- package/src/providers/integrations/deepai/deepai-common.ts +145 -0
- package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
- package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
- package/src/providers/integrations/deepai/provider.ts +42 -0
- package/src/providers/integrations/devin/devin-auth.ts +7 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
- package/src/providers/integrations/factory/factory-oauth.ts +33 -51
- 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 +5 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
- package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
- package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
- package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
- package/src/providers/integrations/freepi/freepi-session.ts +158 -0
- package/src/providers/integrations/freepi/freepi.ts +80 -54
- package/src/providers/integrations/github/github-discovery.ts +30 -13
- package/src/providers/integrations/github/github-oauth.ts +217 -44
- package/src/providers/integrations/github/github-quota.ts +22 -5
- package/src/providers/integrations/github/github.ts +40 -32
- package/src/providers/integrations/grok/grok-oauth.ts +52 -49
- 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 +3 -3
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +42 -13
- package/src/providers/integrations/hermes/index.ts +2 -0
- package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
- package/src/providers/integrations/kilo/kilo.ts +1 -1
- package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
- 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-oauth.ts +2 -2
- package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- package/src/providers/integrations/muse/muse-oauth.ts +5 -5
- 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/openrouter/provider.ts +1 -1
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-protocol.ts +0 -6
- package/src/providers/integrations/perch/perch-quota.ts +2 -16
- package/src/providers/integrations/perch/perch.ts +6 -16
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +20 -125
- package/src/providers/integrations/qoder/qoder-auth.ts +109 -30
- package/src/providers/integrations/qoder/qoder-catalog.ts +21 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +32 -29
- package/src/providers/integrations/qoder/qoder-checkin.ts +5 -5
- package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
- package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
- package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
- package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
- package/src/providers/integrations/qoder/qoder-types.ts +9 -0
- package/src/providers/integrations/qoder/qoder.ts +3 -26
- package/src/providers/integrations/sensenova/provider.ts +144 -0
- package/src/providers/integrations/trae/trae-protocol.ts +9 -5
- package/src/providers/integrations/trae/trae-quota.ts +21 -29
- package/src/providers/integrations/trae/trae-realm.ts +38 -0
- package/src/providers/integrations/trae/trae.ts +7 -24
- package/src/providers/integrations/vercel/provider.ts +117 -0
- package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
- package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
- 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 +205 -0
- package/src/providers/operations/account-error-classification.ts +199 -307
- package/src/providers/operations/account-health-service.ts +82 -1
- package/src/providers/operations/account-refresh-status.ts +36 -37
- package/src/providers/operations/account-tier.ts +15 -16
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +100 -81
- package/src/providers/operations/cooldown-rules.ts +11 -16
- package/src/providers/operations/oauth-refresh-lead.ts +46 -22
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +118 -51
- 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/operations/session-resolution.ts +17 -1
- package/src/providers/provider-contracts.ts +6 -4
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +70 -10
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- package/src/providers/quota/quota-contracts.ts +2 -0
- 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 +100 -21
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/security/ip-boundary.ts +1 -1
- package/src/transport/dispatch/attempt-finalize.ts +15 -14
- package/src/transport/dispatch/attempt-loop.ts +73 -19
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/handler-deps.ts +1 -1
- package/src/transport/dispatch/leases.ts +1 -1
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +8 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/retry-policy.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +10 -7
- package/src/transport/dispatch/streaming/stream.ts +9 -37
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +63 -0
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +16 -30
- 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 +393 -32
- package/src/transport/shutdown-notice.ts +1 -1
- 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 +7 -1
- 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 +22 -5
- package/src/transport/surface/responses/parse.ts +3 -11
- package/src/workers/buddy-growth-tasks.ts +8 -8
- package/src/workers/daily-checkin.ts +4 -4
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/zcode-gift-claim.ts +1 -1
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/pagination-ecPvG_8g.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-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/src/network/pool-health.ts +0 -210
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
- package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
- package/src/providers/integrations/buddy/buddy-identity.ts +0 -300
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
- package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
- package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -185
- package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
- package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -175
- package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
- package/src/providers/integrations/qoder/chat-proto.ts +0 -328
- package/src/providers/integrations/qoder/qoder-grpc.ts +0 -871
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -588
- 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-B7kIWFm5.js → app-C0gTXEoN.js} +0 -0
- /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-C0gTXEoN.js.gz} +0 -0
- /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
- /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
- /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
- /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
- /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
- /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
|
@@ -5,7 +5,7 @@ import type { ApiKeyAdmissionService } from "../../security/admission/service";
|
|
|
5
5
|
import type { VisionHandoffCall, VisionHandoffResult } from "../vision-handoff";
|
|
6
6
|
import { isImageOrPlaceholderPart, lastImageCarryingUserTurnIndex } from "../vision-handoff";
|
|
7
7
|
import type { CallerIdentity } from "../../security/access-control";
|
|
8
|
-
import type { RouteCandidate as RouteCandidate, InMemoryRouteSnapshotService, RoutePlan } from "../routing/route-model";
|
|
8
|
+
import type { RouteCandidate as RouteCandidate, InMemoryRouteSnapshotService, RoutePlan, RouteSnapshot } from "../routing/route-model";
|
|
9
9
|
import type { RouteContinuationAffinity } from "../routing/route-model";
|
|
10
10
|
import { resolveAliasTarget, type RoutingEngine } from "../routing/router";
|
|
11
11
|
import { projectForRoute, routeCapabilitiesFor, deriveRequiredCapabilities } from "../../protocol/translation/capabilities";
|
|
@@ -80,7 +80,17 @@ function replaceImagePartsWithText(
|
|
|
80
80
|
}
|
|
81
81
|
const hasImage = message.content.some(isImageOrPlaceholderPart);
|
|
82
82
|
if (!hasImage) return message;
|
|
83
|
-
|
|
83
|
+
// Never delete images silently (proposal Option E): replace a dropped
|
|
84
|
+
// older image with a marker so the main model at least knows something
|
|
85
|
+
// was there, instead of the loss being invisible.
|
|
86
|
+
return {
|
|
87
|
+
...message,
|
|
88
|
+
content: message.content.flatMap((part) =>
|
|
89
|
+
isImageOrPlaceholderPart(part)
|
|
90
|
+
? [{ kind: "text" as const, text: "[image omitted]" }]
|
|
91
|
+
: [part],
|
|
92
|
+
),
|
|
93
|
+
};
|
|
84
94
|
});
|
|
85
95
|
const unchangedMessages = nextMessages.every(
|
|
86
96
|
(message, i) => message === request.messages[i],
|
|
@@ -350,6 +360,61 @@ export class ProxyRequestPreparer {
|
|
|
350
360
|
|
|
351
361
|
constructor(private readonly deps: ProxyRequestPreparerDeps) {}
|
|
352
362
|
|
|
363
|
+
/**
|
|
364
|
+
* The key's model-admission gate, shared by the canonical and native
|
|
365
|
+
* preparation paths: CLI-mapping eligibility → alias resolution → the
|
|
366
|
+
* model allowlist → the model-prefix gate, in that exact order. Rejects
|
|
367
|
+
* with the same `model_not_allowed` 403s (same code, status, message, and
|
|
368
|
+
* detail fields) on both paths, so a policy change here cannot silently
|
|
369
|
+
* split admission between the chat and native surfaces. Returns the state
|
|
370
|
+
* the gate computed that the caller still needs for routing: the resolved
|
|
371
|
+
* alias target and whether CLI mappings were eligible.
|
|
372
|
+
*/
|
|
373
|
+
#resolveAliasAndGateModel(
|
|
374
|
+
model: string,
|
|
375
|
+
authorization: ResolvedApiKey,
|
|
376
|
+
clientUserAgent: string | undefined,
|
|
377
|
+
snapshot: RouteSnapshot,
|
|
378
|
+
): { readonly resolvedTarget: string; readonly allowCliMappings: boolean } {
|
|
379
|
+
// Scope opts the key into the mapping table; the User-Agent decides whether
|
|
380
|
+
// *this* request may consume it. Without the UA gate, a Claude→DeepSeek
|
|
381
|
+
// remap would also rewrite a non-Claude caller that named `opus`.
|
|
382
|
+
const allowCliMappings = allowsCliToolMappings(authorization.scopes, {
|
|
383
|
+
...(clientUserAgent === undefined ? {} : { userAgent: clientUserAgent }),
|
|
384
|
+
});
|
|
385
|
+
// CLI source→target mappings are an explicit API-key capability. Ordinary
|
|
386
|
+
// tenant aliases remain available to every key; only selected keys may
|
|
387
|
+
// consume the CLI mapping table.
|
|
388
|
+
const resolvedTarget = resolveAliasTarget(
|
|
389
|
+
snapshot,
|
|
390
|
+
authorization.tenantId,
|
|
391
|
+
model,
|
|
392
|
+
allowCliMappings,
|
|
393
|
+
authorization.cliMappingOwnerId ?? authorization.id,
|
|
394
|
+
);
|
|
395
|
+
// Pass `model` as the requested name so an allowlisted alias / a CLI
|
|
396
|
+
// remapping can authorize the resolved target (see modelRejectionReason).
|
|
397
|
+
const allowedForKey = isModelAllowed(authorization.snapshot, resolvedTarget, undefined, model);
|
|
398
|
+
if (
|
|
399
|
+
authorization.modelPrefix &&
|
|
400
|
+
!model.startsWith(authorization.modelPrefix) &&
|
|
401
|
+
!resolvedTarget.startsWith(authorization.modelPrefix) &&
|
|
402
|
+
!allowedForKey
|
|
403
|
+
)
|
|
404
|
+
throw new GatewayError(
|
|
405
|
+
"model_not_allowed",
|
|
406
|
+
403,
|
|
407
|
+
`This key may only use models starting with '${authorization.modelPrefix}' (request {requestId}). Ask for one inside that prefix, or have an operator widen the key. The door's locked for this one — check the allowlist 🔒`,
|
|
408
|
+
{ model, required_prefix: authorization.modelPrefix },
|
|
409
|
+
);
|
|
410
|
+
if (!allowedForKey) {
|
|
411
|
+
throw new GatewayError("model_not_allowed", 403, "model is not allowed for this API key (request {requestId}). The door's locked for this one — check the allowlist 🔒", {
|
|
412
|
+
model,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return { resolvedTarget, allowCliMappings };
|
|
416
|
+
}
|
|
417
|
+
|
|
353
418
|
/** Injects the vision-handoff runner (called once from boot wiring). */
|
|
354
419
|
bindVisionHandoff(
|
|
355
420
|
runner: (input: VisionHandoffCall) => Promise<readonly VisionHandoffResult[]>,
|
|
@@ -391,47 +456,12 @@ export class ProxyRequestPreparer {
|
|
|
391
456
|
? initialRequest
|
|
392
457
|
: withThinkingSuffixIntent({ ...initialRequest, model: bareModel }, thinkingIntent);
|
|
393
458
|
const snapshot = await this.deps.snapshotService.getSnapshot();
|
|
394
|
-
|
|
395
|
-
// *this* request may consume it. Without the UA gate, a Claude→DeepSeek
|
|
396
|
-
// remap would also rewrite a non-Claude caller that named `opus`.
|
|
397
|
-
const allowCliMappings = allowsCliToolMappings(authorization.scopes, {
|
|
398
|
-
...(input.clientUserAgent === undefined ? {} : { userAgent: input.clientUserAgent }),
|
|
399
|
-
});
|
|
400
|
-
// CLI source→target mappings are an explicit API-key capability. Ordinary
|
|
401
|
-
// tenant aliases remain available to every key; only selected keys may
|
|
402
|
-
// consume the CLI mapping table.
|
|
403
|
-
const resolvedTarget = resolveAliasTarget(
|
|
404
|
-
snapshot,
|
|
405
|
-
authorization.tenantId,
|
|
406
|
-
request.model,
|
|
407
|
-
allowCliMappings,
|
|
408
|
-
authorization.cliMappingOwnerId ?? authorization.id,
|
|
409
|
-
);
|
|
410
|
-
// Pass `request.model` as the requested name so an allowlisted alias / a
|
|
411
|
-
// CLI remapping can authorize the resolved target (see modelRejectionReason).
|
|
412
|
-
const allowedForKey = isModelAllowed(
|
|
413
|
-
authorization.snapshot,
|
|
414
|
-
resolvedTarget,
|
|
415
|
-
undefined,
|
|
459
|
+
const { allowCliMappings } = this.#resolveAliasAndGateModel(
|
|
416
460
|
request.model,
|
|
461
|
+
authorization,
|
|
462
|
+
input.clientUserAgent,
|
|
463
|
+
snapshot,
|
|
417
464
|
);
|
|
418
|
-
if (
|
|
419
|
-
authorization.modelPrefix &&
|
|
420
|
-
!request.model.startsWith(authorization.modelPrefix) &&
|
|
421
|
-
!resolvedTarget.startsWith(authorization.modelPrefix) &&
|
|
422
|
-
!allowedForKey
|
|
423
|
-
)
|
|
424
|
-
throw new GatewayError(
|
|
425
|
-
"model_not_allowed",
|
|
426
|
-
403,
|
|
427
|
-
`This key may only use models starting with '${authorization.modelPrefix}' (request {requestId}). Ask for one inside that prefix, or have an operator widen the key. The door's locked for this one — check the allowlist 🔒`,
|
|
428
|
-
{ model: request.model, required_prefix: authorization.modelPrefix },
|
|
429
|
-
);
|
|
430
|
-
if (!allowedForKey) {
|
|
431
|
-
throw new GatewayError("model_not_allowed", 403, "model is not allowed for this API key (request {requestId}). The door's locked for this one — check the allowlist 🔒", {
|
|
432
|
-
model: request.model,
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
465
|
// An inline image that cannot survive the upstream is dropped here rather
|
|
436
466
|
// than dispatched: a provider rejects the whole request for one corrupt
|
|
437
467
|
// attachment, and the buddy family reports it with no field named, so the
|
|
@@ -474,6 +504,7 @@ export class ProxyRequestPreparer {
|
|
|
474
504
|
allowCliMappings,
|
|
475
505
|
authorization.cliMappingOwnerId ?? authorization.id,
|
|
476
506
|
continuationAffinity,
|
|
507
|
+
request.conversation_id ?? request.conversation?.conversation_id,
|
|
477
508
|
);
|
|
478
509
|
} catch (error) {
|
|
479
510
|
// `plan()` collapses "no candidate supports this capability" into
|
|
@@ -541,6 +572,7 @@ export class ProxyRequestPreparer {
|
|
|
541
572
|
allowCliMappings,
|
|
542
573
|
authorization.cliMappingOwnerId ?? authorization.id,
|
|
543
574
|
continuationAffinity,
|
|
575
|
+
request.conversation_id ?? request.conversation?.conversation_id,
|
|
544
576
|
);
|
|
545
577
|
plan = handoffPlan;
|
|
546
578
|
variantRequest = replaced;
|
|
@@ -725,42 +757,18 @@ export class ProxyRequestPreparer {
|
|
|
725
757
|
readonly clientUserAgent?: string;
|
|
726
758
|
readonly eligible: (candidate: RouteCandidate) => boolean;
|
|
727
759
|
readonly emptyMessage: string;
|
|
760
|
+
/** Session identity for sticky routing (conversation id when present). */
|
|
761
|
+
readonly conversationId?: string;
|
|
728
762
|
}): Promise<PreparedNativeRequest> {
|
|
729
763
|
if (input.signal?.aborted)
|
|
730
764
|
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
731
765
|
const snapshot = await this.deps.snapshotService.getSnapshot();
|
|
732
|
-
const allowCliMappings =
|
|
733
|
-
...(input.clientUserAgent === undefined ? {} : { userAgent: input.clientUserAgent }),
|
|
734
|
-
});
|
|
735
|
-
const resolvedTarget = resolveAliasTarget(
|
|
736
|
-
snapshot,
|
|
737
|
-
input.authorization.tenantId,
|
|
738
|
-
input.model,
|
|
739
|
-
allowCliMappings,
|
|
740
|
-
input.authorization.cliMappingOwnerId ?? input.authorization.id,
|
|
741
|
-
);
|
|
742
|
-
const allowedForKey = isModelAllowed(
|
|
743
|
-
input.authorization.snapshot,
|
|
744
|
-
resolvedTarget,
|
|
745
|
-
undefined,
|
|
766
|
+
const { allowCliMappings } = this.#resolveAliasAndGateModel(
|
|
746
767
|
input.model,
|
|
768
|
+
input.authorization,
|
|
769
|
+
input.clientUserAgent,
|
|
770
|
+
snapshot,
|
|
747
771
|
);
|
|
748
|
-
if (
|
|
749
|
-
input.authorization.modelPrefix &&
|
|
750
|
-
!input.model.startsWith(input.authorization.modelPrefix) &&
|
|
751
|
-
!resolvedTarget.startsWith(input.authorization.modelPrefix) &&
|
|
752
|
-
!allowedForKey
|
|
753
|
-
)
|
|
754
|
-
throw new GatewayError(
|
|
755
|
-
"model_not_allowed",
|
|
756
|
-
403,
|
|
757
|
-
`This key may only use models starting with '${input.authorization.modelPrefix}' (request {requestId}). Ask for one inside that prefix, or have an operator widen the key. The door's locked for this one — check the allowlist 🔒`,
|
|
758
|
-
{ model: input.model, required_prefix: input.authorization.modelPrefix },
|
|
759
|
-
);
|
|
760
|
-
if (!allowedForKey)
|
|
761
|
-
throw new GatewayError("model_not_allowed", 403, "model is not allowed for this API key (request {requestId}). The door's locked for this one — check the allowlist 🔒", {
|
|
762
|
-
model: input.model,
|
|
763
|
-
});
|
|
764
772
|
const plan = await this.deps.routingEngine.plan(
|
|
765
773
|
input.model,
|
|
766
774
|
snapshot,
|
|
@@ -768,6 +776,8 @@ export class ProxyRequestPreparer {
|
|
|
768
776
|
undefined,
|
|
769
777
|
allowCliMappings,
|
|
770
778
|
input.authorization.cliMappingOwnerId ?? input.authorization.id,
|
|
779
|
+
undefined,
|
|
780
|
+
input.conversationId,
|
|
771
781
|
);
|
|
772
782
|
if (input.signal?.aborted)
|
|
773
783
|
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
// Bounded LRU map: the one shared implementation for routing-layer caches.
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Evicts the oldest-inserted entry while `map.size > cap`. The shared
|
|
5
|
+
* insertion-order bound for caches whose read path must not refresh recency
|
|
6
|
+
* (unlike `BoundedLruMap`, whose `get` does). Eviction is one `keys().next()`
|
|
7
|
+
* and no bookkeeping arrays.
|
|
8
|
+
*/
|
|
9
|
+
export function evictOldestEntry<K>(map: Map<K, unknown>, cap: number): void {
|
|
10
|
+
while (map.size > cap) {
|
|
11
|
+
const oldest = map.keys().next().value;
|
|
12
|
+
if (oldest === undefined) break;
|
|
13
|
+
map.delete(oldest);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
/**
|
|
4
18
|
* A capacity-bounded LRU `Map`.
|
|
5
19
|
*
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Drain urgency and reserve scoring for candidate ordering.
|
|
2
|
+
//
|
|
3
|
+
// Ported from oh-my-pi's `windowRequiredDrain` and reserve semantics
|
|
4
|
+
// (`packages/ai/src/auth/usage-report.ts`, `policy.ts`): within the same rank
|
|
5
|
+
// group, an account whose remaining quota is about to expire unused should be
|
|
6
|
+
// burned first ("use it or lose it"), and an account parked in reserve (at or
|
|
7
|
+
// below the reserve percent) should yield to non-reserve siblings.
|
|
8
|
+
import {
|
|
9
|
+
totalRemainingPercent,
|
|
10
|
+
type ProviderQuotaWindow,
|
|
11
|
+
} from "../../providers/quota/quota-contracts";
|
|
12
|
+
|
|
13
|
+
/** Minimum time before reset that still counts as "some headroom" (1 minute). */
|
|
14
|
+
const MIN_REMAINING_MS = 60_000;
|
|
15
|
+
|
|
16
|
+
/** Usable fraction of a window: 0..1, with 0.5 neutral when unmeasured. */
|
|
17
|
+
function usedFraction(window: ProviderQuotaWindow): number {
|
|
18
|
+
const used = window.usedPercent;
|
|
19
|
+
if (typeof used === "number" && Number.isFinite(used)) {
|
|
20
|
+
return Math.min(1, Math.max(0, used / 100));
|
|
21
|
+
}
|
|
22
|
+
const remaining = window.remainingPercent;
|
|
23
|
+
if (typeof remaining === "number" && Number.isFinite(remaining)) {
|
|
24
|
+
return Math.min(1, Math.max(0, 1 - remaining / 100));
|
|
25
|
+
}
|
|
26
|
+
if (typeof window.used === "number" && Number.isFinite(window.used)) {
|
|
27
|
+
if (typeof window.limit === "number" && Number.isFinite(window.limit) && window.limit > 0) {
|
|
28
|
+
return Math.min(1, Math.max(0, window.used / window.limit));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (typeof window.remaining === "number" && Number.isFinite(window.remaining)) {
|
|
32
|
+
if (typeof window.limit === "number" && Number.isFinite(window.limit) && window.limit > 0) {
|
|
33
|
+
return Math.min(1, Math.max(0, 1 - window.remaining / window.limit));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return 0.5;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Reset instant (epoch ms) for a window, or undefined when it has no clock. */
|
|
40
|
+
function resetAtMs(window: ProviderQuotaWindow): number | undefined {
|
|
41
|
+
if (window.resetsAt === null || window.resetsAt === undefined) return undefined;
|
|
42
|
+
const at = Date.parse(window.resetsAt);
|
|
43
|
+
return Number.isFinite(at) ? at : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Drain score for the most-urgent relevant window: headroom fraction divided by
|
|
48
|
+
* remaining hours until reset. Higher means quota that is about to expire
|
|
49
|
+
* unused and should be burned first. Returns 0 when no window carries usable
|
|
50
|
+
* evidence (the candidate is neutral, never penalized).
|
|
51
|
+
*/
|
|
52
|
+
export function drainScore(
|
|
53
|
+
windows: readonly ProviderQuotaWindow[] | undefined,
|
|
54
|
+
nowMs = Date.now(),
|
|
55
|
+
): number {
|
|
56
|
+
if (windows === undefined || windows.length === 0) return 0;
|
|
57
|
+
let best = 0;
|
|
58
|
+
for (const window of windows) {
|
|
59
|
+
const headroom = 1 - usedFraction(window);
|
|
60
|
+
if (headroom <= 0) continue;
|
|
61
|
+
const resetAt = resetAtMs(window);
|
|
62
|
+
const durationMs = windowAccountingDurationMs(window);
|
|
63
|
+
let remainingMs = resetAt === undefined ? (durationMs ?? MIN_REMAINING_MS) : resetAt - nowMs;
|
|
64
|
+
if (durationMs !== undefined && durationMs > 0) {
|
|
65
|
+
remainingMs = Math.min(remainingMs, durationMs);
|
|
66
|
+
}
|
|
67
|
+
const remainingHours = Math.max(remainingMs, MIN_REMAINING_MS) / (60 * 60 * 1000);
|
|
68
|
+
best = Math.max(best, headroom / remainingHours);
|
|
69
|
+
}
|
|
70
|
+
return best;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function windowAccountingDurationMs(window: ProviderQuotaWindow): number | undefined {
|
|
74
|
+
// Providers state window durations in the kind label (e.g. "5h", "7d") when
|
|
75
|
+
// they do not carry an explicit duration field. Parse the common spellings;
|
|
76
|
+
// an unparseable label contributes no clock.
|
|
77
|
+
const text = `${window.kind} ${window.label}`.toLowerCase();
|
|
78
|
+
const m = /(\d+(?:\.\d+)?)\s*(h|hour|hr|d|day|m|min|minute|s|sec|second)\b/.exec(text);
|
|
79
|
+
if (!m) return undefined;
|
|
80
|
+
const value = Number(m[1]);
|
|
81
|
+
if (!Number.isFinite(value)) return undefined;
|
|
82
|
+
const unit = m[2];
|
|
83
|
+
if (unit === "h" || unit === "hour" || unit === "hr") return value * 60 * 60 * 1000;
|
|
84
|
+
if (unit === "d" || unit === "day") return value * 24 * 60 * 60 * 1000;
|
|
85
|
+
if (unit === "m" || unit === "min" || unit === "minute") return value * 60 * 1000;
|
|
86
|
+
if (unit === "s" || unit === "sec" || unit === "second") return value * 1000;
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The account's remaining fraction (0..1) across its pooled credit and
|
|
92
|
+
* independent windows, or undefined when nothing is measured. Mirrors
|
|
93
|
+
* `totalRemainingPercent` (the router's existing aggregate) but normalized to a
|
|
94
|
+
* 0..1 fraction.
|
|
95
|
+
*/
|
|
96
|
+
export function remainingFraction(
|
|
97
|
+
windows: readonly ProviderQuotaWindow[] | undefined,
|
|
98
|
+
): number | undefined {
|
|
99
|
+
if (windows === undefined || windows.length === 0) return undefined;
|
|
100
|
+
const percent = totalRemainingPercent(windows);
|
|
101
|
+
if (percent === null) return undefined;
|
|
102
|
+
return Math.min(1, Math.max(0, percent / 100));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** True when the account's remaining fraction is at or below the reserve percent. */
|
|
106
|
+
export function isInReserve(
|
|
107
|
+
windows: readonly ProviderQuotaWindow[] | undefined,
|
|
108
|
+
reservePct: number,
|
|
109
|
+
): boolean {
|
|
110
|
+
if (reservePct <= 0) return false;
|
|
111
|
+
const fraction = remainingFraction(windows);
|
|
112
|
+
if (fraction === undefined) return false;
|
|
113
|
+
return fraction * 100 <= reservePct;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* True when a reserved account has healed above the reclaim threshold and may be
|
|
118
|
+
* reclaimed to full priority.
|
|
119
|
+
*/
|
|
120
|
+
export function isReclaimReady(
|
|
121
|
+
windows: readonly ProviderQuotaWindow[] | undefined,
|
|
122
|
+
reservePct: number,
|
|
123
|
+
reclaimAbovePct: number,
|
|
124
|
+
): boolean {
|
|
125
|
+
if (reservePct <= 0 || reclaimAbovePct <= reservePct) return false;
|
|
126
|
+
const fraction = remainingFraction(windows);
|
|
127
|
+
if (fraction === undefined) return false;
|
|
128
|
+
return fraction * 100 >= reclaimAbovePct;
|
|
129
|
+
}
|
|
@@ -17,7 +17,13 @@
|
|
|
17
17
|
* costs one refused request that the cooldown machinery already handles; a
|
|
18
18
|
* wrong paid match silently blackholes a model.
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The marker set that proves a plan string is free. Exported so every
|
|
22
|
+
* free-plan consumer — the routing plan gate here and the cooldown-tier
|
|
23
|
+
* derivation in `account-tier.ts` — reads the same list, so the two cannot
|
|
24
|
+
* drift apart about an account.
|
|
25
|
+
*/
|
|
26
|
+
export const FREE_PLAN_MARKERS: readonly string[] = ["free"];
|
|
21
27
|
|
|
22
28
|
export function isFreePlanAccount(plan: string | null | undefined): boolean {
|
|
23
29
|
if (plan === null || plan === undefined) return false;
|
|
@@ -36,3 +36,63 @@ const MAX_ROUND_ROBIN_ENTRIES = 1000;
|
|
|
36
36
|
export function createRoundRobinMap(): BoundedLruMap<string, RoundRobinState> {
|
|
37
37
|
return new BoundedLruMap<string, RoundRobinState>(MAX_ROUND_ROBIN_ENTRIES);
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
/** One session's sticky account: which provider account last served it, and when. */
|
|
41
|
+
export interface StickySession {
|
|
42
|
+
readonly accountId: string;
|
|
43
|
+
readonly lastUsedAtMs: number;
|
|
44
|
+
/** Set only by an explicit operator pin; automatic warm stickiness leaves it absent. */
|
|
45
|
+
readonly explicit?: true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Session → account stickiness per provider, bounded by an LRU. Used by the
|
|
50
|
+
* router to prefer the account that last served a session within the warm
|
|
51
|
+
* window — prompt-cache warmth is worth more than perfect load balance. A
|
|
52
|
+
* session id that is absent, evicted, or past the warm window simply carries
|
|
53
|
+
* no preference.
|
|
54
|
+
*/
|
|
55
|
+
export class StickySessionStore {
|
|
56
|
+
private readonly byProvider = new Map<string, BoundedLruMap<string, StickySession>>();
|
|
57
|
+
|
|
58
|
+
constructor(private readonly maxSessionsPerProvider: number) {}
|
|
59
|
+
|
|
60
|
+
private mapFor(providerId: string): BoundedLruMap<string, StickySession> {
|
|
61
|
+
let map = this.byProvider.get(providerId);
|
|
62
|
+
if (map === undefined) {
|
|
63
|
+
map = new BoundedLruMap<string, StickySession>(this.maxSessionsPerProvider);
|
|
64
|
+
this.byProvider.set(providerId, map);
|
|
65
|
+
}
|
|
66
|
+
return map;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The account last pinned to this session, or undefined. */
|
|
70
|
+
get(providerId: string, sessionId: string | undefined): StickySession | undefined {
|
|
71
|
+
if (sessionId === undefined) return undefined;
|
|
72
|
+
return this.mapFor(providerId).get(sessionId);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Record that `accountId` served `sessionId` now (or pin it explicitly). */
|
|
76
|
+
set(providerId: string, sessionId: string | undefined, accountId: string, explicit = false): void {
|
|
77
|
+
if (sessionId === undefined) return;
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
this.mapFor(providerId).set(sessionId, {
|
|
80
|
+
accountId,
|
|
81
|
+
lastUsedAtMs: now,
|
|
82
|
+
...(explicit ? { explicit: true as const } : {}),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Drop one session's stickiness (e.g. after the account is invalidated). */
|
|
87
|
+
clear(providerId: string, sessionId: string | undefined): void {
|
|
88
|
+
if (sessionId === undefined) return;
|
|
89
|
+
this.mapFor(providerId).delete(sessionId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Total retained sticky entries across providers (for metrics/tests). */
|
|
93
|
+
size(): number {
|
|
94
|
+
let total = 0;
|
|
95
|
+
for (const map of this.byProvider.values()) total += map.size;
|
|
96
|
+
return total;
|
|
97
|
+
}
|
|
98
|
+
}
|