cartethyia 2.4.6 → 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/README.md +45 -13
- package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
- package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-Bcy150Jo.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DyNXefph.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-CATpAEOz.js → ConsoleLogPage-u6W1UXSc.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-D6jEU7nL.js → CustomizationPage-CTTpCb25.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-CZkpn0a7.js → HealthEventsModal-C5XpWlvy.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BJlMVN4U.js → LoginPage-DHoB3Agn.js} +1 -1
- package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-Br3CXEau.js → ModelPicker-oQ-SDXp8.js} +1 -1
- 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-BHW4Zt06.js → SettingsPage-CvjZxHE4.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-VOYC_lIa.js → SetupPage-D3nza7LH.js} +1 -1
- package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-Dtt12WT1.js +1 -0
- package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-BNIDLiU6.js → StudioPage-CbK3hXvU.js} +4 -4
- package/dist/dashboard/assets/StudioPage-CbK3hXvU.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-BETfaMYZ.js → api-keys-BmDbsior.js} +1 -1
- 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/assert-BHfaei-X.js +1 -0
- package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
- package/dist/dashboard/assets/{badge-BTyGk_DL.js → badge-DpXT4Ixt.js} +1 -1
- package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
- package/dist/dashboard/assets/{banner-bujTWYM4.js → banner-CSAAWLJv.js} +1 -1
- package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
- package/dist/dashboard/assets/{card-B0Bnx5CA.js → card-DEhRXXw4.js} +1 -1
- package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-DE2ug812.js → cli-tools-B0wBq-RL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-R_rArH8S.js → clipboard-button-DOo6EU8O.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BWIP_EvW.js → dialog-BvQeBMfP.js} +1 -1
- package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BKcgVugF.js → drawer-CMwPXwQB.js} +1 -1
- package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CcWkGR4a.js → entry-D4O2229X.js} +1 -1
- package/dist/dashboard/assets/{entry-CcWkGR4a.js.gz → entry-D4O2229X.js.gz} +0 -0
- package/dist/dashboard/assets/{entry-kTiTFrvh.js → entry-DKvr5Q9K.js} +1 -1
- 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/{github-badge-Booc5mNe.js → github-badge-zliLvBwr.js} +1 -1
- package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
- package/dist/dashboard/assets/{index-D70pChfn.js → index-CXm3FIyt.js} +2 -2
- package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
- package/dist/dashboard/assets/{inline-D1TCqM2D.js → inline-D1IlZZ8H.js} +1 -1
- package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
- package/dist/dashboard/assets/{input-B9oyU93y.js → input-BpVZdrbc.js} +1 -1
- package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BArzXirQ.js → layout-BioLW7gN.js} +1 -1
- package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BVZzaoHF.js → list-row-CwVcGAIc.js} +1 -1
- package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
- package/dist/dashboard/assets/{live-EmuIayqp.js → live-yfEJFF9T.js} +1 -1
- package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CSPQ9xB0.js → model-bans-CT-TxSNX.js} +1 -1
- package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
- package/dist/dashboard/assets/providers/cursor.webp +0 -0
- package/dist/dashboard/assets/providers/trae.webp +0 -0
- package/dist/dashboard/assets/{routing-BExgxxBU.js → routing-Cs6jl49L.js} +1 -1
- package/dist/dashboard/assets/routing-Cs6jl49L.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/{stack-Ca5HxI5i.js → stack-C9_uUjaA.js} +1 -1
- package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
- package/dist/dashboard/assets/{state-CzUPfLXo.js → state-DA0B95UQ.js} +1 -1
- package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DOUIky_i.js → switch-Cittxy-x.js} +1 -1
- package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
- package/dist/dashboard/assets/{toast-C76WrMhc.js → toast-CNt_jzO4.js} +1 -1
- package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-D87FZwH0.js → useMutation-BQKMcJ1H.js} +1 -1
- package/dist/dashboard/assets/useMutation-BQKMcJ1H.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 +51 -52
- 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/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 +3 -2
- package/scripts/cli/command/console-menu.ts +30 -11
- package/scripts/cli/command/start.ts +77 -13
- package/scripts/cli/command/update.ts +1 -1
- package/scripts/cli/gateway/instance-lock.ts +105 -0
- package/scripts/cli/gateway/spawn.ts +173 -101
- package/scripts/cli/ui/background.ts +110 -49
- package/src/boot/app.ts +20 -6
- package/src/boot/dependencies.ts +19 -0
- 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 +5 -5
- package/src/config/runtime.ts +19 -3
- package/src/config/spec.ts +9 -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/domain-registration.ts +9 -2
- package/src/console/observability/contracts.ts +475 -455
- package/src/console/observability/store.ts +1015 -952
- package/src/console/providers/catalog/contracts.ts +31 -0
- package/src/console/providers/catalog/provider-operations.ts +8 -0
- package/src/console/providers/catalog/store.ts +1532 -1525
- package/src/console/providers/oauth/callback-listener.ts +18 -3
- package/src/console/providers/oauth/routes.ts +38 -15
- package/src/console/quota/refresh.ts +19 -0
- 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 +1 -26
- 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/payload-capture.ts +344 -324
- 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 -1295
- 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/omp-tool-call-markup.ts +221 -0
- package/src/providers/default-registry.ts +173 -1
- package/src/providers/discovery/openai-model-discovery.ts +39 -3
- package/src/providers/discovery/openrouter-free-roster.ts +98 -0
- package/src/providers/discovery/probing-service.ts +1022 -1001
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +39 -0
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +18 -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 +15 -6
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
- package/src/providers/integrations/buddy/workbuddy/shared.ts +3 -3
- 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/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/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/provider.ts +532 -538
- 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 -257
- 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/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 +133 -6
- package/src/providers/operations/provider-catalog-seeder.ts +95 -86
- 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 +32 -1
- package/src/transport/dispatch/attempt-loop.ts +530 -515
- 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/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 -777
- package/src/transport/routing/route-model.ts +52 -1
- package/src/transport/routing/router.ts +51 -0
- package/dist/dashboard/assets/BannedPage-QhvbJB9o.js +0 -1
- package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +0 -1
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js +0 -7
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +0 -1
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js +0 -1
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BAFaSDQV.js +0 -56
- package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
- package/dist/dashboard/assets/app-BLCvGdzb.css +0 -2
- package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
- package/dist/dashboard/assets/assert-7J6_LVD3.js +0 -1
- package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
- package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
- package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
- package/dist/dashboard/assets/card-B0Bnx5CA.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-R_rArH8S.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BWIP_EvW.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
- package/dist/dashboard/assets/entry-7dpgvpJ3.js +0 -4
- package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
- package/dist/dashboard/assets/entry-kTiTFrvh.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
- package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
- package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
- package/dist/dashboard/assets/input-B9oyU93y.js.gz +0 -0
- package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
- package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
- package/dist/dashboard/assets/live-EmuIayqp.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CSPQ9xB0.js.gz +0 -0
- package/dist/dashboard/assets/routing-BExgxxBU.js.gz +0 -0
- package/dist/dashboard/assets/select-C9mMoEpJ.js +0 -41
- package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
- package/dist/dashboard/assets/settings-C4_Gn_wq.js +0 -1
- package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
- package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
- package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOUIky_i.js.gz +0 -0
- package/dist/dashboard/assets/toast-C76WrMhc.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-D87FZwH0.js.gz +0 -0
- package/src/network/tls/bun-client-hello.ts +0 -112
- /package/dist/dashboard/assets/{app-Cjq7nrwu.js → app-C-Y4oZT0.js} +0 -0
- /package/dist/dashboard/assets/{app-Cjq7nrwu.js.gz → app-C-Y4oZT0.js.gz} +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Stable, non-secret installation identity for the Cursor integration.
|
|
2
|
+
//
|
|
3
|
+
// Cursor keys device identity off the ids embedded in `x-cursor-checksum` and
|
|
4
|
+
// treats every new machineId as a separate device ("too many connections from
|
|
5
|
+
// different devices"), so the fingerprint MUST be stable across restarts and
|
|
6
|
+
// across processes on one host.
|
|
7
|
+
//
|
|
8
|
+
// The donor derived it from OS identifiers (IOPlatformUUID / MAC address) by
|
|
9
|
+
// shelling out to `ioreg` / `reg query`. Cartethyia does not run provider code
|
|
10
|
+
// that spawns a shell per dispatch: the identity is instead derived from
|
|
11
|
+
// Cartethyia's own persisted installation id, which is stable for one
|
|
12
|
+
// installation and is never derived from a credential. A credential-derived id
|
|
13
|
+
// would rotate whenever an operator re-authenticated, which is exactly the
|
|
14
|
+
// "too many devices" symptom the stability exists to avoid.
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { getOrCreateInstallId } from "../shared/install-id";
|
|
17
|
+
|
|
18
|
+
/** One process-lifetime cache: the id is stable, so it is read once. */
|
|
19
|
+
let cached: { machineId: string; macMachineId: string } | undefined;
|
|
20
|
+
let inFlight: Promise<{ machineId: string; macMachineId: string }> | undefined;
|
|
21
|
+
|
|
22
|
+
function sha256Hex(value: string): string {
|
|
23
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Stable device fingerprints for the `x-cursor-checksum` header.
|
|
28
|
+
*
|
|
29
|
+
* `machineId` and `macMachineId` are two distinct hashes of one stable install
|
|
30
|
+
* id, mirroring the donor's two-id shape without reading a MAC address (which
|
|
31
|
+
* would make the fingerprint depend on hardware that a container may not have).
|
|
32
|
+
* Neither value is logged: they are device identity, not diagnostics.
|
|
33
|
+
*/
|
|
34
|
+
export async function resolveCursorDeviceIds(): Promise<{
|
|
35
|
+
machineId: string;
|
|
36
|
+
macMachineId: string;
|
|
37
|
+
}> {
|
|
38
|
+
if (cached !== undefined) return cached;
|
|
39
|
+
inFlight ??= (async () => {
|
|
40
|
+
const installId = await getOrCreateInstallId("cursor");
|
|
41
|
+
return {
|
|
42
|
+
machineId: sha256Hex(`cursor-machine:${installId}`),
|
|
43
|
+
macMachineId: sha256Hex(`cursor-mac:${installId}`),
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
try {
|
|
47
|
+
cached = await inFlight;
|
|
48
|
+
return cached;
|
|
49
|
+
} finally {
|
|
50
|
+
inFlight = undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Cursor's obfuscated checksum header value.
|
|
56
|
+
*
|
|
57
|
+
* The wire format is a base64 of an obfuscated 6-byte time prefix followed by
|
|
58
|
+
* the device ids. Ported verbatim from the donor so the server's own de-obfuscation
|
|
59
|
+
* produces the ids it expects.
|
|
60
|
+
*/
|
|
61
|
+
export function createCursorChecksumHeader(machineId: string, macMachineId?: string): string {
|
|
62
|
+
const n = Math.floor(Date.now() / 1e6);
|
|
63
|
+
const ts = new Uint8Array([
|
|
64
|
+
(n >> 40) & 0xff,
|
|
65
|
+
(n >> 32) & 0xff,
|
|
66
|
+
(n >> 24) & 0xff,
|
|
67
|
+
(n >> 16) & 0xff,
|
|
68
|
+
(n >> 8) & 0xff,
|
|
69
|
+
n & 0xff,
|
|
70
|
+
]);
|
|
71
|
+
const obfuscated = obfuscate(ts);
|
|
72
|
+
let binary = "";
|
|
73
|
+
for (const byte of obfuscated) binary += String.fromCharCode(byte);
|
|
74
|
+
const prefix = btoa(binary).replace(/=+$/, "");
|
|
75
|
+
return macMachineId ? `${prefix}${machineId}/${macMachineId}` : `${prefix}${machineId}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function obfuscate(bytes: Uint8Array): Uint8Array {
|
|
79
|
+
const out = new Uint8Array(bytes);
|
|
80
|
+
let a = 165;
|
|
81
|
+
for (let i = 0; i < out.length; i++) {
|
|
82
|
+
out[i] = ((out[i]! ^ a) + i) & 0xff;
|
|
83
|
+
a = out[i]!;
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Test seam: clears the process-lifetime cache so an injected id is re-read. */
|
|
89
|
+
export function resetCursorDeviceIdCache(): void {
|
|
90
|
+
cached = undefined;
|
|
91
|
+
inFlight = undefined;
|
|
92
|
+
}
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
// Cartethyia-owned Cursor request builder.
|
|
2
|
+
//
|
|
3
|
+
// Translates a `CanonicalRequest` into Cursor's `AgentClientMessage` protobuf
|
|
4
|
+
// messages. Everything here is wire construction — no transport, no session
|
|
5
|
+
// state, no credentials — so it is testable against the schema without a socket.
|
|
6
|
+
//
|
|
7
|
+
// Two Cursor behaviours drive the shape:
|
|
8
|
+
//
|
|
9
|
+
// - **Nothing goes in `root_prompt_messages_json`.** A client-written entry
|
|
10
|
+
// stands in for the conversation's rendered root prompt, and Cursor then stops
|
|
11
|
+
// rendering RequestContext rules — including the host system-instructions rule
|
|
12
|
+
// the system prompt travels in. Prior history therefore rides in an escaped
|
|
13
|
+
// `<conversation_history>` prefix of the live user text instead.
|
|
14
|
+
// - **Tools are advertised through `mcp_meta_tool_options`.** `AgentRunRequest.mcp_tools`
|
|
15
|
+
// stays empty on real turns; the model calls an advertised tool through the
|
|
16
|
+
// MCP exec channel, which is the only path this gateway executes.
|
|
17
|
+
import type { CanonicalMessage, CanonicalRequest, ContentPart, ToolDefinition } from "../../../protocol/canonical-model";
|
|
18
|
+
import { encodeMessage } from "./protocol/messages";
|
|
19
|
+
import { encodeJsonAsValue } from "./protocol/struct";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* MCP namespace Cartethyia advertises client tools under.
|
|
23
|
+
*
|
|
24
|
+
* Cursor namespaces MCP tools by server, and an inbound `mcp_args` request
|
|
25
|
+
* carries both the server and the function name. Advertising under one fixed
|
|
26
|
+
* namespace is what makes "is this a tool the client declared?" decidable: a
|
|
27
|
+
* Cursor-native tool (shell, read, edit, …) arrives under a different server or
|
|
28
|
+
* with no server at all and is never executed by the gateway.
|
|
29
|
+
*/
|
|
30
|
+
export const CURSOR_MCP_NAMESPACE = "cartethyia";
|
|
31
|
+
|
|
32
|
+
/** One tools advertisement Cursor receives in `RequestContext.mcp_meta_tool_options`. */
|
|
33
|
+
export interface CursorToolDescriptor {
|
|
34
|
+
/** Cursor composite `name`: `<namespace>-<tool name>`. */
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly description: string;
|
|
37
|
+
/** Server identifier every inbound call must echo to be executable. */
|
|
38
|
+
readonly providerIdentifier: string;
|
|
39
|
+
/** Bare function name the client declared. */
|
|
40
|
+
readonly toolName: string;
|
|
41
|
+
/** JSON Schema, encoded as a `google.protobuf.Value` on the wire. */
|
|
42
|
+
readonly inputSchema: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One image accepted from a canonical request, ready for `selected_images`. */
|
|
46
|
+
export interface CursorImageInput {
|
|
47
|
+
readonly data: Uint8Array;
|
|
48
|
+
readonly mimeType: string;
|
|
49
|
+
readonly filename?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Prior turn carried in the escaped history prefix of a rebased Run. */
|
|
53
|
+
export interface CursorHistoryEntry {
|
|
54
|
+
readonly role: "user" | "assistant";
|
|
55
|
+
readonly content: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Closing tags that would let one history message end its own block early. */
|
|
59
|
+
const SEED_HISTORY_TAGS = /<\/(conversation_history|user|assistant)>/gi;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Prior turns of a Run without a checkpoint, as a transcript ahead of the live
|
|
63
|
+
* user text. `system` entries never appear: host system context travels as the
|
|
64
|
+
* system-instructions rule in RequestContext.
|
|
65
|
+
*/
|
|
66
|
+
export function seedHistoryUserText(
|
|
67
|
+
text: string,
|
|
68
|
+
history: readonly CursorHistoryEntry[] | undefined,
|
|
69
|
+
): string {
|
|
70
|
+
const entries = (history ?? []).filter((entry) => entry.content.length > 0);
|
|
71
|
+
if (entries.length === 0) return text;
|
|
72
|
+
const transcript = entries
|
|
73
|
+
.map(
|
|
74
|
+
(entry) =>
|
|
75
|
+
`<${entry.role}>\n${entry.content.replace(SEED_HISTORY_TAGS, "<\\/$1>")}\n</${entry.role}>`,
|
|
76
|
+
)
|
|
77
|
+
.join("\n\n");
|
|
78
|
+
return `<conversation_history>\n${transcript}\n</conversation_history>\n\n${text}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Empty `ConversationStateStructure` for a Run with no checkpoint. */
|
|
82
|
+
export function buildSeedConversationState(): Uint8Array {
|
|
83
|
+
return encodeMessage("ConversationStateStructure", {});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Advertises one request's declared function tools as Cursor MCP descriptors.
|
|
88
|
+
*
|
|
89
|
+
* A tool with no usable name is skipped rather than advertised under an
|
|
90
|
+
* invented one: an unnameable tool cannot be matched back to an inbound call,
|
|
91
|
+
* and advertising it would let the model call something the gateway cannot
|
|
92
|
+
* execute or the client cannot answer.
|
|
93
|
+
*/
|
|
94
|
+
export function toolDescriptorsFor(
|
|
95
|
+
tools: readonly ToolDefinition[] | undefined,
|
|
96
|
+
): CursorToolDescriptor[] {
|
|
97
|
+
const descriptors: CursorToolDescriptor[] = [];
|
|
98
|
+
const seen = new Set<string>();
|
|
99
|
+
for (const tool of tools ?? []) {
|
|
100
|
+
const name = tool.name?.trim();
|
|
101
|
+
if (name === undefined || name.length === 0) continue;
|
|
102
|
+
if (seen.has(name)) continue;
|
|
103
|
+
seen.add(name);
|
|
104
|
+
descriptors.push({
|
|
105
|
+
name: `${CURSOR_MCP_NAMESPACE}-${name}`,
|
|
106
|
+
description: typeof tool.description === "string" ? tool.description : "",
|
|
107
|
+
providerIdentifier: CURSOR_MCP_NAMESPACE,
|
|
108
|
+
toolName: name,
|
|
109
|
+
inputSchema: tool.jsonSchema ?? { type: "object", properties: {} },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return descriptors;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** `McpToolDefinition` protobuf messages for one descriptor set. */
|
|
116
|
+
function mcpToolDefinitions(
|
|
117
|
+
descriptors: readonly CursorToolDescriptor[],
|
|
118
|
+
): Record<string, unknown>[] {
|
|
119
|
+
return descriptors.map((descriptor) => ({
|
|
120
|
+
name: descriptor.name,
|
|
121
|
+
description: descriptor.description,
|
|
122
|
+
input_schema: encodeJsonAsValue(descriptor.inputSchema),
|
|
123
|
+
provider_identifier: descriptor.providerIdentifier,
|
|
124
|
+
tool_name: descriptor.toolName,
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Builds the RequestContext Cartethyia sends.
|
|
130
|
+
*
|
|
131
|
+
* The system prompt is a `global` rule: an untyped Cursor rule is attachable
|
|
132
|
+
* only on request, so its content would never reach the model on its own. Tools
|
|
133
|
+
* are advertised both in `tools` (#7) and in `mcp_meta_tool_options` (#34) —
|
|
134
|
+
* the meta-tool channel is the one Cursor's model actually draws its callable
|
|
135
|
+
* catalog from on a live turn.
|
|
136
|
+
*/
|
|
137
|
+
export function buildCursorRequestContext(input: {
|
|
138
|
+
readonly systemPrompt?: string;
|
|
139
|
+
readonly descriptors: readonly CursorToolDescriptor[];
|
|
140
|
+
}): Record<string, unknown> {
|
|
141
|
+
const tools = mcpToolDefinitions(input.descriptors);
|
|
142
|
+
const rules =
|
|
143
|
+
input.systemPrompt !== undefined && input.systemPrompt.trim().length > 0
|
|
144
|
+
? [
|
|
145
|
+
{
|
|
146
|
+
full_path: "cartethyia/system-instructions",
|
|
147
|
+
content: input.systemPrompt,
|
|
148
|
+
type: { global: {} },
|
|
149
|
+
},
|
|
150
|
+
]
|
|
151
|
+
: [];
|
|
152
|
+
return {
|
|
153
|
+
...(rules.length > 0 ? { rules } : {}),
|
|
154
|
+
...(tools.length > 0 ? { tools } : {}),
|
|
155
|
+
// The host's own filesystem/web/search surface. A headless gateway has
|
|
156
|
+
// none; advertising none up front keeps Cursor from routing a tool call to
|
|
157
|
+
// a channel the gateway must then refuse.
|
|
158
|
+
mcp_file_system_options: { enabled: false },
|
|
159
|
+
web_search_enabled: false,
|
|
160
|
+
web_fetch_enabled: false,
|
|
161
|
+
...(tools.length > 0
|
|
162
|
+
? {
|
|
163
|
+
mcp_meta_tool_options: {
|
|
164
|
+
enabled: true,
|
|
165
|
+
mcp_descriptors: [
|
|
166
|
+
{
|
|
167
|
+
server_name: CURSOR_MCP_NAMESPACE,
|
|
168
|
+
server_identifier: CURSOR_MCP_NAMESPACE,
|
|
169
|
+
tools: tools.map((tool) => ({
|
|
170
|
+
tool_name: tool["tool_name"],
|
|
171
|
+
...(typeof tool["description"] === "string"
|
|
172
|
+
? { description: tool["description"] }
|
|
173
|
+
: {}),
|
|
174
|
+
input_schema: tool["input_schema"],
|
|
175
|
+
})),
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
: {}),
|
|
181
|
+
// Every context section the gateway does not populate is marked complete so
|
|
182
|
+
// Cursor does not wait for a follow-up the client will never send.
|
|
183
|
+
mcp_info_complete: true,
|
|
184
|
+
rules_info_complete: true,
|
|
185
|
+
env_info_complete: true,
|
|
186
|
+
repository_info_complete: true,
|
|
187
|
+
custom_subagents_info_complete: true,
|
|
188
|
+
agent_skills_info_complete: true,
|
|
189
|
+
mcp_file_system_info_complete: true,
|
|
190
|
+
git_status_info_complete: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Joins a request's system and instruction content into one prompt. */
|
|
195
|
+
export function systemPromptFrom(request: CanonicalRequest): string | undefined {
|
|
196
|
+
const parts = [...(request.system ?? []), ...(request.instructions ?? [])];
|
|
197
|
+
const text = parts
|
|
198
|
+
.map((part) => (part.kind === "text" ? part.text : ""))
|
|
199
|
+
.filter((value) => value.length > 0)
|
|
200
|
+
.join("\n\n");
|
|
201
|
+
return text.length > 0 ? text : undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Prior turns of a rebased Run, as escaped history entries.
|
|
206
|
+
*
|
|
207
|
+
* `system` turns never appear: host system context travels as the
|
|
208
|
+
* system-instructions rule, and `tool` turns become `user` entries because
|
|
209
|
+
* Cursor's seed transcript has no tool role.
|
|
210
|
+
*/
|
|
211
|
+
export function historyFromRequest(request: CanonicalRequest): CursorHistoryEntry[] {
|
|
212
|
+
const entries: CursorHistoryEntry[] = [];
|
|
213
|
+
for (const message of request.messages) {
|
|
214
|
+
const text = messageText(message);
|
|
215
|
+
if (text.trim().length === 0) continue;
|
|
216
|
+
if (message.role === "assistant") entries.push({ role: "assistant", content: text });
|
|
217
|
+
else if (message.role === "user" || message.role === "tool") {
|
|
218
|
+
entries.push({ role: "user", content: text });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return entries;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Builds `AgentClientMessage{run_request}` for one Cursor Run. */
|
|
225
|
+
export function buildRunRequest(input: {
|
|
226
|
+
readonly text: string;
|
|
227
|
+
readonly modelId: string;
|
|
228
|
+
readonly conversationId: string;
|
|
229
|
+
/** Stable parent group; unlike the conversation id it survives a rebase. */
|
|
230
|
+
readonly conversationGroupId?: string;
|
|
231
|
+
readonly runId?: string;
|
|
232
|
+
readonly messageId?: string;
|
|
233
|
+
readonly history?: readonly CursorHistoryEntry[];
|
|
234
|
+
/** Cursor-declared parameter tuple (effort/context/fast) for this model. */
|
|
235
|
+
readonly parameterValues?: readonly { id: string; value: string }[];
|
|
236
|
+
readonly maxMode?: boolean;
|
|
237
|
+
readonly images?: readonly CursorImageInput[];
|
|
238
|
+
readonly descriptors?: readonly CursorToolDescriptor[];
|
|
239
|
+
readonly systemPrompt?: string;
|
|
240
|
+
/** Opaque checkpoint bytes; absent sends an empty seed state. */
|
|
241
|
+
readonly conversationState?: Uint8Array;
|
|
242
|
+
/** Resume a held Run instead of submitting another user turn. */
|
|
243
|
+
readonly action?: "user" | "resume";
|
|
244
|
+
}): Uint8Array {
|
|
245
|
+
const messageId = input.messageId ?? crypto.randomUUID();
|
|
246
|
+
const seeded = !(input.conversationState && input.conversationState.length > 0);
|
|
247
|
+
const userMessage: Record<string, unknown> = {
|
|
248
|
+
text: seeded ? seedHistoryUserText(input.text, input.history) : input.text,
|
|
249
|
+
message_id: messageId,
|
|
250
|
+
// AGENT_MODE_AGENT: the default agent turn, as Cursor CLI sends it.
|
|
251
|
+
mode: 1,
|
|
252
|
+
};
|
|
253
|
+
if (input.images !== undefined && input.images.length > 0) {
|
|
254
|
+
userMessage["selected_context"] = {
|
|
255
|
+
selected_images: input.images.map((image) => ({
|
|
256
|
+
data: image.data,
|
|
257
|
+
uuid: crypto.randomUUID(),
|
|
258
|
+
...(image.filename === undefined ? {} : { path: image.filename }),
|
|
259
|
+
mime_type: image.mimeType,
|
|
260
|
+
})),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
const userMessageAction: Record<string, unknown> = { user_message: userMessage };
|
|
264
|
+
const requestContext = buildCursorRequestContext({
|
|
265
|
+
...(input.systemPrompt === undefined ? {} : { systemPrompt: input.systemPrompt }),
|
|
266
|
+
descriptors: input.descriptors ?? [],
|
|
267
|
+
});
|
|
268
|
+
userMessageAction["request_context"] = requestContext;
|
|
269
|
+
const runRequest: Record<string, unknown> = {
|
|
270
|
+
conversation_id: input.conversationId,
|
|
271
|
+
conversation_group_id: input.conversationGroupId ?? input.conversationId,
|
|
272
|
+
run_id: input.runId ?? messageId,
|
|
273
|
+
action:
|
|
274
|
+
input.action === "resume"
|
|
275
|
+
? { resume_action: { request_context: requestContext } }
|
|
276
|
+
: { user_message_action: userMessageAction },
|
|
277
|
+
requested_model: {
|
|
278
|
+
model_id: input.modelId,
|
|
279
|
+
max_mode: input.maxMode ?? false,
|
|
280
|
+
parameters: (input.parameterValues ?? []).map((parameter) => ({ ...parameter })),
|
|
281
|
+
},
|
|
282
|
+
conversation_state: seeded ? buildSeedConversationState() : input.conversationState,
|
|
283
|
+
mcp_tools: { mcp_tools: [] },
|
|
284
|
+
unknown_flag: 0,
|
|
285
|
+
field_12: 0,
|
|
286
|
+
};
|
|
287
|
+
return encodeMessage("AgentClientMessage", { run_request: runRequest });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** `AgentClientMessage{client_heartbeat}`. */
|
|
291
|
+
export function buildHeartbeat(): Uint8Array {
|
|
292
|
+
return encodeMessage("AgentClientMessage", { client_heartbeat: {} });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* `ACM#5 exec_client_control_message.heartbeat{id}`: Cursor CLI sends one every
|
|
297
|
+
* few seconds for each exec it is still running. A zero id is left out, as
|
|
298
|
+
* proto3 does for a default scalar.
|
|
299
|
+
*/
|
|
300
|
+
export function buildExecHeartbeat(execId: number): Uint8Array {
|
|
301
|
+
return encodeMessage("AgentClientMessage", {
|
|
302
|
+
exec_client_control_message: { heartbeat: execId === 0 ? {} : { id: execId } },
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** `ACM#5 exec_client_control_message.stream_close{id}` after an exec result. */
|
|
307
|
+
export function buildExecStreamClose(execId: number): Uint8Array {
|
|
308
|
+
return encodeMessage("AgentClientMessage", {
|
|
309
|
+
exec_client_control_message: { stream_close: { id: execId } },
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Stops the live Run, as Cursor CLI does before closing the stream. */
|
|
314
|
+
export function buildCancelAction(reason: string): Uint8Array {
|
|
315
|
+
return encodeMessage("AgentClientMessage", {
|
|
316
|
+
conversation_action: { cancel_action: { reason } },
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** `ACM#3 kv_client_message` reply to a server `set_blob`. */
|
|
321
|
+
export function buildKvSetBlobResult(id: number): Uint8Array {
|
|
322
|
+
return encodeMessage("AgentClientMessage", {
|
|
323
|
+
kv_client_message: { id, set_blob_result: {} },
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** `ACM#3 kv_client_message` reply to a server `get_blob`. */
|
|
328
|
+
export function buildKvGetBlobResult(id: number, blobData: Uint8Array): Uint8Array {
|
|
329
|
+
return encodeMessage("AgentClientMessage", {
|
|
330
|
+
kv_client_message: { id, get_blob_result: { blob_data: blobData } },
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** `ACM#2 exec_client_message{request_context_result}` reply to a context probe. */
|
|
335
|
+
export function buildRequestContextResult(
|
|
336
|
+
execId: number,
|
|
337
|
+
requestContext: Record<string, unknown>,
|
|
338
|
+
): Uint8Array {
|
|
339
|
+
return encodeMessage("AgentClientMessage", {
|
|
340
|
+
exec_client_message: {
|
|
341
|
+
id: execId,
|
|
342
|
+
request_context_result: { success: { request_context: requestContext } },
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* `ACM#2 exec_client_message{mcp_state_exec_result}` reply to Cursor's MCP
|
|
349
|
+
* availability probe.
|
|
350
|
+
*
|
|
351
|
+
* Cursor probes the servers behind an advertised tool before it emits a call,
|
|
352
|
+
* and a Run that never gets an answer stalls on heartbeats. Answering from the
|
|
353
|
+
* descriptors this Run advertised is what keeps the model's catalog intact.
|
|
354
|
+
*/
|
|
355
|
+
export function buildMcpStateResult(
|
|
356
|
+
execId: number,
|
|
357
|
+
descriptors: readonly CursorToolDescriptor[],
|
|
358
|
+
): Uint8Array {
|
|
359
|
+
return encodeMessage("AgentClientMessage", {
|
|
360
|
+
exec_client_message: {
|
|
361
|
+
id: execId,
|
|
362
|
+
mcp_state_exec_result: {
|
|
363
|
+
success: {
|
|
364
|
+
servers: [
|
|
365
|
+
{
|
|
366
|
+
server_name: CURSOR_MCP_NAMESPACE,
|
|
367
|
+
server_identifier: CURSOR_MCP_NAMESPACE,
|
|
368
|
+
status: "connected",
|
|
369
|
+
tools: mcpToolDefinitions(descriptors),
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** One `McpToolResultContentItem` for an image-bearing tool result. */
|
|
379
|
+
function imageContentItem(data: Uint8Array, mimeType: string): Record<string, unknown> {
|
|
380
|
+
return { image: { data, mime_type: mimeType } };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* `ACM#2 exec_client_message{mcp_result}` carrying one external tool result.
|
|
385
|
+
*
|
|
386
|
+
* `is_error` is set for a tool the client reported as failed: Cursor relays the
|
|
387
|
+
* distinction to the model, so a failure is not silently recast as a successful
|
|
388
|
+
* observation.
|
|
389
|
+
*/
|
|
390
|
+
export function buildMcpResult(input: {
|
|
391
|
+
readonly execId: number;
|
|
392
|
+
readonly text: string;
|
|
393
|
+
readonly isError: boolean;
|
|
394
|
+
readonly images?: readonly { data: Uint8Array; mimeType: string }[];
|
|
395
|
+
}): Uint8Array {
|
|
396
|
+
const content: Record<string, unknown>[] = [];
|
|
397
|
+
if (input.text.length > 0) content.push({ text: { text: input.text } });
|
|
398
|
+
for (const image of input.images ?? []) {
|
|
399
|
+
content.push(imageContentItem(image.data, image.mimeType));
|
|
400
|
+
}
|
|
401
|
+
if (content.length === 0) content.push({ text: { text: "" } });
|
|
402
|
+
return encodeMessage("AgentClientMessage", {
|
|
403
|
+
exec_client_message: {
|
|
404
|
+
id: input.execId,
|
|
405
|
+
mcp_result: { success: { content, is_error: input.isError } },
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* `ACM#2 exec_client_message{mcp_result}` rejecting a tool the client never
|
|
412
|
+
* declared.
|
|
413
|
+
*
|
|
414
|
+
* A refusal is not an execution: Cursor asked for a host tool this gateway does
|
|
415
|
+
* not run, and answering `rejected` (rather than executing it or stalling) is
|
|
416
|
+
* what lets the model continue without the gateway ever touching a filesystem
|
|
417
|
+
* or a shell on the caller's behalf.
|
|
418
|
+
*/
|
|
419
|
+
export function buildMcpRejected(execId: number, reason: string): Uint8Array {
|
|
420
|
+
return encodeMessage("AgentClientMessage", {
|
|
421
|
+
exec_client_message: {
|
|
422
|
+
id: execId,
|
|
423
|
+
mcp_result: { rejected: { reason, is_readonly: true } },
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Text of one canonical message, for the escaped history prefix. */
|
|
429
|
+
export function messageText(message: CanonicalMessage): string {
|
|
430
|
+
const parts: string[] = [];
|
|
431
|
+
for (const part of message.content) {
|
|
432
|
+
if (part.kind === "text") parts.push(part.text);
|
|
433
|
+
else if (part.kind === "toolCall") parts.push(`[tool call ${part.name} ${stringifyArgs(part.arguments)}]`);
|
|
434
|
+
else if (part.kind === "toolResult") parts.push(`[tool result ${part.call_id} ${toolResultText(part)}]`);
|
|
435
|
+
else if (part.kind === "reasoning" && part.summary !== undefined) parts.push(part.summary);
|
|
436
|
+
}
|
|
437
|
+
return parts.join("\n");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function stringifyArgs(arguments_: unknown): string {
|
|
441
|
+
if (typeof arguments_ === "string") return arguments_;
|
|
442
|
+
try {
|
|
443
|
+
return JSON.stringify(arguments_ ?? {});
|
|
444
|
+
} catch {
|
|
445
|
+
return "{}";
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function toolResultText(part: Extract<ContentPart, { kind: "toolResult" }>): string {
|
|
450
|
+
if (typeof part.content === "string") return part.content;
|
|
451
|
+
return part.content
|
|
452
|
+
.map((child) => (child.kind === "text" ? child.text : `[${child.kind}]`))
|
|
453
|
+
.join("\n");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* The live user text of one canonical request: the last user turn.
|
|
458
|
+
*
|
|
459
|
+
* Everything before it is history, and Cursor's own `user_message.text` carries
|
|
460
|
+
* only the current prompt.
|
|
461
|
+
*/
|
|
462
|
+
export function liveUserText(request: CanonicalRequest): string {
|
|
463
|
+
for (let index = request.messages.length - 1; index >= 0; index -= 1) {
|
|
464
|
+
const message = request.messages[index];
|
|
465
|
+
if (message === undefined) continue;
|
|
466
|
+
if (message.role !== "user" && message.role !== "tool") continue;
|
|
467
|
+
const text = messageText(message);
|
|
468
|
+
if (text.trim().length > 0) return text;
|
|
469
|
+
}
|
|
470
|
+
return "";
|
|
471
|
+
}
|