cartethyia 2.4.5 → 2.5.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 +4 -1
- package/README.md +63 -13
- package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
- package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
- package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BzGevuql.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BaRM_7We.js → CliToolsPage-D3xqZbN-.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +1 -0
- package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-DqUGcoH0.js → CustomizationPage-CTTpCb25.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DbhTGfhb.js → LoginPage-DHoB3Agn.js} +1 -1
- package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js +1 -0
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +7 -0
- package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +1 -0
- package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-YxRml8AT.js → SettingsPage-CvjZxHE4.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-D3nza7LH.js +1 -0
- package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-Bol1ASmI.js → SortableList-Dtt12WT1.js} +1 -1
- package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-CbK3hXvU.js +31 -0
- package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +56 -0
- package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BmDbsior.js +1 -0
- package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
- package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
- package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
- package/dist/dashboard/assets/{app-BDJWMThu.js → app-C-Y4oZT0.js} +3 -3
- package/dist/dashboard/assets/app-C-Y4oZT0.js.gz +0 -0
- package/dist/dashboard/assets/arrow-left-CpBxkoxI.js.gz +0 -0
- package/dist/dashboard/assets/arrow-right-Dl5yKnp8.js.gz +0 -0
- package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
- package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
- package/dist/dashboard/assets/backup-file-gdKIlg8U.js.gz +0 -0
- package/dist/dashboard/assets/{badge-BwmwEOgA.js → badge-DpXT4Ixt.js} +1 -1
- package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BKBfKTqn.js → banner-CSAAWLJv.js} +1 -1
- package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
- package/dist/dashboard/assets/brain-Cwed_72z.js.gz +0 -0
- package/dist/dashboard/assets/{card-BTdbRFFg.js → card-DEhRXXw4.js} +1 -1
- package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
- package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
- package/dist/dashboard/assets/chevron-right-BXIy5l3h.js.gz +0 -0
- package/dist/dashboard/assets/circle-alert-CntYHV5v.js.gz +0 -0
- package/dist/dashboard/assets/circle-check-BU3v_mFW.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-B0wBq-RL.js +1 -0
- package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js +1 -0
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
- package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
- package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-iocKA3jr.js → dialog-BvQeBMfP.js} +1 -1
- package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
- package/dist/dashboard/assets/dialog-actions-B1axpEg7.js.gz +0 -0
- package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
- package/dist/dashboard/assets/download-dRFo5_Wu.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CDKOTZdY.js → drawer-CMwPXwQB.js} +1 -1
- package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
- package/dist/dashboard/assets/entry-D4O2229X.js +1 -0
- package/dist/dashboard/assets/entry-D4O2229X.js.gz +0 -0
- package/dist/dashboard/assets/entry-DKvr5Q9K.js +1 -0
- package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
- package/dist/dashboard/assets/entry-FQCejoDY.js +4 -0
- package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
- package/dist/dashboard/assets/eye-C_hawfH4.js.gz +0 -0
- package/dist/dashboard/assets/eye-off-Bk5g2luF.js.gz +0 -0
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/gauge-BbIMy7MB.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-zliLvBwr.js +1 -0
- package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
- package/dist/dashboard/assets/image-BPaouViG.js.gz +0 -0
- package/dist/dashboard/assets/{index-C4hXfAMs.js → index-CXm3FIyt.js} +2 -2
- package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DCejSLlA.js → inline-D1IlZZ8H.js} +1 -1
- package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
- package/dist/dashboard/assets/{input-Bn4_kNav.js → input-BpVZdrbc.js} +1 -1
- package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
- package/dist/dashboard/assets/jsx-runtime-CXUMngI_.js.gz +0 -0
- package/dist/dashboard/assets/key-round-UkavsGxp.js.gz +0 -0
- package/dist/dashboard/assets/layers-2-glBbjl.js.gz +0 -0
- package/dist/dashboard/assets/{layout-Bv-NxqZ5.js → layout-BioLW7gN.js} +1 -1
- package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BLA0sldD.js → list-row-CwVcGAIc.js} +1 -1
- package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
- package/dist/dashboard/assets/{live-YlifPYox.js → live-yfEJFF9T.js} +1 -1
- package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
- package/dist/dashboard/assets/log-in-BmRjy60c.js.gz +0 -0
- package/dist/dashboard/assets/message-circle-CCxGwRvv.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CT-TxSNX.js +1 -0
- package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
- package/dist/dashboard/assets/mutation-BkoLM5Ba.js +1 -0
- package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
- package/dist/dashboard/assets/pencil-768ysRAr.js.gz +0 -0
- package/dist/dashboard/assets/plus-DIdX_g-x.js.gz +0 -0
- package/dist/dashboard/assets/power-off-Yjw9GMJh.js +1 -0
- package/dist/dashboard/assets/power-off-Yjw9GMJh.js.gz +0 -0
- package/dist/dashboard/assets/privacy-mode-GM8bej85.js +1 -0
- package/dist/dashboard/assets/privacy-mode-GM8bej85.js.gz +0 -0
- package/dist/dashboard/assets/providers/agentrouter.svg.gz +0 -0
- package/dist/dashboard/assets/providers/aihubmix.svg.gz +0 -0
- package/dist/dashboard/assets/providers/alibaba-coding-plan.svg.gz +0 -0
- package/dist/dashboard/assets/providers/alibaba.svg.gz +0 -0
- package/dist/dashboard/assets/providers/antigravity.svg.gz +0 -0
- package/dist/dashboard/assets/providers/bai.svg.gz +0 -0
- package/dist/dashboard/assets/providers/blackbox.svg.gz +0 -0
- package/dist/dashboard/assets/providers/claude-code.svg.gz +0 -0
- package/dist/dashboard/assets/providers/cursor.webp +0 -0
- package/dist/dashboard/assets/providers/exa.svg.gz +0 -0
- package/dist/dashboard/assets/providers/gmi-cloud.svg.gz +0 -0
- package/dist/dashboard/assets/providers/inferhub.svg.gz +0 -0
- package/dist/dashboard/assets/providers/muse.svg.gz +0 -0
- package/dist/dashboard/assets/providers/nous-research.svg.gz +0 -0
- package/dist/dashboard/assets/providers/openai-light.svg.gz +0 -0
- package/dist/dashboard/assets/providers/parallel.svg.gz +0 -0
- package/dist/dashboard/assets/providers/tokenharbor.svg.gz +0 -0
- package/dist/dashboard/assets/providers/trae.webp +0 -0
- package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-ROf-g1Wi.js → query-keys-D7wX2vM5.js} +1 -1
- package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
- package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
- package/dist/dashboard/assets/refresh-cw-BqJFk4sU.js.gz +0 -0
- package/dist/dashboard/assets/rotate-ccw-CufcqITi.js.gz +0 -0
- package/dist/dashboard/assets/route-CNSNj9Zm.js.gz +0 -0
- package/dist/dashboard/assets/routing-Cs6jl49L.js +1 -0
- package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
- package/dist/dashboard/assets/scaling-nWE4Cx8f.js.gz +0 -0
- package/dist/dashboard/assets/search-JOKus9rb.js.gz +0 -0
- package/dist/dashboard/assets/select-C0Rer3gF.js +41 -0
- package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
- package/dist/dashboard/assets/shield-check-DVS0Foqh.js.gz +0 -0
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
- package/dist/dashboard/assets/{stack-C1kTcOmP.js → stack-C9_uUjaA.js} +1 -1
- package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
- package/dist/dashboard/assets/{state-DHKdg-pm.js → state-DA0B95UQ.js} +1 -1
- package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DIDeAiz9.js → switch-Cittxy-x.js} +1 -1
- package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js +1 -0
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
- package/dist/dashboard/assets/toast-CNt_jzO4.js +2 -0
- package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
- package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
- package/dist/dashboard/assets/trash-Cppn5c3R.js.gz +0 -0
- package/dist/dashboard/assets/use-clipboard-B0Sy03fE.js.gz +0 -0
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
- package/dist/dashboard/assets/use-reduced-motion-C4B0R6Sz.js.gz +0 -0
- package/dist/dashboard/assets/use-timeout-BwcBBRrT.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-DVmwOKU-.js → useMutation-BQKMcJ1H.js} +1 -1
- package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
- package/dist/dashboard/assets/wrench-B6E1oEuY.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +122 -0
- package/migrations/full-postgres/0013_add_providers_tenant_id_index.sql +22 -0
- package/migrations/full-postgres/0014_add_provider_accounts_tenant_id_index.sql +21 -0
- package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
- package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
- package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
- package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
- package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
- package/migrations/lite-sqlite3/0000_baseline.sql +4 -2
- package/migrations/lite-sqlite3/0013_add_providers_tenant_id_index.sql +4 -0
- package/migrations/lite-sqlite3/0014_add_provider_accounts_tenant_id_index.sql +4 -0
- package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
- package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
- package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
- package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
- package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
- package/package.json +26 -30
- package/scripts/cli/{carte-launcher.cjs → bin/carte.cjs} +8 -8
- package/scripts/cli/{cli-launcher.cjs → bin/cartethyia.cjs} +167 -134
- package/scripts/cli/command/admin.ts +65 -0
- package/scripts/cli/command/console-menu.ts +246 -0
- package/scripts/cli/{doctor.ts → command/doctor.ts} +257 -258
- package/scripts/cli/{setup.ts → command/setup.ts} +8 -12
- package/scripts/cli/command/start.ts +77 -0
- package/scripts/cli/command/tray.ts +165 -0
- package/scripts/cli/command/update.ts +23 -0
- package/scripts/cli/{runtime-state.ts → gateway/drain-token.ts} +78 -78
- package/scripts/cli/gateway/instance-lock.ts +105 -0
- package/scripts/cli/gateway/spawn.ts +173 -0
- package/scripts/cli/gateway/stop.ts +51 -0
- package/scripts/cli/main.ts +94 -0
- package/scripts/cli/support/bun.ts +27 -0
- package/scripts/cli/support/env.ts +99 -0
- package/scripts/cli/support/platform.ts +64 -0
- package/scripts/cli/support/services.ts +88 -0
- package/scripts/cli/support/startup-signal.ts +23 -0
- package/scripts/cli/{version.ts → support/version.ts} +1 -1
- package/scripts/cli/tray/hidden-window.vbs +28 -0
- package/scripts/cli/{tray.ts → tray/index.ts} +3 -3
- package/scripts/cli/{tray.ps1 → tray/windows.ps1} +107 -98
- package/scripts/cli/ui/background.ts +110 -0
- package/src/boot/app.ts +23 -7
- package/src/boot/dependencies.ts +55 -13
- package/src/boot/elysia-typebox-bridge.ts +37 -0
- package/src/boot/lifecycle.ts +2 -0
- package/src/config/cooldowns.ts +138 -138
- package/src/config/providers.ts +31 -5
- package/src/config/runtime.ts +19 -3
- package/src/config/spec.ts +20 -0
- package/src/console/api-keys/contracts.ts +414 -391
- package/src/console/api-keys/routes.ts +880 -870
- package/src/console/auth/account-reset-store.ts +14 -0
- package/src/console/auth/account-reset.ts +3 -2
- package/src/console/auth/setup-service.ts +6 -3
- package/src/console/backup/routes.ts +8 -0
- package/src/console/cli-tools/contracts.ts +6 -6
- package/src/console/console-router.ts +5 -3
- package/src/console/dashboard-assets.ts +81 -11
- package/src/console/domain-registration.ts +9 -2
- package/src/console/observability/contracts.ts +475 -423
- package/src/console/observability/routes.ts +31 -6
- package/src/console/observability/store.ts +1015 -912
- package/src/console/providers/catalog/contracts.ts +31 -0
- package/src/console/providers/catalog/provider-operations.ts +8 -0
- package/src/console/providers/catalog/public-model-store.ts +91 -60
- package/src/console/providers/catalog/store.ts +1532 -1521
- package/src/console/providers/oauth/callback-listener.ts +18 -3
- package/src/console/providers/oauth/routes.ts +38 -15
- package/src/console/quota/account-quota-tenant.ts +9 -6
- package/src/console/quota/refresh.ts +19 -0
- package/src/console/response-compression.ts +69 -2
- package/src/console/routing/pools/probe-verdict.ts +72 -0
- package/src/console/share/share-router.ts +7 -4
- package/src/console/share/share-stats.ts +12 -5
- package/src/console/shared/read-through-cache.ts +133 -7
- package/src/console/updates/repository-updates.ts +39 -34
- package/src/console/updates/routes.ts +162 -4
- package/src/main.ts +3 -0
- package/src/network/http2-bidi.ts +85 -0
- package/src/network/outbound-fetch.ts +87 -24
- package/src/network/pool/agent.ts +478 -460
- package/src/network/pool/resolver.ts +1104 -544
- package/src/network/pool-health-machine.ts +12 -5
- package/src/observability/event-log.ts +29 -8
- package/src/observability/metrics.ts +40 -13
- package/src/observability/payload-capture.ts +344 -324
- package/src/observability/runtime-metrics.ts +7 -0
- package/src/persistence/db-pg.ts +9 -3
- package/src/persistence/db-sqlite.ts +497 -495
- package/src/persistence/health-event-flush.ts +137 -0
- package/src/persistence/migrate.ts +181 -129
- package/src/persistence/schema.ts +1360 -1257
- package/src/persistence/settings-revision.ts +25 -0
- package/src/persistence/shared-cache.ts +217 -0
- package/src/persistence/stores/share-store.ts +556 -528
- package/src/persistence/usage-flush.ts +61 -1
- package/src/protocol/canonical-model.ts +23 -1
- package/src/protocol/response/chat.ts +143 -10
- package/src/protocol/response/codex.ts +9 -75
- package/src/protocol/response/omp-tool-call-markup.ts +221 -0
- package/src/protocol/tool-identity.ts +43 -9
- package/src/providers/authentication/jwt-validator.ts +130 -187
- package/src/providers/authentication/oauth-flow-store.ts +16 -9
- package/src/providers/default-registry.ts +177 -1
- package/src/providers/discovery/catalog/catalog-format.ts +20 -0
- package/src/providers/discovery/models-dev-catalog.ts +39 -22
- package/src/providers/discovery/openai-model-discovery.ts +39 -3
- package/src/providers/discovery/openrouter-free-roster.ts +98 -0
- package/src/providers/discovery/probe-wire.ts +29 -5
- package/src/providers/discovery/probing-service.ts +1022 -1001
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +42 -2
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +88 -0
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +22 -5
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +33 -1
- package/src/providers/integrations/buddy/buddy-identity.ts +46 -0
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +37 -1
- package/src/providers/integrations/buddy/buddy-usage-store.ts +40 -5
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
- package/src/providers/integrations/buddy/codebuddy/shared.ts +115 -73
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +5 -2
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
- package/src/providers/integrations/buddy/workbuddy/quota.ts +172 -159
- package/src/providers/integrations/buddy/workbuddy/shared.ts +200 -171
- package/src/providers/integrations/claude/claude.ts +606 -582
- package/src/providers/integrations/cline/cline-quota.ts +18 -16
- package/src/providers/integrations/cline/cline.ts +539 -468
- package/src/providers/integrations/codex/codex.ts +5 -8
- package/src/providers/integrations/commandcode/commandcode.ts +675 -541
- package/src/providers/integrations/commandcode/discovery.ts +116 -0
- package/src/providers/integrations/commandcode/quota.ts +160 -0
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
- package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
- package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
- package/src/providers/integrations/cursor/cursor-request.ts +471 -0
- package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
- package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
- package/src/providers/integrations/cursor/cursor.ts +960 -0
- package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
- package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
- package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
- package/src/providers/integrations/factory/factory-oauth.ts +7 -11
- package/src/providers/integrations/github/github.ts +4 -4
- package/src/providers/integrations/hermes.ts +79 -8
- package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
- package/src/providers/integrations/kilo/kilo.ts +153 -146
- package/src/providers/integrations/opencode/opencode-go-quota.ts +130 -0
- package/src/providers/integrations/opencode/provider.ts +532 -499
- package/src/providers/integrations/openrouter/provider.ts +139 -281
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +310 -263
- package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
- package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
- package/src/providers/integrations/qoder/qoder.ts +865 -730
- package/src/providers/integrations/shared/install-id.ts +29 -2
- package/src/providers/integrations/trae/trae-catalog.ts +190 -0
- package/src/providers/integrations/trae/trae-headers.ts +149 -0
- package/src/providers/integrations/trae/trae-oauth.ts +773 -0
- package/src/providers/integrations/trae/trae-protocol.ts +400 -0
- package/src/providers/integrations/trae/trae-realm.ts +220 -0
- package/src/providers/integrations/trae/trae.ts +458 -0
- package/src/providers/operations/account-health-service.ts +107 -10
- package/src/providers/operations/client-versions.ts +143 -6
- package/src/providers/operations/model-catalog-cache.ts +18 -3
- package/src/providers/operations/provider-catalog-seeder.ts +95 -86
- package/src/providers/operations/provider-catalog-service.ts +76 -0
- package/src/providers/operations/provider-credential-service.ts +46 -7
- package/src/providers/provider-contracts.ts +76 -0
- package/src/providers/provider-metadata.ts +78 -6
- package/src/providers/provider-model-definition.ts +373 -349
- package/src/security/api-key-auth.ts +126 -8
- package/src/transport/contracts.ts +3 -0
- package/src/transport/dispatch/attempt-finalize.ts +39 -2
- package/src/transport/dispatch/attempt-loop.ts +530 -513
- package/src/transport/dispatch/empty-stream.ts +17 -26
- package/src/transport/dispatch/generation.ts +88 -21
- package/src/transport/dispatch/leases.ts +99 -2
- package/src/transport/dispatch/proxy-request.ts +60 -3
- package/src/transport/dispatch/streaming/finalize.ts +16 -1
- package/src/transport/dispatch/streaming/stream.ts +22 -2
- package/src/transport/dispatch/upstream.ts +19 -1
- package/src/transport/failure-policy.ts +20 -0
- package/src/transport/guards/auth-guard.ts +26 -1
- package/src/transport/guards/body-policy.ts +5 -4
- package/src/transport/request/preparer.ts +103 -0
- package/src/transport/request/state.ts +14 -0
- package/src/transport/routing/plan-gate.ts +45 -0
- package/src/transport/routing/route-catalog.ts +799 -738
- package/src/transport/routing/route-model.ts +52 -1
- package/src/transport/routing/router.ts +51 -0
- package/src/transport/surface/chat/encode.ts +18 -31
- package/src/transport/surface/messages/stream.ts +3 -2
- package/src/transport/translation/tool-repair.ts +37 -16
- package/src/workers/account-identity-sweep.ts +14 -12
- package/src/workers/daily-checkin.ts +62 -0
- package/src/workers/quota-refresh-sweep.ts +27 -2
- package/dist/dashboard/assets/BannedPage-DUrXpp-X.js +0 -1
- package/dist/dashboard/assets/CombosPage-B7ETkfVH.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-AisQzCI1.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-BNflDm_f.js +0 -1
- package/dist/dashboard/assets/ModelPicker-CAiPR5Vd.js +0 -1
- package/dist/dashboard/assets/OverviewPage-C3NIzsDx.js +0 -1
- package/dist/dashboard/assets/ProviderDetailPage-BK8i5B5X.js +0 -3
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-CvCVgm1e.js +0 -1
- package/dist/dashboard/assets/ProxyPage-DjabkuzP.js +0 -7
- package/dist/dashboard/assets/QuotaPage-Dxe0B0iu.js +0 -1
- package/dist/dashboard/assets/SetupPage-BaD4P_jW.js +0 -1
- package/dist/dashboard/assets/StudioPage-CLP4le3U.js +0 -31
- package/dist/dashboard/assets/UsagePage-Djes-Epy.js +0 -56
- package/dist/dashboard/assets/api-keys-CjIGQhe7.js +0 -1
- package/dist/dashboard/assets/app-Cjgo4-Lf.css +0 -2
- package/dist/dashboard/assets/assert-Bwh6DJTK.js +0 -1
- package/dist/dashboard/assets/cli-tools-DccaOfhX.js +0 -1
- package/dist/dashboard/assets/clipboard-button-DQmMikNQ.js +0 -1
- package/dist/dashboard/assets/entry-Cpi0rph1.js +0 -4
- package/dist/dashboard/assets/entry-Dm3PpDCO.js +0 -1
- package/dist/dashboard/assets/entry-keNDZ_Wz.js +0 -1
- package/dist/dashboard/assets/github-badge-BuAJr8oU.js +0 -1
- package/dist/dashboard/assets/model-bans-CVvHZ-qz.js +0 -1
- package/dist/dashboard/assets/mutation-BIizFZav.js +0 -1
- package/dist/dashboard/assets/privacy-mode-CQjbZ3xk.js +0 -1
- package/dist/dashboard/assets/routing-jntw9-Ui.js +0 -1
- package/dist/dashboard/assets/select-CGUuTQmj.js +0 -41
- package/dist/dashboard/assets/settings-BAS6QbmJ.js +0 -1
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
- package/dist/dashboard/assets/terminal-C9mZ4Ugm.js +0 -1
- package/dist/dashboard/assets/toast-DKAdP2mo.js +0 -2
- package/scripts/cli/cli.ts +0 -771
- package/scripts/operations/probe-egress.ts +0 -335
- package/scripts/operations/restart.ts +0 -120
- package/scripts/shared/env.ts +0 -269
- /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.ico +0 -0
- /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.png +0 -0
- /package/scripts/cli/{menu.ts → ui/prompt.ts} +0 -0
- /package/scripts/cli/{wordmark.ts → ui/wordmark.ts} +0 -0
|
@@ -884,10 +884,30 @@ export function isRetryableFailure(error: unknown): boolean {
|
|
|
884
884
|
return classifyUpstreamFailure(error).retryable;
|
|
885
885
|
}
|
|
886
886
|
|
|
887
|
+
/**
|
|
888
|
+
* Minimum wall-clock budget a follow-on attempt needs to be worth starting.
|
|
889
|
+
*
|
|
890
|
+
* A retry repeats the whole pre-dispatch phase — credential resolution,
|
|
891
|
+
* admission, pool slot, request preparation — so it needs at least this much
|
|
892
|
+
* headroom on top of whatever the upstream itself takes. Below this, the
|
|
893
|
+
* attempt is killed by the deadline before it can return anything usable, and
|
|
894
|
+
* starting it replaces the first attempt's real error with a second one's
|
|
895
|
+
* timeout.
|
|
896
|
+
*
|
|
897
|
+
* Owned here so the attempt loop, the empty-stream replay, and the Codex
|
|
898
|
+
* retry budget cannot drift apart; each previously declared its own copy of
|
|
899
|
+
* the constant.
|
|
900
|
+
*/
|
|
901
|
+
export const MIN_RETRY_BUDGET_MS = 15_000;
|
|
902
|
+
|
|
887
903
|
/**
|
|
888
904
|
* Capped exponential backoff with full jitter for a zero-based attempt index.
|
|
889
905
|
* The base and cap are operator-tunable (`CARTETHYIA_FALLBACK_RETRY_BASE_MS`,
|
|
890
906
|
* `CARTETHYIA_FALLBACK_RETRY_CAP_MS`) and default to 100ms / 2000ms.
|
|
907
|
+
*
|
|
908
|
+
* Used for every retry delay, including the empty-stream replay: jitter keeps
|
|
909
|
+
* concurrent clients from resynchronizing, and the cap keeps a provider that
|
|
910
|
+
* returns 200-with-no-events from driving an unbounded wait.
|
|
891
911
|
*/
|
|
892
912
|
export function fallbackRetryDelayMs(attemptIndex: number): number {
|
|
893
913
|
const exp = Math.min(resolveFallbackRetryCapMs(), resolveFallbackRetryBaseMs() * 2 ** attemptIndex);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from "../../observability/logger";
|
|
1
2
|
// API-key authentication guard for `/v1/*`: token resolution, scope check,
|
|
2
3
|
// client-router denylist, and the model-abuse ban gate.
|
|
3
4
|
import { Elysia } from "elysia";
|
|
@@ -39,7 +40,31 @@ export function createApiKeyAuthenticationMiddleware(deps: {
|
|
|
39
40
|
const pathname = fastPathname(request.url);
|
|
40
41
|
if (!pathname.startsWith("/v1/")) return;
|
|
41
42
|
const token = requestToken(request.headers);
|
|
42
|
-
|
|
43
|
+
// A database failure here is not "the key is bad". Left to propagate it
|
|
44
|
+
// surfaced as a 500 whose log line carried the whole statement and its
|
|
45
|
+
// bound parameters — including the key hash — once per request. Under an
|
|
46
|
+
// overloaded database that is a credential leak written at request rate.
|
|
47
|
+
//
|
|
48
|
+
// It is also the wrong answer: the caller cannot fix it by sending a
|
|
49
|
+
// different key, so it is a capacity signal, not an auth one.
|
|
50
|
+
const authorization = await resolveApiKeyAuthorization(deps.db, token).catch((error: unknown) => {
|
|
51
|
+
// Only the error's identity is logged, never its message: a drizzle
|
|
52
|
+
// query error carries the full statement and its bound parameters, and
|
|
53
|
+
// on this path one of those parameters is the key hash.
|
|
54
|
+
// A sanitized stand-in, not the original: a drizzle query error's
|
|
55
|
+
// message is the statement plus its bound parameters, and one of those
|
|
56
|
+
// parameters is the key hash.
|
|
57
|
+
const safe = new Error("[redacted: credential-store query failed]");
|
|
58
|
+
safe.name = error instanceof Error ? error.name : "Error";
|
|
59
|
+
log.error("[auth] key lookup failed; treating as unavailable", safe);
|
|
60
|
+
throw new GatewayError(
|
|
61
|
+
"accounts_unavailable",
|
|
62
|
+
503,
|
|
63
|
+
"The gateway could not verify this API key because its credential store is unavailable. Retry shortly.",
|
|
64
|
+
{},
|
|
65
|
+
"cartethyia",
|
|
66
|
+
);
|
|
67
|
+
});
|
|
43
68
|
if (!authorization)
|
|
44
69
|
throw new GatewayError("invalid_request", 401, "This API key is unknown, revoked, or expired. Issue a new key and send it as: Authorization: Bearer <key>.");
|
|
45
70
|
const decision = createAccessDecision({
|
|
@@ -17,10 +17,11 @@ export function isProxyRequest(request: Request): boolean {
|
|
|
17
17
|
* This list must mirror the routes `app.ts` actually mounts. It drives
|
|
18
18
|
* body-read policy — a JSON route rejects a missing or non-JSON content-type
|
|
19
19
|
* with 415, while an unrouted path is not read at all — so an entry with no
|
|
20
|
-
* handler behind it describes a route the gateway does not serve.
|
|
21
|
-
* entries (`/v1/embeddings`, `/v1/audio/speech`)
|
|
22
|
-
*
|
|
23
|
-
* re-adding one
|
|
20
|
+
* handler behind it describes a route the gateway does not serve. Three such
|
|
21
|
+
* entries (`/v1/embeddings`, `/v1/audio/transcriptions`, `/v1/audio/speech`)
|
|
22
|
+
* were listed for a while with nothing mounted — they advertised a surface
|
|
23
|
+
* that 404d — and the route-table test pinned them absent so re-adding one was
|
|
24
|
+
* a deliberate act. They are mounted now, as passthrough surfaces.
|
|
24
25
|
*
|
|
25
26
|
* The native service routes (`NATIVE_SERVICE_PATHS` — System One, web search,
|
|
26
27
|
* image and video generation) are included: they read a JSON body through the
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
2
2
|
import type { CanonicalRequest, ServiceKind } from "../../protocol/canonical-model";
|
|
3
|
+
import { toolResultParts } from "../../protocol/canonical-model";
|
|
3
4
|
import type { ApiKeyAdmissionService } from "../../security/admission/service";
|
|
4
5
|
import type { RouteCandidate as RouteCandidate, InMemoryRouteSnapshotService, RoutePlan } from "../routing/route-model";
|
|
6
|
+
import type { RouteContinuationAffinity } from "../routing/route-model";
|
|
5
7
|
import { resolveAliasTarget, type RoutingEngine } from "../routing/router";
|
|
6
8
|
import { projectForRoute, routeCapabilitiesFor } from "../translation/capabilities";
|
|
7
9
|
import type { RequiredCapability } from "../translation/capabilities";
|
|
@@ -14,6 +16,7 @@ import { parseThinkingSuffix, withThinkingSuffixIntent } from "../translation/th
|
|
|
14
16
|
import { nativeServicePathFor } from "../contracts";
|
|
15
17
|
import { log } from "../../observability/logger";
|
|
16
18
|
import { BUDDY_PROVIDER_IDS } from "../../providers/provider-metadata";
|
|
19
|
+
import { cursorRunSessions } from "../../providers/integrations/cursor/cursor-session-manager";
|
|
17
20
|
import {
|
|
18
21
|
degradedRequestVariants,
|
|
19
22
|
degradeEncryptedReasoning,
|
|
@@ -32,6 +35,90 @@ function requestNeedsToolRepair(request: CanonicalRequest): boolean {
|
|
|
32
35
|
return false;
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
/** Prefix of a provider-continuation tool-call id the gateway itself minted. */
|
|
39
|
+
const CURSOR_CALL_PREFIX = "call_cursor_";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The provider-continuation tool results this request carries.
|
|
43
|
+
*
|
|
44
|
+
* Only ids the gateway minted (`call_cursor_<run-id>_<exec-id>`) are considered.
|
|
45
|
+
* Any other id belongs to a conversation the client owns; it is not a
|
|
46
|
+
* continuation and must not influence routing.
|
|
47
|
+
*/
|
|
48
|
+
export function continuationCallIds(request: CanonicalRequest): readonly string[] {
|
|
49
|
+
const ids: string[] = [];
|
|
50
|
+
for (const message of request.messages) {
|
|
51
|
+
if (message.role !== "tool" && message.role !== "user") continue;
|
|
52
|
+
for (const part of toolResultParts(message)) {
|
|
53
|
+
if (!part.call_id.startsWith(CURSOR_CALL_PREFIX)) continue;
|
|
54
|
+
if (!ids.includes(part.call_id)) ids.push(part.call_id);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return ids;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolves a request's pending tool results to the one account that owns them.
|
|
62
|
+
*
|
|
63
|
+
* Every pending id must resolve to the *same* live Run and the same account.
|
|
64
|
+
* Ids resolving to different Runs, or to no live Run at all, are refused rather
|
|
65
|
+
* than partially applied: one result cannot be delivered to another account's
|
|
66
|
+
* upstream turn, and a request that cannot be satisfied as a whole must not be
|
|
67
|
+
* dispatched at all.
|
|
68
|
+
*
|
|
69
|
+
* Returns `undefined` when the request carries no continuation ids — the
|
|
70
|
+
* ordinary routing path.
|
|
71
|
+
*/
|
|
72
|
+
export function resolveContinuationAffinity(
|
|
73
|
+
request: CanonicalRequest,
|
|
74
|
+
authorization: ResolvedApiKey,
|
|
75
|
+
): RouteContinuationAffinity | undefined {
|
|
76
|
+
const ids = continuationCallIds(request);
|
|
77
|
+
if (ids.length === 0) return undefined;
|
|
78
|
+
const scope = {
|
|
79
|
+
tenantId: authorization.tenantId,
|
|
80
|
+
apiKeyId: authorization.id,
|
|
81
|
+
};
|
|
82
|
+
let affinity: RouteContinuationAffinity | undefined;
|
|
83
|
+
for (const callId of ids) {
|
|
84
|
+
// The registry resolves by tenant + API key + provider + account, so a call
|
|
85
|
+
// id replayed from another key or tenant resolves nothing.
|
|
86
|
+
const record = lookupPendingCall(callId, scope);
|
|
87
|
+
if (record === undefined) {
|
|
88
|
+
throw new GatewayError(
|
|
89
|
+
"invalid_request",
|
|
90
|
+
400,
|
|
91
|
+
"This tool result refers to a Cursor tool call that is no longer pending. Resend the complete conversation.",
|
|
92
|
+
{ callId },
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (affinity !== undefined && affinity.providerAccountId !== record.providerAccountId) {
|
|
96
|
+
throw new GatewayError(
|
|
97
|
+
"invalid_request",
|
|
98
|
+
400,
|
|
99
|
+
"These tool results belong to different Cursor accounts; resend them one account at a time.",
|
|
100
|
+
{ callIds: [...ids] },
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
affinity = {
|
|
104
|
+
providerId: record.providerId,
|
|
105
|
+
providerAccountId: record.providerAccountId,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return affinity;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Looks up one pending call across every provider that records continuations. */
|
|
112
|
+
function lookupPendingCall(
|
|
113
|
+
callId: string,
|
|
114
|
+
scope: { readonly tenantId: string; readonly apiKeyId: string },
|
|
115
|
+
): { readonly providerId: string; readonly providerAccountId: string } | undefined {
|
|
116
|
+
const record = cursorRunSessions.describePendingCall(callId, scope);
|
|
117
|
+
return record === undefined
|
|
118
|
+
? undefined
|
|
119
|
+
: { providerId: record.providerId, providerAccountId: record.providerAccountId };
|
|
120
|
+
}
|
|
121
|
+
|
|
35
122
|
export interface PreparedProxyRequest {
|
|
36
123
|
readonly canonicalRequest: CanonicalRequest;
|
|
37
124
|
readonly authorization: ResolvedApiKey;
|
|
@@ -42,6 +129,13 @@ export interface PreparedProxyRequest {
|
|
|
42
129
|
readonly degradedCapabilities?: readonly RequiredCapability[];
|
|
43
130
|
/** Snapshot-consistent plan used to reserve each individual attempt. */
|
|
44
131
|
readonly plan: RoutePlan;
|
|
132
|
+
/**
|
|
133
|
+
* The account this request must reach because a provider session is already
|
|
134
|
+
* live on it. Set only when the request carried a live tool result; the
|
|
135
|
+
* dispatch path uses it to borrow the held session instead of acquiring a
|
|
136
|
+
* second account and pool slot.
|
|
137
|
+
*/
|
|
138
|
+
readonly continuationAffinity?: RouteContinuationAffinity;
|
|
45
139
|
readonly estimatedInputTokens: number;
|
|
46
140
|
readonly estimatedOutputTokens: number;
|
|
47
141
|
readonly deadlineMs: number;
|
|
@@ -150,6 +244,13 @@ export class ProxyRequestPreparer {
|
|
|
150
244
|
// requires the `image` capability — it plans like the text request it has
|
|
151
245
|
// become, instead of being routed to a vision route for nothing.
|
|
152
246
|
const requestWithAttachments = dropCorruptAttachments(request).request;
|
|
247
|
+
// Continuation affinity is resolved before anything else: a request
|
|
248
|
+
// carrying a tool result for a live provider session must be planned against
|
|
249
|
+
// the one account that owns that session, before capability derivation or
|
|
250
|
+
// degradation could narrow the candidate set past it. This is also the
|
|
251
|
+
// point where a mismatched or expired call id becomes a 400 the caller can
|
|
252
|
+
// act on, instead of a dispatch that silently starts a new upstream turn.
|
|
253
|
+
const continuationAffinity = resolveContinuationAffinity(request, authorization);
|
|
153
254
|
// Capability-aware routing: derive requirements BEFORE planning so the
|
|
154
255
|
// router filters candidates against snapshot profiles. When nothing
|
|
155
256
|
// supports the full request, degrade (same greedy order) and re-plan
|
|
@@ -176,6 +277,7 @@ export class ProxyRequestPreparer {
|
|
|
176
277
|
variant.required,
|
|
177
278
|
allowCliMappings,
|
|
178
279
|
authorization.cliMappingOwnerId ?? authorization.id,
|
|
280
|
+
continuationAffinity,
|
|
179
281
|
);
|
|
180
282
|
} catch (error) {
|
|
181
283
|
if (error instanceof GatewayError && error.code === "capability_unsupported") continue;
|
|
@@ -303,6 +405,7 @@ export class ProxyRequestPreparer {
|
|
|
303
405
|
eligibleRouteCandidates: eligible,
|
|
304
406
|
degradedCapabilities: degraded,
|
|
305
407
|
plan,
|
|
408
|
+
...(continuationAffinity === undefined ? {} : { continuationAffinity }),
|
|
306
409
|
estimatedInputTokens,
|
|
307
410
|
estimatedOutputTokens,
|
|
308
411
|
deadlineMs: input.deadlineMs,
|
|
@@ -4,6 +4,7 @@ import type { ClientIdentity } from "../../security/abuse";
|
|
|
4
4
|
import type { ResolvedApiKey } from "../../security/api-key-auth";
|
|
5
5
|
import type { PreparedProxyRequest } from "./preparer";
|
|
6
6
|
import type { PayloadCaptureState, TelemetryPayloadCapture } from "../../observability/payload-capture";
|
|
7
|
+
import type { TelemetryPayloadDepth, TelemetryPayloadMode } from "../../console/settings/contracts";
|
|
7
8
|
import { createInFlightRegistry, type InFlightSnapshot } from "./inflight";
|
|
8
9
|
import { log } from "../../observability/logger";
|
|
9
10
|
import { fastPathname } from "../pathname";
|
|
@@ -103,6 +104,19 @@ export interface ProxyRequestState {
|
|
|
103
104
|
* hint at it, and they look identical in both cases.
|
|
104
105
|
*/
|
|
105
106
|
payloadCaptureState?: PayloadCaptureState;
|
|
107
|
+
/**
|
|
108
|
+
* This request's resolved payload-capture preference, memoized on the request.
|
|
109
|
+
*
|
|
110
|
+
* The preference is a tenant setting, so it cannot change between attempts of
|
|
111
|
+
* one request. It was resolved twice — once per attempt to decide whether to
|
|
112
|
+
* tee the provider body, and again on the terminal attempt to decide what to
|
|
113
|
+
* store — so a request with several attempts paid for it several times.
|
|
114
|
+
* Memoized here, the second read is free and the two cannot disagree about
|
|
115
|
+
* whether capture is on.
|
|
116
|
+
*/
|
|
117
|
+
payloadCapturePreference?:
|
|
118
|
+
| { readonly ok: true; readonly mode: TelemetryPayloadMode; readonly depth: TelemetryPayloadDepth }
|
|
119
|
+
| { readonly ok: false };
|
|
106
120
|
/**
|
|
107
121
|
* Set by the dispatch handler synchronously before returning a streaming
|
|
108
122
|
* response. `afterResponse` lifecycle hooks read it to skip finalization
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether an account's reported plan is known to be a free one.
|
|
3
|
+
*
|
|
4
|
+
* CodeBuddy is the reason this exists. It refuses a set of models on a free
|
|
5
|
+
* plan with `11158 "model [X] is not allowed for user type [personal_free_user]"`,
|
|
6
|
+
* and the plan is the only thing that predicts it — the model directory
|
|
7
|
+
* publishes nothing usable. Routing therefore has to keep those models away
|
|
8
|
+
* from accounts that are known to be free, and the only per-account signal is
|
|
9
|
+
* the plan string the quota read reported: `"Free Plan Subscription"`.
|
|
10
|
+
*
|
|
11
|
+
* The match is deliberately narrow and the default is deliberately permissive.
|
|
12
|
+
* Only a plan that names itself free excludes an account. Anything else — a
|
|
13
|
+
* paid plan name, a provider that reports no plan, a plan string that changed
|
|
14
|
+
* shape — leaves the account in rotation, because the alternative is worse:
|
|
15
|
+
* guessing "free" for an unrecognised plan would take a working account out of
|
|
16
|
+
* rotation on the strength of a string we failed to parse. A wrong free match
|
|
17
|
+
* costs one refused request that the cooldown machinery already handles; a
|
|
18
|
+
* wrong paid match silently blackholes a model.
|
|
19
|
+
*/
|
|
20
|
+
const FREE_PLAN_MARKERS: readonly string[] = ["free"];
|
|
21
|
+
|
|
22
|
+
export function isFreePlanAccount(plan: string | null | undefined): boolean {
|
|
23
|
+
if (plan === null || plan === undefined) return false;
|
|
24
|
+
const normalized = plan.trim().toLowerCase();
|
|
25
|
+
if (normalized.length === 0) return false;
|
|
26
|
+
return FREE_PLAN_MARKERS.some((marker) => normalized.includes(marker));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether this candidate can serve a request, given what is known about the
|
|
31
|
+
* model's plan gate and the account's plan.
|
|
32
|
+
*
|
|
33
|
+
* Returns `true` for every case except the one that is known to fail: a model
|
|
34
|
+
* the upstream refuses on a free plan, on an account the upstream reports as
|
|
35
|
+
* free. That pair is a certain refusal, so sending it wastes the request and
|
|
36
|
+
* advances the account's failure streak toward a six-hour park for something
|
|
37
|
+
* that is not the account's fault.
|
|
38
|
+
*/
|
|
39
|
+
export function planAllowsCandidate(candidate: {
|
|
40
|
+
readonly pro_only?: boolean;
|
|
41
|
+
readonly account_plan?: string | null;
|
|
42
|
+
}): boolean {
|
|
43
|
+
if (candidate.pro_only !== true) return true;
|
|
44
|
+
return !isFreePlanAccount(candidate.account_plan);
|
|
45
|
+
}
|