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
|
@@ -12,6 +12,8 @@ import { createAnthropicCompatibleAdapter } from "./integrations/anthropic";
|
|
|
12
12
|
import { defineModel } from "./provider-model-definition";
|
|
13
13
|
import type { ProviderModelDiscovery } from "./discovery/discovery-types";
|
|
14
14
|
import type { ProviderDispatchContext } from "./provider-contracts";
|
|
15
|
+
import type { QoderRealm } from "./integrations/qoder/qoder-realm";
|
|
16
|
+
import type { TraeRealm } from "./integrations/trae/trae-realm";
|
|
15
17
|
import type { WireFamily } from "../protocol/canonical-model";
|
|
16
18
|
import type { CanonicalRequest } from "../protocol/canonical-model";
|
|
17
19
|
|
|
@@ -25,6 +27,106 @@ function configuredProvider(
|
|
|
25
27
|
};
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Qoder's live model list for one realm.
|
|
32
|
+
*
|
|
33
|
+
* The request has to be signed with the account's COSY identity and has to
|
|
34
|
+
* carry the body that was signed — a bare GET is refused — so discovery cannot
|
|
35
|
+
* use the generic bearer helper. It resolves the same identity dispatch uses
|
|
36
|
+
* (PAT exchange or the persisted OAuth state) and then lists against the
|
|
37
|
+
* realm's own gateway hosts.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Trae model discovery for one realm.
|
|
41
|
+
*
|
|
42
|
+
* The listing is per account region: it answers on CN and on the intl region
|
|
43
|
+
* host that serves the account. This returns the static roster when the live
|
|
44
|
+
* call yields nothing, rather than raising, so one unsupported account cannot
|
|
45
|
+
* fail the whole catalog refresh.
|
|
46
|
+
*/
|
|
47
|
+
function traeDiscovery(
|
|
48
|
+
realm: TraeRealm,
|
|
49
|
+
providerId: string,
|
|
50
|
+
): NonNullable<ProviderModuleCapabilities["loadModelDiscovery"]> {
|
|
51
|
+
return async () => async (request: Parameters<ProviderModelDiscovery>[0]) => {
|
|
52
|
+
const { discoverTraeModels } = await import("./integrations/trae/trae");
|
|
53
|
+
const { TRAE_INTL_MODELS, TRAE_CN_MODELS } = await import("./integrations/trae/trae-catalog");
|
|
54
|
+
const secret = request.credential.trim();
|
|
55
|
+
const fetcher = request.fetcher as unknown as typeof fetch | undefined;
|
|
56
|
+
if (secret.length === 0 || fetcher === undefined) {
|
|
57
|
+
return realm === "cn" ? TRAE_CN_MODELS : TRAE_INTL_MODELS;
|
|
58
|
+
}
|
|
59
|
+
let token = secret;
|
|
60
|
+
if (secret.startsWith("{")) {
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(secret) as Record<string, unknown>;
|
|
63
|
+
const found = ["accessToken", "access_token", "token"].find(
|
|
64
|
+
(key) => typeof parsed[key] === "string" && (parsed[key] as string).length > 0,
|
|
65
|
+
);
|
|
66
|
+
if (found !== undefined) token = parsed[found] as string;
|
|
67
|
+
} catch {
|
|
68
|
+
// Use the raw secret as the token.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const region = request.authState?.["region"];
|
|
72
|
+
const rows = await discoverTraeModels({
|
|
73
|
+
realm,
|
|
74
|
+
token,
|
|
75
|
+
uid: request.authState?.["accountId"] as string | undefined ?? "",
|
|
76
|
+
fetcher,
|
|
77
|
+
providerId,
|
|
78
|
+
...(typeof region === "string" ? { region } : {}),
|
|
79
|
+
});
|
|
80
|
+
if (rows.length === 0) return realm === "cn" ? TRAE_CN_MODELS : TRAE_INTL_MODELS;
|
|
81
|
+
const fallback = realm === "cn" ? TRAE_CN_MODELS : TRAE_INTL_MODELS;
|
|
82
|
+
const byId = new Map(fallback.map((model) => [model.modelId, model]));
|
|
83
|
+
return rows.map((row) => {
|
|
84
|
+
const existing = byId.get(row.id);
|
|
85
|
+
if (existing === undefined) {
|
|
86
|
+
// Discovered upstream but absent from the static roster: clone the
|
|
87
|
+
// realm's first row so a new model is routable with the shape this
|
|
88
|
+
// provider serves, carrying the window upstream reported.
|
|
89
|
+
const template = fallback[0];
|
|
90
|
+
if (template === undefined) return undefined;
|
|
91
|
+
return { ...template, modelId: row.id, contextLimit: row.ctx ?? null };
|
|
92
|
+
}
|
|
93
|
+
return row.ctx === undefined ? existing : { ...existing, contextLimit: row.ctx };
|
|
94
|
+
}).filter((model): model is NonNullable<typeof model> => model !== undefined);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function qoderDiscovery(
|
|
99
|
+
realm: QoderRealm,
|
|
100
|
+
providerId: string,
|
|
101
|
+
): NonNullable<ProviderModuleCapabilities["loadModelDiscovery"]> {
|
|
102
|
+
return async () => async (request: Parameters<ProviderModelDiscovery>[0]) => {
|
|
103
|
+
const { discoverQoderModels } = await import("./integrations/qoder/qoder-catalog");
|
|
104
|
+
const { resolveQoderAuth, qoderSignerFor, qoderEncodeBody } = await import("./integrations/qoder/qoder");
|
|
105
|
+
const secret = request.credential.trim();
|
|
106
|
+
if (secret.length === 0) throw new Error("Qoder credential is required");
|
|
107
|
+
const fetcher = request.fetcher as unknown as typeof fetch | undefined;
|
|
108
|
+
// The discovery context carries only the secret plus the non-secret
|
|
109
|
+
// `authState` an OAuth login persisted; the account id lives there too, so
|
|
110
|
+
// an OAuth credential resolves to the same identity dispatch would use.
|
|
111
|
+
const auth = await resolveQoderAuth({
|
|
112
|
+
secret,
|
|
113
|
+
authState: request.authState,
|
|
114
|
+
signal: AbortSignal.timeout(15_000),
|
|
115
|
+
fetcher: (url: string, init: RequestInit) => (fetcher ?? globalThis.fetch)(url, init),
|
|
116
|
+
});
|
|
117
|
+
const signer = qoderSignerFor(auth);
|
|
118
|
+
return discoverQoderModels({
|
|
119
|
+
realm,
|
|
120
|
+
providerId,
|
|
121
|
+
credential: secret,
|
|
122
|
+
headers: (url: string, body: string) => Promise.resolve(signer(url, body)),
|
|
123
|
+
...(fetcher === undefined ? {} : { fetcher }),
|
|
124
|
+
encode: qoderEncodeBody,
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
28
130
|
/**
|
|
29
131
|
* Builds the `loadAdapter` + `loadModelDiscovery` pair every plain bearer
|
|
30
132
|
* provider needs.
|
|
@@ -201,6 +303,29 @@ export const PROVIDER_CAPABILITIES = {
|
|
|
201
303
|
loadAuthentication: oauthCapability(() => import("./integrations/kimi/kimi-oauth"), "kimiCodeOAuthClient", { withRefresher: true }),
|
|
202
304
|
loadQuotaCollector: quotaCapability(() => import("./integrations/kimi/kimi-quota"), "fetchKimiQuota"),
|
|
203
305
|
},
|
|
306
|
+
cursor: {
|
|
307
|
+
// Cursor's Run is a Connect-RPC bidi stream on a *dynamically discovered*
|
|
308
|
+
// agent origin, not the API host the auth and catalog calls use. The path
|
|
309
|
+
// is pinned here so a probe verifies the endpoint dispatch actually calls;
|
|
310
|
+
// the origin itself is resolved per dispatch from GetServerConfig.
|
|
311
|
+
endpointPathsByWireFamily: { chat: "/agent.v1.AgentService/Run" },
|
|
312
|
+
loadAdapter: async () => (await import("./integrations/cursor/cursor")).cursorAdapter,
|
|
313
|
+
loadModels: async () => (await import("./integrations/cursor/cursor-catalog")).CURSOR_MODELS,
|
|
314
|
+
loadAuthentication: oauthCapability(() => import("./integrations/cursor/cursor-auth"), "cursorOAuthClient", {
|
|
315
|
+
withRefresher: true,
|
|
316
|
+
}),
|
|
317
|
+
// No quota collector: no Cursor quota endpoint is established by the donor
|
|
318
|
+
// or the API evidence, and inventing one would publish a balance this
|
|
319
|
+
// gateway cannot measure.
|
|
320
|
+
//
|
|
321
|
+
// The roster is per-account and per-plan, and Cursor declares each model's
|
|
322
|
+
// capabilities, so discovery is the catalog rather than a static list.
|
|
323
|
+
loadModelDiscovery: async () => async ({ credential, fetcher }) =>
|
|
324
|
+
(await import("./integrations/cursor/cursor-catalog")).discoverCursorModels({
|
|
325
|
+
credential,
|
|
326
|
+
...(fetcher === undefined ? {} : { fetcher }),
|
|
327
|
+
}),
|
|
328
|
+
},
|
|
204
329
|
cerebras: {
|
|
205
330
|
endpointPathsByWireFamily: { chat: "/chat/completions" },
|
|
206
331
|
loadAdapter: async () => createApiKeyAdapter((await import("./integrations/cerebras")).CEREBRAS_SPEC),
|
|
@@ -274,6 +399,10 @@ export const PROVIDER_CAPABILITIES = {
|
|
|
274
399
|
loadAdapter: async () => createApiKeyAdapter((await import("./integrations/opencode/provider")).OPENCODE_GO_SPEC),
|
|
275
400
|
loadModels: async () => (await import("./integrations/opencode/provider")).OPENCODE_GO_MODELS,
|
|
276
401
|
loadModelDiscovery: openAIModelDiscovery("opencode-go", { transformBaseUrl: (baseUrl) => `${baseUrl.replace(/\/+$/, "")}/zen/go/v1`, headers: (credential) => ({ authorization: `Bearer ${credential}` }) }),
|
|
402
|
+
// The Go plan publishes a real usage surface; the anonymous free tier has
|
|
403
|
+
// none (it is rate-limited by egress IP, not by a balance), so this is
|
|
404
|
+
// deliberately registered on the keyed tier only.
|
|
405
|
+
loadQuotaCollector: async () => (await import("./integrations/opencode/opencode-go-quota")).fetchOpenCodeGoQuota,
|
|
277
406
|
},
|
|
278
407
|
agentrouter: {
|
|
279
408
|
loadAdapter: async () => (await import("./integrations/agentrouter/agentrouter")).createAgentRouterAdapter(),
|
|
@@ -320,7 +449,10 @@ export const PROVIDER_CAPABILITIES = {
|
|
|
320
449
|
},
|
|
321
450
|
kilo: {
|
|
322
451
|
loadAdapter: async () => (await import("./integrations/kilo/kilo")).kiloAdapter,
|
|
323
|
-
loadModels: async () =>
|
|
452
|
+
loadModels: async () => {
|
|
453
|
+
const spec = await import("./integrations/kilo/kilo");
|
|
454
|
+
return [...spec.KILO_MODELS, ...spec.KILO_FREE_MODELS];
|
|
455
|
+
},
|
|
324
456
|
// No `withRefresher`: Kilo Code has no refresh grant, so this client
|
|
325
457
|
// declares no refresher and the stored token is used exactly as issued.
|
|
326
458
|
// Registering one would make the 401 retry path call a method that always
|
|
@@ -335,6 +467,14 @@ export const PROVIDER_CAPABILITIES = {
|
|
|
335
467
|
commandcode: {
|
|
336
468
|
loadAdapter: async () => (await import("./integrations/commandcode/commandcode")).createCommandCodeAdapter(),
|
|
337
469
|
loadModels: async () => (await import("./integrations/commandcode/commandcode")).COMMANDCODE_MODELS,
|
|
470
|
+
// The Provider API publishes the real roster with each model's wire, so the
|
|
471
|
+
// static seed above is the offline floor rather than the catalog.
|
|
472
|
+
loadModelDiscovery: async () => async (input) =>
|
|
473
|
+
(await import("./integrations/commandcode/discovery")).discoverCommandCodeModels(input),
|
|
474
|
+
loadQuotaCollector: quotaCapability(
|
|
475
|
+
() => import("./integrations/commandcode/quota"),
|
|
476
|
+
"fetchCommandCodeQuota",
|
|
477
|
+
),
|
|
338
478
|
},
|
|
339
479
|
qoder: {
|
|
340
480
|
loadAdapter: async () => (await import("./integrations/qoder/qoder")).createQoderAdapter(),
|
|
@@ -343,6 +483,42 @@ export const PROVIDER_CAPABILITIES = {
|
|
|
343
483
|
// device flow, so the issued token is used exactly as issued until the
|
|
344
484
|
// operator re-runs login — same shape as Kilo Code.
|
|
345
485
|
loadAuthentication: oauthCapability(() => import("./integrations/qoder/qoder-oauth"), "qoderOAuthClient"),
|
|
486
|
+
// Discovery signed by the account's own COSY identity: the model list is
|
|
487
|
+
// per-account and per-realm, and a GET without the signed body is 403.
|
|
488
|
+
loadModelDiscovery: qoderDiscovery("intl", "qoder"),
|
|
489
|
+
},
|
|
490
|
+
// Separate provider, not a region flag: a CN account has its own gateway
|
|
491
|
+
// host, OAuth client id, and model roster, and is refused by the global host.
|
|
492
|
+
qodercn: {
|
|
493
|
+
loadAdapter: async () => (await import("./integrations/qoder/qoder")).createQoderCnAdapter(),
|
|
494
|
+
loadModels: async () => (await import("./integrations/qoder/qoder-catalog")).QODER_CN_MODELS,
|
|
495
|
+
// The CN client, not the intl one: a device login opened on
|
|
496
|
+
// `qoder.com/device/selectAccounts` polled `openapi.qoder.sh`, which a CN
|
|
497
|
+
// account is refused by. `qoderCnOAuthClient` is the same class bound to
|
|
498
|
+
// `QODER_REALMS.cn` — its own login/token/userinfo hosts, client id, and
|
|
499
|
+
// native redirect.
|
|
500
|
+
loadAuthentication: oauthCapability(() => import("./integrations/qoder/qoder-oauth"), "qoderCnOAuthClient"),
|
|
501
|
+
loadModelDiscovery: qoderDiscovery("cn", "qodercn"),
|
|
502
|
+
},
|
|
503
|
+
trae: {
|
|
504
|
+
loadAdapter: async () => (await import("./integrations/trae/trae")).createTraeAdapter(),
|
|
505
|
+
loadModels: async () => (await import("./integrations/trae/trae-catalog")).TRAE_INTL_MODELS,
|
|
506
|
+
// A Trae credential is an access/refresh pair and the refresh token is what
|
|
507
|
+
// keeps it alive, so the refresher is registered rather than optional:
|
|
508
|
+
// without it the account dies at first expiry.
|
|
509
|
+
loadAuthentication: oauthCapability(() => import("./integrations/trae/trae-oauth"), "traeOAuthClient", { withRefresher: true }),
|
|
510
|
+
// Live model list is intl-unsupported: upstream answers `403 no schema
|
|
511
|
+
// found`, so discovery returning nothing is the truthful result and the
|
|
512
|
+
// static roster above stays authoritative.
|
|
513
|
+
loadModelDiscovery: traeDiscovery("intl", "trae"),
|
|
514
|
+
},
|
|
515
|
+
traecn: {
|
|
516
|
+
loadAdapter: async () => (await import("./integrations/trae/trae")).createTraeCnAdapter(),
|
|
517
|
+
loadModels: async () => (await import("./integrations/trae/trae-catalog")).TRAE_CN_MODELS,
|
|
518
|
+
// The CN client, not the intl one: the exchange runs on the CN account
|
|
519
|
+
// host, and a refresh posted to the intl host is refused.
|
|
520
|
+
loadAuthentication: oauthCapability(() => import("./integrations/trae/trae-oauth"), "traeCnOAuthClient", { withRefresher: true }),
|
|
521
|
+
loadModelDiscovery: traeDiscovery("cn", "traecn"),
|
|
346
522
|
},
|
|
347
523
|
inferhub: {
|
|
348
524
|
loadAdapter: async () => (await import("./integrations/inferhub")).createInferhubAdapter(),
|
|
@@ -166,6 +166,16 @@ const decoder = new TextDecoder();
|
|
|
166
166
|
|
|
167
167
|
const FLAG_REASONING = 1;
|
|
168
168
|
const FLAG_TOOL_CALL = 2;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Ceiling on {@link PackedCatalog}'s row-metadata cache.
|
|
172
|
+
*
|
|
173
|
+
* Sized well under the catalog's 8941 rows: a gateway resolves a working set of
|
|
174
|
+
* models, not the whole catalog, and every entry is rebuilt from the blob in a
|
|
175
|
+
* few typed-array reads. Holding more than this trades RAM for reads measured
|
|
176
|
+
* in nanoseconds.
|
|
177
|
+
*/
|
|
178
|
+
export const METADATA_CACHE_MAX_ENTRIES = 2048;
|
|
169
179
|
const SLASH = 0x2f;
|
|
170
180
|
|
|
171
181
|
/** Lowercases and trims an ASCII id into the exact bytes the blob stores. */
|
|
@@ -182,6 +192,12 @@ export function lowerAscii(value: string): Uint8Array {
|
|
|
182
192
|
* Holds typed-array views (zero-copy) and nothing else. Metadata objects are
|
|
183
193
|
* created only for rows a caller actually resolves, and cached, so a long-lived
|
|
184
194
|
* process pays for the handful of ids it queried rather than for all 8090.
|
|
195
|
+
*
|
|
196
|
+
* That cache is **bounded**: an entry is rebuilt from the blob in a few
|
|
197
|
+
* typed-array reads, so holding one at the cost of another is never worth it,
|
|
198
|
+
* and an unbounded memo over 8941 rows is memory the operator pays for at idle
|
|
199
|
+
* whether or not those rows are ever queried again. Bounded LRU, oldest
|
|
200
|
+
* insertion evicted first — the same shape the rest of the codebase uses.
|
|
185
201
|
*/
|
|
186
202
|
export class PackedCatalog {
|
|
187
203
|
private readonly bytes: Uint8Array;
|
|
@@ -399,6 +415,10 @@ export class PackedCatalog {
|
|
|
399
415
|
reasoning: (flags & FLAG_REASONING) !== 0,
|
|
400
416
|
toolCall: (flags & FLAG_TOOL_CALL) !== 0,
|
|
401
417
|
};
|
|
418
|
+
if (this.metadataCache.size >= METADATA_CACHE_MAX_ENTRIES) {
|
|
419
|
+
const oldest = this.metadataCache.keys().next().value;
|
|
420
|
+
if (oldest !== undefined) this.metadataCache.delete(oldest);
|
|
421
|
+
}
|
|
402
422
|
this.metadataCache.set(index, metadata);
|
|
403
423
|
return metadata;
|
|
404
424
|
}
|
|
@@ -134,25 +134,38 @@ const UNKNOWN_COST: ModelsDevModelCost = {
|
|
|
134
134
|
};
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
|
-
*
|
|
137
|
+
* The packed catalog, decompressed on first use and memoized.
|
|
138
138
|
*
|
|
139
139
|
* Brotli + base64, not `JSON.parse`: the blob is columnar, so this allocates
|
|
140
140
|
* one buffer and takes typed-array views over it rather than materialising
|
|
141
141
|
* 8900 objects. `CATALOG_BLOB_BYTES` is the decompressed length, checked here
|
|
142
|
-
* so a consumer paired with a half-regenerated blob fails
|
|
143
|
-
* resolving a truncated catalog.
|
|
142
|
+
* so a consumer paired with a half-regenerated blob fails on first access
|
|
143
|
+
* rather than resolving a truncated catalog.
|
|
144
|
+
*
|
|
145
|
+
* The decode is lazy on purpose. It is ~105 KB of base64 that expands to a
|
|
146
|
+
* much larger buffer, and it used to run at module load — so every process,
|
|
147
|
+
* including the ones that never resolve a model (a CLI subcommand, a migration
|
|
148
|
+
* run, a test that only touches the console), paid it on the boot path.
|
|
149
|
+
* Deferring it moves that cost to the first catalog lookup and pays it once.
|
|
144
150
|
*/
|
|
145
|
-
|
|
146
|
-
if (CATALOG_BYTES.byteLength !== CATALOG_BLOB_BYTES) {
|
|
147
|
-
throw new Error(
|
|
148
|
-
`The packed model catalog is ${CATALOG_BYTES.byteLength} B, expected ` +
|
|
149
|
-
`${CATALOG_BLOB_BYTES} B — regenerate it.`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
151
|
+
let catalogCache: PackedCatalog | undefined;
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
function catalog(): PackedCatalog {
|
|
154
|
+
const cached = catalogCache;
|
|
155
|
+
if (cached !== undefined) return cached;
|
|
156
|
+
const bytes = brotliDecompressSync(Buffer.from(CATALOG_BLOB_BROTLI_BASE64, "base64"));
|
|
157
|
+
if (bytes.byteLength !== CATALOG_BLOB_BYTES) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`The packed model catalog is ${bytes.byteLength} B, expected ` +
|
|
160
|
+
`${CATALOG_BLOB_BYTES} B — regenerate it.`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
// `PackedCatalog` copies into a 4-byte-aligned buffer of its own, so the
|
|
164
|
+
// decompressed buffer is dropped here rather than held beside the copy.
|
|
165
|
+
const packed = new PackedCatalog(bytes);
|
|
166
|
+
catalogCache = packed;
|
|
167
|
+
return packed;
|
|
168
|
+
}
|
|
156
169
|
|
|
157
170
|
/**
|
|
158
171
|
* Resolves model metadata and pricing exclusively from the packed base catalog
|
|
@@ -160,9 +173,13 @@ const CATALOG = new PackedCatalog(CATALOG_BYTES);
|
|
|
160
173
|
*/
|
|
161
174
|
export class ModelsDevCatalog {
|
|
162
175
|
/** When the generator fetched the upstream catalogs. */
|
|
163
|
-
|
|
176
|
+
get fetchedAt(): string {
|
|
177
|
+
return catalog().fetchedAt;
|
|
178
|
+
}
|
|
164
179
|
/** Every provider key the catalog files. */
|
|
165
|
-
|
|
180
|
+
get providers(): readonly string[] {
|
|
181
|
+
return catalog().providers;
|
|
182
|
+
}
|
|
166
183
|
|
|
167
184
|
/**
|
|
168
185
|
* Metadata for an exact `(providerIndex, idLower)` pair, or `undefined`.
|
|
@@ -172,19 +189,19 @@ export class ModelsDevCatalog {
|
|
|
172
189
|
* matches exactly keeps winning.
|
|
173
190
|
*/
|
|
174
191
|
private exactMetadata(providerLower: string, idLower: Uint8Array): ModelsDevModelMetadata | undefined {
|
|
175
|
-
const direct =
|
|
176
|
-
if (direct >= 0) return
|
|
192
|
+
const direct = catalog().exactIndex(catalog().providerIndex(providerLower), idLower);
|
|
193
|
+
if (direct >= 0) return catalog().metadataAt(direct);
|
|
177
194
|
const mapped = CATALOG_PROVIDER_IDS[providerLower];
|
|
178
195
|
if (mapped === undefined) return undefined;
|
|
179
|
-
return
|
|
196
|
+
return catalog().metadataAt(catalog().exactIndex(catalog().providerIndex(mapped), idLower));
|
|
180
197
|
}
|
|
181
198
|
|
|
182
199
|
/** Metadata rows whose *bare* id equals `bare`, in catalog order. */
|
|
183
200
|
private metadataForBare(bare: Uint8Array): ModelsDevModelMetadata[] {
|
|
184
|
-
const [start, end] =
|
|
201
|
+
const [start, end] = catalog().bareRange(bare);
|
|
185
202
|
const out: ModelsDevModelMetadata[] = [];
|
|
186
203
|
for (let position = start; position < end; position += 1) {
|
|
187
|
-
const metadata =
|
|
204
|
+
const metadata = catalog().metadataAt(catalog().bareOrderAt(position));
|
|
188
205
|
if (metadata !== undefined) out.push(metadata);
|
|
189
206
|
}
|
|
190
207
|
return out;
|
|
@@ -327,8 +344,8 @@ export class ModelsDevCatalog {
|
|
|
327
344
|
return own;
|
|
328
345
|
}
|
|
329
346
|
const idLower = lowerAscii(modelId);
|
|
330
|
-
const row =
|
|
331
|
-
const global = row >= 0 ?
|
|
347
|
+
const row = catalog().globalCostRow(bareBytes(idLower));
|
|
348
|
+
const global = row >= 0 ? catalog().rowCost(row) : undefined;
|
|
332
349
|
if (global !== undefined && global.pricing_model !== "unknown") return global;
|
|
333
350
|
return own ?? global ?? UNKNOWN_COST;
|
|
334
351
|
}
|
|
@@ -54,10 +54,18 @@ export async function fetchOpenAICompatibleModels(
|
|
|
54
54
|
modalityContextLimit(entry);
|
|
55
55
|
const contextWindow = parsedContext ?? fallback?.contextLimit ?? 200_000;
|
|
56
56
|
|
|
57
|
+
// `max_completion_tokens` first: it is the name an OpenAI-shaped catalog
|
|
58
|
+
// uses, and it is the name this gateway's own `/v1/models` emits. A
|
|
59
|
+
// reader that knew only `max_output_tokens`/`maxTokens` saw none of them
|
|
60
|
+
// and fell through to the 64_192 default, so every model on such an
|
|
61
|
+
// upstream was advertised at the same wrong ceiling no matter what the
|
|
62
|
+
// upstream reported.
|
|
57
63
|
const parsedOutput =
|
|
64
|
+
positiveNumber(entry.max_completion_tokens) ??
|
|
58
65
|
positiveNumber(entry.max_output_tokens) ??
|
|
59
66
|
positiveNumber((entry as Record<string, unknown>).max_output_tokens) ??
|
|
60
67
|
positiveNumber(entry.maxTokens) ??
|
|
68
|
+
positiveNumber((entry as Record<string, unknown>).completion_token_limit) ??
|
|
61
69
|
modalityOutputLimit(entry);
|
|
62
70
|
const maxOutputTokens = parsedOutput ?? fallback?.outputLimit ?? 64_192;
|
|
63
71
|
const outputTokens = Math.min(maxOutputTokens, contextWindow);
|
|
@@ -74,7 +82,14 @@ export async function fetchOpenAICompatibleModels(
|
|
|
74
82
|
// the upstream declares for the model it serves. A catalog that declares
|
|
75
83
|
// it as an `input_modalities[]` array instead of a `modality` string was
|
|
76
84
|
// read as text-only, so a vision model lost image input.
|
|
77
|
-
|
|
85
|
+
// `capabilities.input` is a third spelling — an array of modality names,
|
|
86
|
+
// used by OpenAI-shaped catalogs including this gateway's own. Read
|
|
87
|
+
// alongside the other two so a vision model is not downgraded to
|
|
88
|
+
// text-only, which then drops image parts before they reach the upstream.
|
|
89
|
+
const hasImage =
|
|
90
|
+
rawModality.includes("image") ||
|
|
91
|
+
modalityImageInput(entry) ||
|
|
92
|
+
capabilityImageInput(entry);
|
|
78
93
|
const responsesWire = isResponsesNativeModelId(id);
|
|
79
94
|
byId.set(id, {
|
|
80
95
|
modelId: id,
|
|
@@ -83,8 +98,14 @@ export async function fetchOpenAICompatibleModels(
|
|
|
83
98
|
contextLimit: contextWindow,
|
|
84
99
|
outputLimit: outputTokens,
|
|
85
100
|
modalities: { input: hasImage ? ["text", "image"] : ["text"], output: ["text"] },
|
|
86
|
-
reasoning
|
|
87
|
-
|
|
101
|
+
// Upstream declares reasoning, so believe it rather than pinning false:
|
|
102
|
+
// a reasoning model advertised as non-reasoning is routed without the
|
|
103
|
+
// thinking budget it needs, and the caller's `reasoning_effort` is
|
|
104
|
+
// dropped as unsupported.
|
|
105
|
+
reasoning: entry.reasoning === true,
|
|
106
|
+
// Same for tool calling — an upstream that names a model as
|
|
107
|
+
// non-tool-capable must not have tools routed to it.
|
|
108
|
+
toolCall: entry.tool_call === false ? false : true,
|
|
88
109
|
cost,
|
|
89
110
|
});
|
|
90
111
|
}
|
|
@@ -150,6 +171,21 @@ function modalityImageInput(entry: Record<string, unknown>): boolean {
|
|
|
150
171
|
|
|
151
172
|
|
|
152
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Image input from a `capabilities.input` array.
|
|
176
|
+
*
|
|
177
|
+
* The field is a plain list of modality names (`["text","image"]`), not the
|
|
178
|
+
* structured `input_modalities[]` objects the sibling reader expects. A catalog
|
|
179
|
+
* that spells it this way was read as text-only.
|
|
180
|
+
*/
|
|
181
|
+
function capabilityImageInput(entry: Record<string, unknown>): boolean {
|
|
182
|
+
const capabilities = entry.capabilities;
|
|
183
|
+
if (!isRecord(capabilities)) return false;
|
|
184
|
+
const inputs = capabilities.input;
|
|
185
|
+
if (!Array.isArray(inputs)) return false;
|
|
186
|
+
return inputs.some((name) => typeof name === "string" && name.toLowerCase().includes("image"));
|
|
187
|
+
}
|
|
188
|
+
|
|
153
189
|
function extractEntries(payload: unknown): readonly Record<string, unknown>[] | null {
|
|
154
190
|
if (Array.isArray(payload)) return payload.filter(isRecord);
|
|
155
191
|
if (typeof payload !== "object" || payload === null) return null;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The free tier shared by the OpenRouter-backed providers.
|
|
3
|
+
*
|
|
4
|
+
* Cline, Kilo Code and OpenRouter serve one model catalog: the same 469 ids,
|
|
5
|
+
* and the same 17 of them carrying a `:free` suffix. Verified against all three
|
|
6
|
+
* live listings — every id below is present under `openrouter`, `kilo` and
|
|
7
|
+
* `cline` — so this roster is a catalog fact, not a per-provider guess, and it
|
|
8
|
+
* lives in one place rather than being restated three times.
|
|
9
|
+
*
|
|
10
|
+
* The limits, modalities and capability flags are read from OpenRouter's own
|
|
11
|
+
* `/api/v1/models`, which states `context_length`,
|
|
12
|
+
* `top_provider.max_completion_tokens`, `architecture.input_modalities` and
|
|
13
|
+
* `supported_parameters`. That is the serving catalog's declaration, so these
|
|
14
|
+
* are measurements rather than defaults.
|
|
15
|
+
*
|
|
16
|
+
* This is a floor, not the authority: each provider also discovers live, and a
|
|
17
|
+
* row the listing carries that this roster does not is still found there. The
|
|
18
|
+
* roster exists so a fresh install has a usable free tier before any fetch, and
|
|
19
|
+
* so the free section is populated even when discovery is unavailable.
|
|
20
|
+
*/
|
|
21
|
+
import { defineModel } from "../provider-model-definition";
|
|
22
|
+
import type { ModelDefinition } from "../provider-model-definition";
|
|
23
|
+
|
|
24
|
+
export interface FreeRosterOptions {
|
|
25
|
+
readonly providerId: string;
|
|
26
|
+
/** The wire suffix that completes the provider's base URL. */
|
|
27
|
+
readonly endpoint: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface FreeRow {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly ctx: number;
|
|
33
|
+
readonly out: number;
|
|
34
|
+
readonly vision?: boolean;
|
|
35
|
+
/** `supported_parameters` omits `tools` on the safety classifier only. */
|
|
36
|
+
readonly toolCall?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The 17 `:free` ids, with the limits the catalog states for each.
|
|
41
|
+
*
|
|
42
|
+
* Every row declares `reasoning`: `supported_parameters` lists it on all
|
|
43
|
+
* seventeen. `toolCall` is `true` unless stated otherwise, because a row that
|
|
44
|
+
* advertises tools must keep them — a false `toolCall` makes the preflight
|
|
45
|
+
* strip `tools` from the request, which is a wrong capability rather than a
|
|
46
|
+
* conservative one.
|
|
47
|
+
*/
|
|
48
|
+
const FREE_ROWS: readonly FreeRow[] = [
|
|
49
|
+
{ id: "cohere/north-mini-code:free", ctx: 256_000, out: 64_000 },
|
|
50
|
+
{ id: "dots-studio/dots-3-note-preview:free", ctx: 512_000, out: 460_800, vision: true },
|
|
51
|
+
{ id: "google/gemma-4-26b-a4b-it:free", ctx: 262_144, out: 32_768, vision: true },
|
|
52
|
+
{ id: "google/gemma-4-31b-it:free", ctx: 262_144, out: 32_768, vision: true },
|
|
53
|
+
{ id: "liquid/lfm-2.5-2.6b:free", ctx: 65_536, out: 8_192 },
|
|
54
|
+
{ id: "minimax/minimax-m3.1-flash:free", ctx: 1_000_000, out: 524_288, vision: true },
|
|
55
|
+
{
|
|
56
|
+
id: "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
|
|
57
|
+
ctx: 256_000,
|
|
58
|
+
out: 65_536,
|
|
59
|
+
vision: true,
|
|
60
|
+
},
|
|
61
|
+
{ id: "nvidia/nemotron-3-super-120b-a12b:free", ctx: 262_144, out: 235_929 },
|
|
62
|
+
{ id: "nvidia/nemotron-3-ultra-550b-a55b:free", ctx: 1_000_000, out: 65_536 },
|
|
63
|
+
{
|
|
64
|
+
id: "nvidia/nemotron-3.5-content-safety:free",
|
|
65
|
+
ctx: 128_000,
|
|
66
|
+
out: 8_192,
|
|
67
|
+
vision: true,
|
|
68
|
+
toolCall: false,
|
|
69
|
+
},
|
|
70
|
+
{ id: "nvidia/nemotron-3.5-lightning:free", ctx: 1_000_000, out: 65_536 },
|
|
71
|
+
{ id: "nvidia/nemotron-3.5-super-vl:free", ctx: 1_000_000, out: 131_072, vision: true },
|
|
72
|
+
{ id: "poolside/laguna-s-2.1:free", ctx: 262_144, out: 32_768 },
|
|
73
|
+
{ id: "poolside/laguna-xs-2.1:free", ctx: 262_144, out: 32_768 },
|
|
74
|
+
{ id: "thinkingmachines/inkling-small:free", ctx: 1_048_576, out: 262_144, vision: true },
|
|
75
|
+
{ id: "thinkingmachines/inkling:free", ctx: 1_048_576, out: 262_144, vision: true },
|
|
76
|
+
{ id: "xingchen-agi/xing4.0-29b-a4b:free", ctx: 262_144, out: 235_929 },
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
/** Builds this roster for one provider, since each spells its own endpoint. */
|
|
80
|
+
export function openrouterFreeRoster(options: FreeRosterOptions): readonly ModelDefinition[] {
|
|
81
|
+
return FREE_ROWS.map((row) =>
|
|
82
|
+
defineModel({
|
|
83
|
+
id: row.id,
|
|
84
|
+
providerId: options.providerId,
|
|
85
|
+
wireFamily: "chat",
|
|
86
|
+
endpoint: options.endpoint,
|
|
87
|
+
ctx: row.ctx,
|
|
88
|
+
out: row.out,
|
|
89
|
+
vision: row.vision ?? false,
|
|
90
|
+
reasoning: true,
|
|
91
|
+
toolCall: row.toolCall ?? true,
|
|
92
|
+
// `free` is the billing statement and `freeTier` the catalog one. The
|
|
93
|
+
// `:free` suffix makes both true, and the model list groups on the tier.
|
|
94
|
+
free: true,
|
|
95
|
+
freeTier: true,
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -172,17 +172,41 @@ export function staticEndpointForWire(
|
|
|
172
172
|
return catalog.get(providerId)?.find((def) => def.wireFamily === wireFamily)?.endpointPath;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Compiled-pattern memo.
|
|
177
|
+
*
|
|
178
|
+
* `matchModelWireFamily` runs per model on every discovery pass and on every
|
|
179
|
+
* manual registration, and it used to compile each rule's `RegExp` inside that
|
|
180
|
+
* loop — so the same handful of patterns were recompiled once per model per
|
|
181
|
+
* pass. Keyed by the pattern source, which is exactly what `new RegExp(source)`
|
|
182
|
+
* derives from.
|
|
183
|
+
*
|
|
184
|
+
* A pattern that fails to compile is not memoized; the tolerance stays at read
|
|
185
|
+
* time because an invalid pattern is rejected at write time, and a stored one
|
|
186
|
+
* must not throw during a listing.
|
|
187
|
+
*/
|
|
188
|
+
const compiledPatternCache = new Map<string, RegExp>();
|
|
189
|
+
|
|
190
|
+
function compilePattern(pattern: string): RegExp | undefined {
|
|
191
|
+
const cached = compiledPatternCache.get(pattern);
|
|
192
|
+
if (cached !== undefined) return cached;
|
|
193
|
+
try {
|
|
194
|
+
const compiled = new RegExp(pattern);
|
|
195
|
+
compiledPatternCache.set(pattern, compiled);
|
|
196
|
+
return compiled;
|
|
197
|
+
} catch {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
175
202
|
function matchModelWireFamily(
|
|
176
203
|
modelId: string,
|
|
177
204
|
rules: ReadonlyArray<{ pattern: string; wire_family: WireFamily }> | undefined,
|
|
178
205
|
): WireFamily | undefined {
|
|
179
206
|
if (!rules?.length) return undefined;
|
|
180
207
|
for (const rule of rules) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
} catch {
|
|
184
|
-
// Invalid pattern is rejected at write time; be tolerant at read time.
|
|
185
|
-
}
|
|
208
|
+
const pattern = compilePattern(rule.pattern);
|
|
209
|
+
if (pattern !== undefined && pattern.test(modelId)) return rule.wire_family as WireFamily;
|
|
186
210
|
}
|
|
187
211
|
return undefined;
|
|
188
212
|
}
|