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.
Files changed (302) hide show
  1. package/README.md +45 -13
  2. package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
  3. package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-Bcy150Jo.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-DyNXefph.js → CliToolsPage-D3xqZbN-.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
  8. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +1 -0
  9. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CATpAEOz.js → ConsoleLogPage-u6W1UXSc.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-D6jEU7nL.js → CustomizationPage-CTTpCb25.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
  14. package/dist/dashboard/assets/{HealthEventsModal-CZkpn0a7.js → HealthEventsModal-C5XpWlvy.js} +1 -1
  15. package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
  16. package/dist/dashboard/assets/{LoginPage-BJlMVN4U.js → LoginPage-DHoB3Agn.js} +1 -1
  17. package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
  18. package/dist/dashboard/assets/{ModelPicker-Br3CXEau.js → ModelPicker-oQ-SDXp8.js} +1 -1
  19. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
  20. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +1 -0
  21. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +3 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
  25. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  26. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +1 -0
  27. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
  28. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +7 -0
  29. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
  30. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +1 -0
  31. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
  32. package/dist/dashboard/assets/{SettingsPage-BHW4Zt06.js → SettingsPage-CvjZxHE4.js} +1 -1
  33. package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
  34. package/dist/dashboard/assets/{SetupPage-VOYC_lIa.js → SetupPage-D3nza7LH.js} +1 -1
  35. package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
  36. package/dist/dashboard/assets/SortableList-Dtt12WT1.js +1 -0
  37. package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-BNIDLiU6.js → StudioPage-CbK3hXvU.js} +4 -4
  39. package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
  40. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +56 -0
  41. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BETfaMYZ.js → api-keys-BmDbsior.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
  44. package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
  45. package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
  47. package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-BTyGk_DL.js → badge-DpXT4Ixt.js} +1 -1
  49. package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-bujTWYM4.js → banner-CSAAWLJv.js} +1 -1
  51. package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-B0Bnx5CA.js → card-DEhRXXw4.js} +1 -1
  53. package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-DE2ug812.js → cli-tools-B0wBq-RL.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-R_rArH8S.js → clipboard-button-DOo6EU8O.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-BWIP_EvW.js → dialog-BvQeBMfP.js} +1 -1
  59. package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-BKcgVugF.js → drawer-CMwPXwQB.js} +1 -1
  61. package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-CcWkGR4a.js → entry-D4O2229X.js} +1 -1
  63. package/dist/dashboard/assets/{entry-CcWkGR4a.js.gz → entry-D4O2229X.js.gz} +0 -0
  64. package/dist/dashboard/assets/{entry-kTiTFrvh.js → entry-DKvr5Q9K.js} +1 -1
  65. package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-FQCejoDY.js +4 -0
  67. package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
  68. package/dist/dashboard/assets/{github-badge-Booc5mNe.js → github-badge-zliLvBwr.js} +1 -1
  69. package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-D70pChfn.js → index-CXm3FIyt.js} +2 -2
  71. package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-D1TCqM2D.js → inline-D1IlZZ8H.js} +1 -1
  73. package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-B9oyU93y.js → input-BpVZdrbc.js} +1 -1
  75. package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-BArzXirQ.js → layout-BioLW7gN.js} +1 -1
  77. package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-BVZzaoHF.js → list-row-CwVcGAIc.js} +1 -1
  79. package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-EmuIayqp.js → live-yfEJFF9T.js} +1 -1
  81. package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
  82. package/dist/dashboard/assets/{model-bans-CSPQ9xB0.js → model-bans-CT-TxSNX.js} +1 -1
  83. package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
  84. package/dist/dashboard/assets/providers/cursor.webp +0 -0
  85. package/dist/dashboard/assets/providers/trae.webp +0 -0
  86. package/dist/dashboard/assets/{routing-BExgxxBU.js → routing-Cs6jl49L.js} +1 -1
  87. package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
  88. package/dist/dashboard/assets/select-C0Rer3gF.js +41 -0
  89. package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
  90. package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
  91. package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
  92. package/dist/dashboard/assets/{stack-Ca5HxI5i.js → stack-C9_uUjaA.js} +1 -1
  93. package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
  94. package/dist/dashboard/assets/{state-CzUPfLXo.js → state-DA0B95UQ.js} +1 -1
  95. package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
  96. package/dist/dashboard/assets/{switch-DOUIky_i.js → switch-Cittxy-x.js} +1 -1
  97. package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
  98. package/dist/dashboard/assets/{toast-C76WrMhc.js → toast-CNt_jzO4.js} +1 -1
  99. package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
  100. package/dist/dashboard/assets/{useMutation-D87FZwH0.js → useMutation-BQKMcJ1H.js} +1 -1
  101. package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
  102. package/dist/dashboard/index.html +1 -1
  103. package/dist/dashboard/index.html.gz +0 -0
  104. package/dist/dashboard/precompressed-assets.json +51 -52
  105. package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
  106. package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
  107. package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
  108. package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
  109. package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
  110. package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
  111. package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
  112. package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
  113. package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
  114. package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
  115. package/package.json +3 -2
  116. package/scripts/cli/command/console-menu.ts +30 -11
  117. package/scripts/cli/command/start.ts +77 -13
  118. package/scripts/cli/command/update.ts +1 -1
  119. package/scripts/cli/gateway/instance-lock.ts +105 -0
  120. package/scripts/cli/gateway/spawn.ts +173 -101
  121. package/scripts/cli/ui/background.ts +110 -49
  122. package/src/boot/app.ts +20 -6
  123. package/src/boot/dependencies.ts +19 -0
  124. package/src/boot/elysia-typebox-bridge.ts +37 -0
  125. package/src/boot/lifecycle.ts +2 -0
  126. package/src/config/cooldowns.ts +138 -138
  127. package/src/config/providers.ts +5 -5
  128. package/src/config/runtime.ts +19 -3
  129. package/src/config/spec.ts +9 -0
  130. package/src/console/api-keys/contracts.ts +414 -391
  131. package/src/console/api-keys/routes.ts +880 -870
  132. package/src/console/auth/account-reset-store.ts +14 -0
  133. package/src/console/auth/account-reset.ts +3 -2
  134. package/src/console/auth/setup-service.ts +6 -3
  135. package/src/console/domain-registration.ts +9 -2
  136. package/src/console/observability/contracts.ts +475 -455
  137. package/src/console/observability/store.ts +1015 -952
  138. package/src/console/providers/catalog/contracts.ts +31 -0
  139. package/src/console/providers/catalog/provider-operations.ts +8 -0
  140. package/src/console/providers/catalog/store.ts +1532 -1525
  141. package/src/console/providers/oauth/callback-listener.ts +18 -3
  142. package/src/console/providers/oauth/routes.ts +38 -15
  143. package/src/console/quota/refresh.ts +19 -0
  144. package/src/console/updates/routes.ts +162 -4
  145. package/src/main.ts +3 -0
  146. package/src/network/http2-bidi.ts +85 -0
  147. package/src/network/outbound-fetch.ts +1 -26
  148. package/src/network/pool/agent.ts +478 -460
  149. package/src/network/pool/resolver.ts +1104 -544
  150. package/src/network/pool-health-machine.ts +12 -5
  151. package/src/observability/payload-capture.ts +344 -324
  152. package/src/persistence/db-pg.ts +9 -3
  153. package/src/persistence/db-sqlite.ts +497 -495
  154. package/src/persistence/health-event-flush.ts +137 -0
  155. package/src/persistence/migrate.ts +181 -129
  156. package/src/persistence/schema.ts +1360 -1295
  157. package/src/persistence/settings-revision.ts +25 -0
  158. package/src/persistence/shared-cache.ts +217 -0
  159. package/src/persistence/stores/share-store.ts +556 -528
  160. package/src/persistence/usage-flush.ts +61 -1
  161. package/src/protocol/canonical-model.ts +23 -1
  162. package/src/protocol/response/chat.ts +143 -10
  163. package/src/protocol/response/omp-tool-call-markup.ts +221 -0
  164. package/src/providers/default-registry.ts +173 -1
  165. package/src/providers/discovery/openai-model-discovery.ts +39 -3
  166. package/src/providers/discovery/openrouter-free-roster.ts +98 -0
  167. package/src/providers/discovery/probing-service.ts +1022 -1001
  168. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +39 -0
  169. package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
  170. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +18 -5
  171. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
  172. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
  173. package/src/providers/integrations/buddy/codebuddy/shared.ts +15 -6
  174. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
  175. package/src/providers/integrations/buddy/workbuddy/shared.ts +3 -3
  176. package/src/providers/integrations/claude/claude.ts +606 -582
  177. package/src/providers/integrations/cline/cline-quota.ts +18 -16
  178. package/src/providers/integrations/cline/cline.ts +539 -468
  179. package/src/providers/integrations/commandcode/commandcode.ts +675 -541
  180. package/src/providers/integrations/commandcode/discovery.ts +116 -0
  181. package/src/providers/integrations/commandcode/quota.ts +160 -0
  182. package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
  183. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
  184. package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
  185. package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
  186. package/src/providers/integrations/cursor/cursor-request.ts +471 -0
  187. package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
  188. package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
  189. package/src/providers/integrations/cursor/cursor.ts +960 -0
  190. package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
  191. package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
  192. package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
  193. package/src/providers/integrations/github/github.ts +4 -4
  194. package/src/providers/integrations/hermes.ts +79 -8
  195. package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
  196. package/src/providers/integrations/kilo/kilo.ts +153 -146
  197. package/src/providers/integrations/opencode/provider.ts +532 -538
  198. package/src/providers/integrations/openrouter/provider.ts +139 -281
  199. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
  200. package/src/providers/integrations/qoder/qoder-oauth.ts +310 -257
  201. package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
  202. package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
  203. package/src/providers/integrations/qoder/qoder.ts +865 -730
  204. package/src/providers/integrations/trae/trae-catalog.ts +190 -0
  205. package/src/providers/integrations/trae/trae-headers.ts +149 -0
  206. package/src/providers/integrations/trae/trae-oauth.ts +773 -0
  207. package/src/providers/integrations/trae/trae-protocol.ts +400 -0
  208. package/src/providers/integrations/trae/trae-realm.ts +220 -0
  209. package/src/providers/integrations/trae/trae.ts +458 -0
  210. package/src/providers/operations/account-health-service.ts +107 -10
  211. package/src/providers/operations/client-versions.ts +133 -6
  212. package/src/providers/operations/provider-catalog-seeder.ts +95 -86
  213. package/src/providers/operations/provider-credential-service.ts +46 -7
  214. package/src/providers/provider-contracts.ts +76 -0
  215. package/src/providers/provider-metadata.ts +78 -6
  216. package/src/providers/provider-model-definition.ts +373 -349
  217. package/src/security/api-key-auth.ts +126 -8
  218. package/src/transport/contracts.ts +3 -0
  219. package/src/transport/dispatch/attempt-finalize.ts +32 -1
  220. package/src/transport/dispatch/attempt-loop.ts +530 -515
  221. package/src/transport/dispatch/generation.ts +88 -21
  222. package/src/transport/dispatch/leases.ts +99 -2
  223. package/src/transport/dispatch/proxy-request.ts +60 -3
  224. package/src/transport/dispatch/streaming/finalize.ts +16 -1
  225. package/src/transport/dispatch/streaming/stream.ts +22 -2
  226. package/src/transport/dispatch/upstream.ts +19 -1
  227. package/src/transport/guards/auth-guard.ts +26 -1
  228. package/src/transport/guards/body-policy.ts +5 -4
  229. package/src/transport/request/preparer.ts +103 -0
  230. package/src/transport/request/state.ts +14 -0
  231. package/src/transport/routing/plan-gate.ts +45 -0
  232. package/src/transport/routing/route-catalog.ts +799 -777
  233. package/src/transport/routing/route-model.ts +52 -1
  234. package/src/transport/routing/router.ts +51 -0
  235. package/dist/dashboard/assets/BannedPage-QhvbJB9o.js +0 -1
  236. package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
  237. package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
  238. package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
  239. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +0 -1
  240. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
  241. package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
  242. package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
  243. package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
  244. package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
  245. package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
  246. package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js +0 -1
  247. package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
  248. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +0 -3
  249. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
  250. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
  251. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
  252. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +0 -1
  253. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
  254. package/dist/dashboard/assets/ProxyPage-CXiumA53.js +0 -7
  255. package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
  256. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +0 -1
  257. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
  258. package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
  259. package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
  260. package/dist/dashboard/assets/SortableList-DpOTnGuH.js +0 -1
  261. package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
  262. package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
  263. package/dist/dashboard/assets/UsagePage-BAFaSDQV.js +0 -56
  264. package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
  265. package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
  266. package/dist/dashboard/assets/app-BLCvGdzb.css +0 -2
  267. package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
  268. package/dist/dashboard/assets/assert-7J6_LVD3.js +0 -1
  269. package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
  270. package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
  271. package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
  272. package/dist/dashboard/assets/card-B0Bnx5CA.js.gz +0 -0
  273. package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
  274. package/dist/dashboard/assets/clipboard-button-R_rArH8S.js.gz +0 -0
  275. package/dist/dashboard/assets/dialog-BWIP_EvW.js.gz +0 -0
  276. package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
  277. package/dist/dashboard/assets/entry-7dpgvpJ3.js +0 -4
  278. package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
  279. package/dist/dashboard/assets/entry-kTiTFrvh.js.gz +0 -0
  280. package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
  281. package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
  282. package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
  283. package/dist/dashboard/assets/input-B9oyU93y.js.gz +0 -0
  284. package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
  285. package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
  286. package/dist/dashboard/assets/live-EmuIayqp.js.gz +0 -0
  287. package/dist/dashboard/assets/model-bans-CSPQ9xB0.js.gz +0 -0
  288. package/dist/dashboard/assets/routing-BExgxxBU.js.gz +0 -0
  289. package/dist/dashboard/assets/select-C9mMoEpJ.js +0 -41
  290. package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
  291. package/dist/dashboard/assets/settings-C4_Gn_wq.js +0 -1
  292. package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
  293. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
  294. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
  295. package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
  296. package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
  297. package/dist/dashboard/assets/switch-DOUIky_i.js.gz +0 -0
  298. package/dist/dashboard/assets/toast-C76WrMhc.js.gz +0 -0
  299. package/dist/dashboard/assets/useMutation-D87FZwH0.js.gz +0 -0
  300. package/src/network/tls/bun-client-hello.ts +0 -112
  301. /package/dist/dashboard/assets/{app-Cjq7nrwu.js → app-C-Y4oZT0.js} +0 -0
  302. /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
+ }