cartethyia 2.7.9 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (496) hide show
  1. package/.env.example +6 -5
  2. package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Dq530awD.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-DaZZ3cqP.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BlC_xdkX.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-7xT5yg2E.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
  16. package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-6Teoq9Se.js} +2 -2
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CZuI0MAR.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
  20. package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-C1hNTlkr.js} +1 -1
  21. package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-0yfhSI1C.js} +1 -1
  23. package/dist/dashboard/assets/OverviewPage-0yfhSI1C.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js +8 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js.gz +0 -0
  26. package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-Ddhm1InL.js} +1 -1
  27. package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js.gz +0 -0
  30. package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js +9 -0
  31. package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-BzZAvYSX.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
  36. package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage--s-EfEIL.js} +1 -1
  37. package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-BjXFGSK4.js} +1 -1
  39. package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
  40. package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-CP8F8BN7.js} +1 -1
  41. package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-2hc12Uz2.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-2hc12Uz2.js.gz +0 -0
  44. package/dist/dashboard/assets/app-DGXbNfAR.css +2 -0
  45. package/dist/dashboard/assets/app-DGXbNfAR.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-BMcj1xZE.js +1 -0
  47. package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-DkRUuVfz.js} +1 -1
  49. package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-CDjW841Q.js} +1 -1
  51. package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-Bto5UlL9.js} +1 -1
  53. package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-DaH6g9hL.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-BYKBCzjk.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-DL0Zftwv.js} +1 -1
  59. package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-Dpx_UXaS.js} +1 -1
  61. package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-BV7-1NoM.js} +1 -1
  63. package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
  64. package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-CGvdNU6r.js} +1 -1
  65. package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-DScKYvZj.js +4 -0
  67. package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
  68. package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-BS3DZlZj.js} +1 -1
  69. package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-BU64Lqvp.js → index-SQnvvTWX.js} +2 -2
  71. package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-Dl-cuLK0.js} +1 -1
  73. package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-BCVkdR9d.js → input-Bfx0EzRc.js} +1 -1
  75. package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-BxhbQ_JS.js} +1 -1
  77. package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-D49Y4ptn.js} +1 -1
  79. package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-Cae01iu8.js → live-CbVTdX9V.js} +1 -1
  81. package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
  82. package/dist/dashboard/assets/{network-CRib7H_2.js → network-JGvDveof.js} +1 -1
  83. package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
  84. package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-Dw6kiew-.js} +1 -1
  85. package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
  86. package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-iUoI8GeE.js} +1 -1
  87. package/dist/dashboard/assets/pagination-iUoI8GeE.js.gz +0 -0
  88. package/dist/dashboard/assets/providers/sensenova-intl.png +0 -0
  89. package/dist/dashboard/assets/providers/sensenova.png +0 -0
  90. package/dist/dashboard/assets/providers/zro.svg +3 -0
  91. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  92. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  93. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  94. package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-8jZr-3WE.js} +1 -1
  95. package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
  96. package/dist/dashboard/assets/{select-ChoFWyKz.js → select-oO1FxMVb.js} +2 -2
  97. package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
  98. package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-CLr3kdIx.js} +1 -1
  99. package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
  100. package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-Y3pDteZr.js} +1 -1
  101. package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
  102. package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-CArX_Iak.js} +1 -1
  103. package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
  104. package/dist/dashboard/assets/{switch-Db70BMix.js → switch-DwlwmDO-.js} +1 -1
  105. package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
  106. package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-DJ9GYEgf.js} +1 -1
  107. package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
  108. package/dist/dashboard/index.html +2 -2
  109. package/dist/dashboard/index.html.gz +0 -0
  110. package/dist/dashboard/precompressed-assets.json +54 -53
  111. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  112. package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
  113. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  114. package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
  115. package/package.json +1 -1
  116. package/src/boot/app.ts +11 -14
  117. package/src/boot/dependencies.ts +5 -7
  118. package/src/boot/flush.ts +20 -0
  119. package/src/boot/lifecycle.ts +7 -5
  120. package/src/config/providers.ts +6 -5
  121. package/src/config/routing.ts +26 -0
  122. package/src/config/spec-metadata.ts +72 -0
  123. package/src/config/spec.ts +28 -8
  124. package/src/console/api-keys/contracts.ts +12 -1
  125. package/src/console/api-keys/routes.ts +40 -28
  126. package/src/console/auth/account-reset.ts +7 -9
  127. package/src/console/backup/contracts.ts +4 -0
  128. package/src/console/backup/export-endpoint.ts +7 -5
  129. package/src/console/backup/nine-router.ts +71 -10
  130. package/src/console/backup/service.ts +8 -1
  131. package/src/console/cli-tools/service.ts +6 -12
  132. package/src/console/console-router-guards.ts +23 -16
  133. package/src/console/console-router.ts +1 -1
  134. package/src/console/domain-registration.ts +7 -4
  135. package/src/console/inferhub/client.ts +1 -1
  136. package/src/console/inferhub/normalize.ts +24 -28
  137. package/src/console/inferhub/types.ts +3 -0
  138. package/src/console/ip-bans/routes.ts +7 -13
  139. package/src/console/observability/live.ts +2 -2
  140. package/src/console/observability/store.ts +0 -53
  141. package/src/console/providers/catalog/contracts.ts +9 -3
  142. package/src/console/providers/catalog/model-operations.ts +2 -3
  143. package/src/console/providers/catalog/provider-operations.ts +1 -1
  144. package/src/console/providers/catalog/routes.ts +3 -2
  145. package/src/console/providers/catalog/store.ts +1914 -1902
  146. package/src/console/providers/detail/routes.ts +1 -0
  147. package/src/console/providers/detail/store.ts +4 -0
  148. package/src/console/providers/oauth/callback-listener.ts +30 -11
  149. package/src/console/providers/oauth/device-egress.ts +123 -0
  150. package/src/console/providers/oauth/routes.ts +123 -16
  151. package/src/console/quota/account-quota-global.ts +14 -24
  152. package/src/console/quota/account-quota-shared.ts +49 -0
  153. package/src/console/quota/account-quota-tenant.ts +11 -23
  154. package/src/console/quota/account-test-egress.ts +2 -2
  155. package/src/console/quota/daily-growth-job.ts +1 -1
  156. package/src/console/quota/quota-view.ts +19 -3
  157. package/src/console/routing/pools/contracts.ts +1 -1
  158. package/src/console/routing/pools/operations.ts +32 -47
  159. package/src/console/routing/pools/probe-verdict.ts +2 -2
  160. package/src/console/routing/pools/relay-deploy.ts +1 -1
  161. package/src/console/routing/pools/store.ts +4 -4
  162. package/src/console/shared/errors.ts +33 -6
  163. package/src/main.ts +2 -3
  164. package/src/network/client.ts +67 -0
  165. package/src/network/egress/bidi-session.ts +322 -0
  166. package/src/network/egress/dial.ts +201 -0
  167. package/src/network/egress/factory.ts +191 -0
  168. package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
  169. package/src/network/egress/tls-profile.ts +123 -0
  170. package/src/network/egress/ws.ts +175 -0
  171. package/src/network/pool/{byte-accounting.ts → accounting.ts} +3 -2
  172. package/src/network/{types.ts → pool/agent-config.ts} +1 -1
  173. package/src/network/pool/agent.ts +2 -2
  174. package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +95 -20
  175. package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
  176. package/src/network/pool/resolver.ts +10 -849
  177. package/src/network/pool/selector.ts +184 -41
  178. package/src/network/ssrf.ts +7 -7
  179. package/src/network/{retry.ts → util/retry.ts} +1 -1
  180. package/src/network/{rate-limit-verdict.ts → verdict.ts} +1 -1
  181. package/src/observability/event-log.ts +7 -361
  182. package/src/observability/performance-metrics.ts +9 -0
  183. package/src/observability/runtime-metrics.ts +14 -9
  184. package/src/observability/telemetry-buffer.ts +16 -26
  185. package/src/observability/telemetry-reconciliation.ts +9 -37
  186. package/src/observability/telemetry-row.ts +125 -0
  187. package/src/persistence/db-mode.ts +4 -1
  188. package/src/persistence/dialect.ts +18 -0
  189. package/src/persistence/migrate.ts +6 -3
  190. package/src/persistence/schema.ts +17 -0
  191. package/src/persistence/ttl-cache.ts +21 -3
  192. package/src/protocol/codecs.ts +59 -2
  193. package/src/protocol/dsml.ts +34 -86
  194. package/src/protocol/markup-stream-parser.ts +195 -0
  195. package/src/protocol/reasoning-text.ts +19 -0
  196. package/src/protocol/request/chat.ts +10 -10
  197. package/src/protocol/request/gemini.ts +8 -2
  198. package/src/protocol/request/messages.ts +34 -22
  199. package/src/protocol/response/chat.ts +12 -20
  200. package/src/protocol/response/messages.ts +34 -7
  201. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  202. package/src/protocol/response/responses.ts +18 -2
  203. package/src/protocol/stop-reason.ts +23 -1
  204. package/src/protocol/think-split.ts +112 -0
  205. package/src/protocol/translation/dialect/qwen.ts +26 -58
  206. package/src/protocol/translation/quirks.ts +15 -0
  207. package/src/protocol/upstream/send-claude-messages.ts +2 -2
  208. package/src/providers/adapters/compatible-adapter.ts +76 -12
  209. package/src/providers/authentication/oauth-client.ts +40 -6
  210. package/src/providers/authentication/oauth-device-flow.ts +105 -11
  211. package/src/providers/authentication/oauth-flow-store.ts +82 -1
  212. package/src/providers/authentication/oauth-refresh-service.ts +766 -758
  213. package/src/providers/configured-provider.ts +148 -0
  214. package/src/providers/default-registry.ts +137 -36
  215. package/src/providers/discovery/discovery-types.ts +7 -0
  216. package/src/providers/discovery/probe-phases.ts +20 -6
  217. package/src/providers/discovery/probing-service.ts +22 -2
  218. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  219. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  220. package/src/providers/integrations/antigravity/antigravity-protocol.ts +16 -1
  221. package/src/providers/integrations/buddy/account.ts +969 -0
  222. package/src/providers/integrations/buddy/capabilities.ts +12 -12
  223. package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
  224. package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +176 -20
  225. package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
  226. package/src/providers/integrations/buddy/codebuddy/shared.ts +41 -26
  227. package/src/providers/integrations/buddy/headers.ts +311 -0
  228. package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
  229. package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
  230. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  231. package/src/providers/integrations/cline/cline-oauth.ts +10 -5
  232. package/src/providers/integrations/cline/cline.ts +5 -6
  233. package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
  234. package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
  235. package/src/providers/integrations/clixad/provider.ts +145 -0
  236. package/src/providers/integrations/codex/codex-oauth.ts +8 -5
  237. package/src/providers/integrations/commandcode/quota.ts +22 -28
  238. package/src/providers/integrations/cursor/cursor-auth.ts +1 -9
  239. package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
  240. package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
  241. package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
  242. package/src/providers/integrations/cursor/cursor-request.ts +26 -12
  243. package/src/providers/integrations/cursor/cursor-shared.ts +8 -5
  244. package/src/providers/integrations/cursor/cursor.ts +574 -110
  245. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  246. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  247. package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
  248. package/src/providers/integrations/deepai/deepai-common.ts +145 -0
  249. package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
  250. package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
  251. package/src/providers/integrations/deepai/provider.ts +42 -0
  252. package/src/providers/integrations/devin/devin-auth.ts +7 -0
  253. package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
  254. package/src/providers/integrations/factory/factory-oauth.ts +33 -51
  255. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  256. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  257. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  258. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  259. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  260. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  261. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  262. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  263. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  264. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  265. package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
  266. package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
  267. package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
  268. package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
  269. package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
  270. package/src/providers/integrations/freepi/freepi-session.ts +158 -0
  271. package/src/providers/integrations/freepi/freepi.ts +80 -54
  272. package/src/providers/integrations/github/github-discovery.ts +30 -13
  273. package/src/providers/integrations/github/github-oauth.ts +217 -44
  274. package/src/providers/integrations/github/github-quota.ts +22 -5
  275. package/src/providers/integrations/github/github.ts +40 -32
  276. package/src/providers/integrations/grok/grok-oauth.ts +52 -49
  277. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  278. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  279. package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
  280. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +42 -13
  281. package/src/providers/integrations/hermes/index.ts +2 -0
  282. package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
  283. package/src/providers/integrations/kilo/kilo.ts +1 -1
  284. package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
  285. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  286. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  287. package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
  288. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  289. package/src/providers/integrations/kiro/kiro.ts +15 -19
  290. package/src/providers/integrations/muse/muse-oauth.ts +5 -5
  291. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  292. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  293. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  294. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  295. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  296. package/src/providers/integrations/openrouter/provider.ts +1 -1
  297. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  298. package/src/providers/integrations/perch/perch-protocol.ts +0 -6
  299. package/src/providers/integrations/perch/perch-quota.ts +2 -16
  300. package/src/providers/integrations/perch/perch.ts +6 -16
  301. package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
  302. package/src/providers/integrations/qoder/qoder-adapter.ts +20 -125
  303. package/src/providers/integrations/qoder/qoder-auth.ts +109 -30
  304. package/src/providers/integrations/qoder/qoder-catalog.ts +21 -6
  305. package/src/providers/integrations/qoder/qoder-chat.ts +32 -29
  306. package/src/providers/integrations/qoder/qoder-checkin.ts +5 -5
  307. package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
  308. package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
  309. package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
  310. package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
  311. package/src/providers/integrations/qoder/qoder-types.ts +9 -0
  312. package/src/providers/integrations/qoder/qoder.ts +3 -26
  313. package/src/providers/integrations/sensenova/provider.ts +144 -0
  314. package/src/providers/integrations/trae/trae-protocol.ts +9 -5
  315. package/src/providers/integrations/trae/trae-quota.ts +21 -29
  316. package/src/providers/integrations/trae/trae-realm.ts +38 -0
  317. package/src/providers/integrations/trae/trae.ts +7 -24
  318. package/src/providers/integrations/vercel/provider.ts +117 -0
  319. package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
  320. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  321. package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
  322. package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
  323. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  324. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  325. package/src/providers/integrations/zro/zro.ts +205 -0
  326. package/src/providers/operations/account-error-classification.ts +199 -307
  327. package/src/providers/operations/account-health-service.ts +82 -1
  328. package/src/providers/operations/account-refresh-status.ts +36 -37
  329. package/src/providers/operations/account-tier.ts +15 -16
  330. package/src/providers/operations/client-version-resolver.ts +1 -1
  331. package/src/providers/operations/client-versions.ts +100 -81
  332. package/src/providers/operations/cooldown-rules.ts +11 -16
  333. package/src/providers/operations/oauth-refresh-lead.ts +46 -22
  334. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  335. package/src/providers/operations/provider-catalog-service.ts +1 -1
  336. package/src/providers/operations/provider-credential-service.ts +118 -51
  337. package/src/providers/operations/provider-rules/github.ts +48 -0
  338. package/src/providers/operations/provider-rules/grok.ts +33 -0
  339. package/src/providers/operations/provider-rules/inception.ts +49 -0
  340. package/src/providers/operations/provider-rules/index.ts +32 -0
  341. package/src/providers/operations/provider-rules/perch.ts +37 -0
  342. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  343. package/src/providers/operations/provider-rules/trae.ts +81 -0
  344. package/src/providers/operations/session-resolution.ts +17 -1
  345. package/src/providers/provider-contracts.ts +6 -4
  346. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  347. package/src/providers/provider-metadata.ts +70 -10
  348. package/src/providers/provider-probe.ts +15 -1
  349. package/src/providers/provider-registry.ts +42 -33
  350. package/src/providers/quota/quota-contracts.ts +2 -0
  351. package/src/providers/quota/quota-limit-windows.ts +16 -5
  352. package/src/providers/quota/quota-support.ts +1 -1
  353. package/src/providers/quota/quota-window-parser.ts +90 -18
  354. package/src/providers/quota/refresh.ts +100 -21
  355. package/src/security/api-key-auth-cache.ts +1 -1
  356. package/src/security/api-key-auth.ts +6 -2
  357. package/src/security/ip-boundary.ts +1 -1
  358. package/src/transport/dispatch/attempt-finalize.ts +15 -14
  359. package/src/transport/dispatch/attempt-loop.ts +73 -19
  360. package/src/transport/dispatch/generation.ts +2 -8
  361. package/src/transport/dispatch/handler-deps.ts +1 -1
  362. package/src/transport/dispatch/leases.ts +1 -1
  363. package/src/transport/dispatch/native-shared.ts +25 -0
  364. package/src/transport/dispatch/proxy-request.ts +8 -46
  365. package/src/transport/dispatch/responses-compact.ts +2 -8
  366. package/src/transport/dispatch/retry-policy.ts +1 -1
  367. package/src/transport/dispatch/streaming/finalize.ts +10 -7
  368. package/src/transport/dispatch/streaming/stream.ts +9 -37
  369. package/src/transport/dispatch/systemone.ts +2 -8
  370. package/src/transport/dispatch/token-count.ts +2 -6
  371. package/src/transport/dispatch/upstream.ts +63 -0
  372. package/src/transport/drain-endpoint.ts +31 -4
  373. package/src/transport/failure-policy.ts +16 -30
  374. package/src/transport/guards/body-policy.ts +7 -1
  375. package/src/transport/guards/error-lifecycle.ts +13 -1
  376. package/src/transport/guards/request-context.ts +22 -14
  377. package/src/transport/request/preparer.ts +81 -71
  378. package/src/transport/routing/bounded-lru.ts +14 -0
  379. package/src/transport/routing/drain-reserve.ts +129 -0
  380. package/src/transport/routing/plan-gate.ts +7 -1
  381. package/src/transport/routing/round-robin.ts +60 -0
  382. package/src/transport/routing/route-catalog.ts +123 -111
  383. package/src/transport/routing/route-model.ts +51 -9
  384. package/src/transport/routing/router.ts +393 -32
  385. package/src/transport/shutdown-notice.ts +1 -1
  386. package/src/transport/surface/adapters.ts +11 -2
  387. package/src/transport/surface/chat/adapter.ts +2 -2
  388. package/src/transport/surface/chat/encode.ts +47 -4
  389. package/src/transport/surface/chat/parse.ts +2 -12
  390. package/src/transport/surface/completion.ts +44 -33
  391. package/src/transport/surface/content-parts.ts +21 -0
  392. package/src/transport/surface/messages/adapter.ts +7 -1
  393. package/src/transport/surface/messages/encode.ts +11 -3
  394. package/src/transport/surface/messages/parse.ts +2 -1
  395. package/src/transport/surface/messages/stream.ts +33 -17
  396. package/src/transport/surface/responses/encode.ts +22 -5
  397. package/src/transport/surface/responses/parse.ts +3 -11
  398. package/src/workers/buddy-growth-tasks.ts +8 -8
  399. package/src/workers/daily-checkin.ts +4 -4
  400. package/src/workers/quota-refresh-sweep.ts +1 -1
  401. package/src/workers/zcode-gift-claim.ts +1 -1
  402. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
  403. package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
  404. package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
  405. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
  406. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
  407. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
  408. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
  409. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
  410. package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
  411. package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
  412. package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
  413. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
  414. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
  415. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
  416. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
  417. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
  418. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
  419. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js +0 -9
  420. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
  421. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
  422. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
  423. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
  424. package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
  425. package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
  426. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
  427. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
  428. package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
  429. package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
  430. package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
  431. package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
  432. package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
  433. package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
  434. package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
  435. package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
  436. package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
  437. package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
  438. package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
  439. package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
  440. package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
  441. package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
  442. package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
  443. package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
  444. package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
  445. package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
  446. package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
  447. package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
  448. package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
  449. package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
  450. package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
  451. package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
  452. package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
  453. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  454. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  455. package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
  456. package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
  457. package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
  458. package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
  459. package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
  460. package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
  461. package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
  462. package/src/network/pool-health.ts +0 -210
  463. package/src/providers/bundled-discovery-providers.ts +0 -98
  464. package/src/providers/configured-openai-providers.ts +0 -120
  465. package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
  466. package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
  467. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
  468. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
  469. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
  470. package/src/providers/integrations/buddy/buddy-identity.ts +0 -300
  471. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
  472. package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
  473. package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
  474. package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
  475. package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
  476. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
  477. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -185
  478. package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
  479. package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -175
  480. package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
  481. package/src/providers/integrations/qoder/chat-proto.ts +0 -328
  482. package/src/providers/integrations/qoder/qoder-grpc.ts +0 -871
  483. package/src/providers/integrations/qoder/qoder-ide.ts +0 -588
  484. package/src/providers/operations/model-discovery-cache.ts +0 -34
  485. package/src/providers/operations/probe-fetch.ts +0 -15
  486. package/src/providers/operations/provider-version-cache.ts +0 -30
  487. package/src/providers/provider-capabilities.ts +0 -33
  488. package/src/providers/provider-credential-envelope.ts +0 -43
  489. /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-C0gTXEoN.js} +0 -0
  490. /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-C0gTXEoN.js.gz} +0 -0
  491. /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
  492. /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
  493. /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
  494. /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
  495. /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
  496. /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
@@ -6,19 +6,21 @@
6
6
  // It never calls `node:http2.connect` itself — the binding owns the socket, the
7
7
  // DNS pinning, the SSRF policy, and the pool tunnel.
8
8
  import { GatewayError } from "../../../protocol/gateway-error";
9
- import type { ValidatedHttp2BidiStream, Http2BidiEndReason } from "../../../network/http2-bidi";
9
+ import type { ValidatedHttp2BidiStream, Http2BidiEndReason } from "../../../network/bidi";
10
10
  import type { ValidatedOutboundHttp2Bidi } from "../../provider-contracts";
11
11
  import { decodeFramePayload, encodeFrame, streamFrames, FLAG_END_STREAM } from "./protocol/framing";
12
12
  import { decodeMessage } from "./protocol/messages";
13
13
  import {
14
14
  CONTENT_TYPE_CONNECT_PROTO,
15
15
  CONNECT_PROTOCOL_VERSION,
16
+ CURSOR_API_BASE_URL,
16
17
  FALLBACK_CLIENT_VERSION,
17
18
  RUN_PATH,
18
19
  isAllowedAgentHost,
19
20
  resolveCursorTimezone,
20
21
  } from "./cursor-shared";
21
22
  import { createCursorChecksumHeader, resolveCursorDeviceIds } from "./cursor-identity";
23
+ import { fnv1a32 } from "../shared/hash";
22
24
 
23
25
  import { isRecord } from "../../../protocol/codecs";
24
26
 
@@ -88,7 +90,11 @@ export interface CursorServerMessage {
88
90
  const DEFAULT_READ_IDLE_MS = 120_000;
89
91
 
90
92
  /** Cursor identity headers for the Run stream. */
91
- async function runHeaders(credential: string, clientVersion: string): Promise<Record<string, string>> {
93
+ async function runHeaders(
94
+ credential: string,
95
+ clientVersion: string,
96
+ requestId: string,
97
+ ): Promise<Record<string, string>> {
92
98
  const { machineId, macMachineId } = await resolveCursorDeviceIds();
93
99
  return {
94
100
  authorization: `Bearer ${credential}`,
@@ -102,7 +108,7 @@ async function runHeaders(credential: string, clientVersion: string): Promise<Re
102
108
  // Without it Cursor may treat the stream as non-streaming.
103
109
  "x-cursor-streaming": "true",
104
110
  "x-ghost-mode": "true",
105
- "x-request-id": crypto.randomUUID(),
111
+ "x-request-id": requestId,
106
112
  ...(resolveCursorTimezone() === "" ? {} : { "x-cursor-timezone": resolveCursorTimezone() }),
107
113
  "user-agent": "connect-es/1.6.1",
108
114
  };
@@ -122,7 +128,8 @@ export async function openCursorRun(input: {
122
128
  readonly signal: AbortSignal;
123
129
  readonly clientVersion?: string;
124
130
  readonly readIdleMs?: number;
125
- }): Promise<{ stream: ValidatedHttp2BidiStream; write: (message: Uint8Array) => Promise<void> }> {
131
+ readonly requestId?: string;
132
+ }): Promise<{ stream: ValidatedHttp2BidiStream; requestId: string; write: (message: Uint8Array) => Promise<void> }> {
126
133
  const url = new URL(`${input.origin}${RUN_PATH}`);
127
134
  if (url.protocol !== "https:" || !isAllowedAgentHost(url.hostname)) {
128
135
  throw new GatewayError(
@@ -132,7 +139,8 @@ export async function openCursorRun(input: {
132
139
  { origin: input.origin },
133
140
  );
134
141
  }
135
- const headers = await runHeaders(input.credential, input.clientVersion ?? FALLBACK_CLIENT_VERSION);
142
+ const requestId = input.requestId ?? crypto.randomUUID();
143
+ const headers = await runHeaders(input.credential, input.clientVersion ?? FALLBACK_CLIENT_VERSION, requestId);
136
144
  const stream = await input.outboundHttp2(url, headers, {
137
145
  signal: input.signal,
138
146
  readIdleMs: input.readIdleMs ?? DEFAULT_READ_IDLE_MS,
@@ -140,6 +148,7 @@ export async function openCursorRun(input: {
140
148
  });
141
149
  return {
142
150
  stream,
151
+ requestId,
143
152
  write: async (message: Uint8Array): Promise<void> => {
144
153
  await stream.write(encodeFrame(0, message));
145
154
  },
@@ -275,7 +284,13 @@ export async function* decodeServerMessages(
275
284
  const envelope = connectErrorEnvelope(payload);
276
285
  if (envelope !== undefined) throw envelope;
277
286
  try {
278
- yield decodeMessage("AgentServerMessage", payload);
287
+ const decoded = decodeMessage("AgentServerMessage", payload);
288
+ // Shape-only wire traces and checkpoint retention need the raw frame,
289
+ // but the decoded message must stay a plain protobuf object for the
290
+ // adapter (and for encodeMessage round-trips). Non-enumerable keeps it
291
+ // out of Object.keys/JSON, so no existing consumer sees a new field.
292
+ Object.defineProperty(decoded, "__bytes", { value: payload, enumerable: false });
293
+ yield decoded;
279
294
  } catch {
280
295
  // One undecodable frame is not a protocol failure to abort on: Cursor
281
296
  // extends this schema with fields this vendored copy does not model, and
@@ -297,3 +312,587 @@ function concat(left: Uint8Array, right: Uint8Array): Uint8Array {
297
312
  out.set(right, left.byteLength);
298
313
  return out;
299
314
  }
315
+
316
+ // ---------------------------------------------------------------------------
317
+ // Normalized Run transport, and the HTTP/1.1 fallback.
318
+ //
319
+ // HTTP/2 is the Run's native shape, but a network that cannot carry a
320
+ // bidirectional HTTP/2 stream (a tunneling proxy, an ALPN-restricted middlebox)
321
+ // must not turn Cursor into a provider that cannot serve at all: Cursor's own
322
+ // clients run the same conversation over HTTP/1.1, with the server's frames
323
+ // down one `AgentService/RunSSE` stream and each client message up as a
324
+ // numbered `BidiService/BidiAppend` call (donor wire, verified: the append
325
+ // body is raw protobuf — hex message, the request id, the sequence number —
326
+ // not a Connect frame).
327
+ // ---------------------------------------------------------------------------
328
+
329
+ /** Why one Run transport ended, with the failure the pump should surface. */
330
+ export interface CursorRunTerminal {
331
+ readonly kind: "remote-error" | "remote-clean-close" | "local-close";
332
+ readonly failure: GatewayError | undefined;
333
+ }
334
+
335
+ /**
336
+ * One Run's wire, normalized across transports.
337
+ *
338
+ * `body` yields raw response bytes (Connect-framed server messages) and
339
+ * `write` sends one framed client message; the frame pump in the adapter
340
+ * consumes both without knowing which transport is underneath.
341
+ */
342
+ export interface CursorRunTransport {
343
+ readonly mode: "http2" | "http1";
344
+ readonly body: AsyncIterable<Uint8Array>;
345
+ write(message: Uint8Array): Promise<void>;
346
+ abort(): void;
347
+ /** Terminal listener; at most one terminal is delivered per transport. */
348
+ onTerminal(listener: (terminal: CursorRunTerminal) => void): () => void;
349
+ }
350
+
351
+ /** Wraps the validated HTTP/2 stream in the normalized transport shape. */
352
+ export function wrapHttp2Run(
353
+ stream: ValidatedHttp2BidiStream,
354
+ write: (message: Uint8Array) => Promise<void>,
355
+ ): CursorRunTransport {
356
+ const listeners = new Set<(terminal: CursorRunTerminal) => void>();
357
+ let settled = false;
358
+ const settle = (terminal: CursorRunTerminal): void => {
359
+ if (settled) return;
360
+ settled = true;
361
+ for (const listener of [...listeners]) {
362
+ try {
363
+ listener(terminal);
364
+ } catch {
365
+ // A listener failure must not skip the remaining listeners.
366
+ }
367
+ }
368
+ listeners.clear();
369
+ };
370
+ stream.onTerminal((reason) => {
371
+ if (reason.kind === "local-close") {
372
+ settle({ kind: "local-close", failure: undefined });
373
+ return;
374
+ }
375
+ if (reason.kind === "remote-clean-close") {
376
+ // A clean close carries no failure of its own: the pump decides whether
377
+ // a turn that ended without `turn_ended` is complete (text/step/tool
378
+ // batch) or quiet. `cursorRunTerminationError` is for status/trailer
379
+ // refusals, not for a stream the server simply closed.
380
+ settle({ kind: "remote-clean-close", failure: undefined });
381
+ return;
382
+ }
383
+ settle({
384
+ kind: "remote-error",
385
+ failure: cursorRunTerminationError({ stream, reason }) ?? undefined,
386
+ });
387
+ });
388
+ return {
389
+ mode: "http2",
390
+ body: stream.body,
391
+ write,
392
+ abort: () => stream.abort(),
393
+ onTerminal: (listener) => {
394
+ listeners.add(listener);
395
+ return () => listeners.delete(listener);
396
+ },
397
+ };
398
+ }
399
+
400
+ /** HTTP/1.1 Run constants, as the donor's clients pin them. */
401
+ const H1 = {
402
+ /** ms a BidiAppend may take before the Run fails (donor appendWait). */
403
+ appendWaitMs: 60_000,
404
+ /** BidiAppends allowed on the wire at once (donor atOnce). */
405
+ atOnce: 16,
406
+ } as const;
407
+
408
+ /** ms after one h2-blocked open that fresh Runs go straight to HTTP/1.1. */
409
+ const H2_RETRY_AFTER_MS = 10 * 60_000;
410
+ /** ms an HTTP/2 open may take before the transport itself is declared blocked. */
411
+ const H2_HEAD_WAIT_MS = 15_000;
412
+
413
+ /** Per-credential HTTP/1.1 window after one h2-blocked open, digest-keyed. */
414
+ const h1UntilByCredential = new Map<string, number>();
415
+
416
+ function credentialDigest(credential: string): string {
417
+ return String(fnv1a32(credential));
418
+ }
419
+
420
+ /** True when the failure to open an HTTP/2 Run says HTTP/2 itself is blocked. */
421
+ export function isH2BlockedOpen(error: unknown): boolean {
422
+ if (!(error instanceof Error)) return false;
423
+ const code = String((error as Error & { code?: string }).code ?? "");
424
+ if (/^ERR_HTTP2|^EPROTO/.test(code)) return true;
425
+ const message = `${error.message} ${code}`.toLowerCase();
426
+ return (
427
+ /http\/?2|nghttp2|alpn|protocol error|settings frame|flow control/.test(message) &&
428
+ !/refused|enotfound|econn|enetunreach|ehostunreach|epipe/.test(message)
429
+ );
430
+ }
431
+
432
+ /**
433
+ * Whether a failed Run *open* is worth retrying on a fresh transport.
434
+ *
435
+ * Connection and deadline failures are retried; auth, quota, and request-shape
436
+ * refusals are not — retrying those just burns the account's remaining window.
437
+ */
438
+ export function isRetryableOpenFailure(error: unknown): boolean {
439
+ if (!(error instanceof GatewayError)) {
440
+ // Raw transport throws (DNS, dial, TLS) come through as plain errors.
441
+ return true;
442
+ }
443
+ if (error.code === "authentication_failed") return false;
444
+ if (error.code === "quota_exceeded") return false;
445
+ if (error.code === "invalid_request") return false;
446
+ return true;
447
+ }
448
+
449
+ /** Backoff for open retries: 500 ms doubling, capped at 8 s (donor policy). */
450
+ export function openRetryDelayMs(attempt: number): number {
451
+ return Math.min(500 * 2 ** attempt, 8_000);
452
+ }
453
+
454
+ function sleep(ms: number, signal: AbortSignal): Promise<void> {
455
+ return new Promise((resolve, reject) => {
456
+ const timer = setTimeout(resolve, ms);
457
+ timer.unref?.();
458
+ signal.addEventListener(
459
+ "abort",
460
+ () => {
461
+ clearTimeout(timer);
462
+ reject(new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃"));
463
+ },
464
+ { once: true },
465
+ );
466
+ });
467
+ }
468
+
469
+ /** Applies the open-retry backoff; exported as part of the transport policy. */
470
+ export { sleep as cursorSleep };
471
+
472
+ /** Connect's code for an HTTP status that carried no Connect error of its own. */
473
+ function connectCodeForStatus(status: number): string {
474
+ return { 400: "invalid_argument", 401: "unauthenticated", 403: "permission_denied", 429: "resource_exhausted" }[
475
+ status
476
+ ] ?? "unavailable";
477
+ }
478
+
479
+ /** Maps an HTTP/1.1 Run failure onto the gateway's error taxonomy. */
480
+ function h1OpenFailure(status: number, message: string): GatewayError {
481
+ if (status === 401 || status === 403) {
482
+ return new GatewayError(
483
+ "authentication_failed",
484
+ 401,
485
+ `Cursor refused the credential over HTTP/1.1: ${message} (request {requestId}). Wrong key, right door — check the credential 🔑`,
486
+ {},
487
+ "upstream",
488
+ );
489
+ }
490
+ if (status === 429) {
491
+ return new GatewayError(
492
+ "quota_exceeded",
493
+ 429,
494
+ `Cursor refused the Run over HTTP/1.1: ${message} (request {requestId}). You've maxed this window — pace yourself ⏳`,
495
+ {},
496
+ "upstream",
497
+ );
498
+ }
499
+ return new GatewayError(
500
+ "platform_unavailable",
501
+ 502,
502
+ `Cursor Run failed over HTTP/1.1: ${message} (request {requestId}). The upstream came back empty-handed — retry 🌙`,
503
+ {},
504
+ "upstream",
505
+ );
506
+ }
507
+
508
+ /** varint encoding for the BidiAppend body builder. */
509
+ function appendVarint(out: number[], value: number): void {
510
+ let v = value;
511
+ while (v > 0x7f) {
512
+ out.push((v & 0x7f) | 0x80);
513
+ v >>>= 7;
514
+ }
515
+ out.push(v);
516
+ }
517
+
518
+ /**
519
+ * Builds one raw `aiserver.v1.BidiAppendRequest` body:
520
+ * field 1 `data` (hex string), field 2 `requestId` (message, field 1 string),
521
+ * field 3 `appendSeqno` (int64). Verified against the donor's wire encoder.
522
+ */
523
+ function bidiAppendBody(message: Uint8Array, requestId: string, seqno: number): Uint8Array {
524
+ const hex = Buffer.from(message).toString("hex");
525
+ const data = new TextEncoder().encode(hex);
526
+ const id = new TextEncoder().encode(requestId);
527
+ const out: number[] = [];
528
+ out.push(0x0a);
529
+ appendVarint(out, data.byteLength);
530
+ for (const byte of data) out.push(byte);
531
+ out.push(0x12, 2 + id.byteLength, 0x0a);
532
+ appendVarint(out, id.byteLength);
533
+ for (const byte of id) out.push(byte);
534
+ out.push(0x18);
535
+ appendVarint(out, seqno);
536
+ return new Uint8Array(out);
537
+ }
538
+
539
+ /** True when a client message is a KV blob reply, which no append waits on. */
540
+ function isKvReply(message: Uint8Array): boolean {
541
+ return message.byteLength > 0 && message[0] === 0x1a;
542
+ }
543
+
544
+ /**
545
+ * Opens one Run over HTTP/1.1: the server's frames come down one RunSSE
546
+ * stream, every client message goes up as a numbered BidiAppend call.
547
+ *
548
+ * Failures of an append or of the down stream end the Run with a synthesized
549
+ * Connect error envelope (END_STREAM), so the shared decoder surfaces it as
550
+ * the Run's own refusal — the pump cannot tell the transports apart.
551
+ */
552
+ export async function openCursorRunH1(input: {
553
+ readonly requestId: string;
554
+ readonly credential: string;
555
+ readonly fetcher: typeof fetch;
556
+ readonly signal: AbortSignal;
557
+ readonly clientVersion?: string;
558
+ readonly readIdleMs?: number;
559
+ }): Promise<CursorRunTransport> {
560
+ const ac = new AbortController();
561
+ const forwardAbort = (): void => ac.abort();
562
+ input.signal.addEventListener("abort", forwardAbort, { once: true });
563
+ if (input.signal.aborted) ac.abort();
564
+
565
+ const headers = await runHeaders(input.credential, input.clientVersion ?? FALLBACK_CLIENT_VERSION, input.requestId);
566
+ const wireHeaders = {
567
+ ...headers,
568
+ "accept-encoding": "identity",
569
+ "x-cursor-streaming": "true",
570
+ };
571
+
572
+ const listeners = new Set<(terminal: CursorRunTerminal) => void>();
573
+ let ended = false;
574
+ let settled = false;
575
+ let seqno = 0;
576
+ const inflight = new Set<Promise<void>>();
577
+
578
+ const queue: Array<Uint8Array | null> = [];
579
+ let waiter: (() => void) | undefined;
580
+ const pushDown = (chunk: Uint8Array | null): void => {
581
+ queue.push(chunk);
582
+ const wake = waiter;
583
+ waiter = undefined;
584
+ wake?.();
585
+ };
586
+ const settle = (terminal: CursorRunTerminal): void => {
587
+ if (settled) return;
588
+ settled = true;
589
+ for (const listener of [...listeners]) {
590
+ try {
591
+ listener(terminal);
592
+ } catch {
593
+ // A listener failure must not skip the remaining listeners.
594
+ }
595
+ }
596
+ listeners.clear();
597
+ };
598
+
599
+ const failStream = (code: string, message: string, status: number): void => {
600
+ if (ended) return;
601
+ ended = true;
602
+ // Synthesized Connect error envelope: the shared decoder reads it as the
603
+ // Run's own refusal, exactly as it does over HTTP/2.
604
+ const json = new TextEncoder().encode(JSON.stringify({ error: { code, message } }));
605
+ pushDown(encodeFrame(FLAG_END_STREAM, json));
606
+ pushDown(null);
607
+ settle({ kind: "remote-error", failure: h1OpenFailure(status, message) });
608
+ };
609
+
610
+ const endCleanly = (): void => {
611
+ if (ended || inflight.size > 0) return;
612
+ ended = true;
613
+ pushDown(null);
614
+ settle({ kind: "remote-clean-close", failure: undefined });
615
+ };
616
+
617
+ const append = async (message: Uint8Array): Promise<void> => {
618
+ const seq = seqno;
619
+ seqno += 1;
620
+ const body = bidiAppendBody(message, input.requestId, seq);
621
+ const deadlineMs = H1.appendWaitMs + Math.ceil(body.byteLength / 65_536) * 1_000;
622
+ const deadline = AbortSignal.timeout(deadlineMs);
623
+ try {
624
+ const response = await input.fetcher(`${CURSOR_API_BASE_URL}/aiserver.v1.BidiService/BidiAppend`, {
625
+ method: "POST",
626
+ headers: { ...wireHeaders, "content-type": "application/proto" },
627
+ body: body as unknown as BodyInit,
628
+ signal: AbortSignal.any([ac.signal, deadline]),
629
+ });
630
+ if (!response.ok) {
631
+ const text = await response.text().catch(() => "");
632
+ failStream(connectCodeForStatus(response.status), text.trim() || `HTTP ${response.status}`, response.status);
633
+ return;
634
+ }
635
+ await response.arrayBuffer().catch(() => undefined);
636
+ } catch (error) {
637
+ if (ac.signal.aborted || input.signal.aborted) return;
638
+ if (deadline.aborted) {
639
+ failStream("deadline_exceeded", `BidiAppend got no answer in ${Math.round(deadlineMs / 1_000)}s`, 504);
640
+ return;
641
+ }
642
+ failStream("unavailable", `BidiAppend: ${error instanceof Error ? error.message : String(error)}`, 502);
643
+ }
644
+ };
645
+
646
+ const track = (promise: Promise<void>): void => {
647
+ inflight.add(promise);
648
+ void promise.finally(() => {
649
+ inflight.delete(promise);
650
+ // A down stream that already ended cleanly while appends were on the
651
+ // wire becomes terminal only once they have all been answered.
652
+ if (inflight.size === 0 && sseDone && !ended) endCleanly();
653
+ });
654
+ };
655
+ let sseDone = false;
656
+ /** One append whose completion the write path waits on. */
657
+ const appendTracked = (message: Uint8Array): Promise<void> => {
658
+ const promise = append(message);
659
+ track(promise);
660
+ return promise;
661
+ };
662
+
663
+ const transport: CursorRunTransport = {
664
+ mode: "http1",
665
+ body: {
666
+ [Symbol.asyncIterator](): AsyncIterator<Uint8Array> {
667
+ return {
668
+ next: async (): Promise<IteratorResult<Uint8Array>> => {
669
+ for (;;) {
670
+ const front = queue.shift();
671
+ if (front === undefined) {
672
+ if (ended) return { done: true, value: undefined };
673
+ await new Promise<void>((resolve) => {
674
+ waiter = resolve;
675
+ });
676
+ continue;
677
+ }
678
+ if (front === null) return { done: true, value: undefined };
679
+ return { done: false, value: front };
680
+ }
681
+ },
682
+ };
683
+ },
684
+ },
685
+ write: async (message: Uint8Array): Promise<void> => {
686
+ if (ended || ac.signal.aborted) {
687
+ throw new GatewayError(
688
+ "transport_unavailable",
689
+ 502,
690
+ "Cursor Run transport is closed (request {requestId}). The connection ghosted us — come back in a moment 👻",
691
+ {},
692
+ "network",
693
+ );
694
+ }
695
+ // KV blob replies never wait: nothing else depends on them, and Cursor
696
+ // asks for a fresh Run's whole history at once (donor kvReply rule).
697
+ if (isKvReply(message)) {
698
+ track(append(message));
699
+ return;
700
+ }
701
+ // Every other message serializes: it goes up only after the appends
702
+ // before it were answered, in sequence order (donor ordering rule).
703
+ while (inflight.size > 0 && !ended && !ac.signal.aborted) {
704
+ await Promise.race([...inflight]);
705
+ }
706
+ if (ended || ac.signal.aborted) {
707
+ throw new GatewayError(
708
+ "transport_unavailable",
709
+ 502,
710
+ "Cursor Run transport is closed (request {requestId}). The connection ghosted us — come back in a moment 👻",
711
+ {},
712
+ "network",
713
+ );
714
+ }
715
+ await appendTracked(message);
716
+ },
717
+ abort: () => {
718
+ ac.abort();
719
+ if (!ended) {
720
+ ended = true;
721
+ pushDown(null);
722
+ settle({ kind: "local-close", failure: undefined });
723
+ }
724
+ },
725
+ onTerminal: (listener) => {
726
+ listeners.add(listener);
727
+ return () => listeners.delete(listener);
728
+ },
729
+ };
730
+
731
+ // Down stream: RunSSE, opened with one Connect frame carrying the request
732
+ // id (field 1, string) — the linkage every append repeats. The opener is
733
+ // a regular message frame (flags 0), not an END_STREAM frame: END_STREAM
734
+ // ends the *response*, not the request body.
735
+ void (async () => {
736
+ try {
737
+ const id = new TextEncoder().encode(input.requestId);
738
+ const runOpen = new Uint8Array(2 + id.byteLength);
739
+ runOpen[0] = 0x0a;
740
+ runOpen[1] = id.byteLength;
741
+ runOpen.set(id, 2);
742
+ const response = await input.fetcher(`${CURSOR_API_BASE_URL}/agent.v1.AgentService/RunSSE`, {
743
+ method: "POST",
744
+ headers: wireHeaders,
745
+ body: encodeFrame(0, runOpen) as unknown as BodyInit,
746
+ signal: ac.signal,
747
+ });
748
+ if (!response.ok || response.body === null) {
749
+ const text = response.body === null ? "no response body" : await response.text().catch(() => "");
750
+ failStream(
751
+ connectCodeForStatus(response.status),
752
+ text.trim() || `HTTP ${response.status}`,
753
+ response.status,
754
+ );
755
+ return;
756
+ }
757
+ const reader = response.body.getReader();
758
+ const readIdleMs = input.readIdleMs ?? DEFAULT_READ_IDLE_MS;
759
+ for (;;) {
760
+ if (ended) {
761
+ await reader.cancel().catch(() => undefined);
762
+ return;
763
+ }
764
+ const chunk = await Promise.race([
765
+ reader.read(),
766
+ new Promise<never>((_, reject) => {
767
+ const timer = setTimeout(() => reject(new Error(`RunSSE idle past ${readIdleMs}ms`)), readIdleMs);
768
+ timer.unref?.();
769
+ }),
770
+ ]);
771
+ if (chunk.done) {
772
+ sseDone = true;
773
+ endCleanly();
774
+ return;
775
+ }
776
+ pushDown(chunk.value);
777
+ }
778
+ } catch (error) {
779
+ if (ac.signal.aborted || input.signal.aborted) {
780
+ if (!ended) {
781
+ ended = true;
782
+ pushDown(null);
783
+ settle({ kind: "local-close", failure: undefined });
784
+ }
785
+ return;
786
+ }
787
+ failStream("unavailable", `RunSSE: ${error instanceof Error ? error.message : String(error)}`, 502);
788
+ }
789
+ })();
790
+
791
+ return transport;
792
+ }
793
+
794
+ /**
795
+ * Opens one Cursor Run with the donor's transport policy: HTTP/2 first, and —
796
+ * when HTTP/2 itself is blocked here — HTTP/1.1 immediately, with fresh Runs
797
+ * staying on HTTP/1.1 for a window. A plain connection failure leaves the next
798
+ * Run on HTTP/2 again; it says nothing about HTTP/2 being unavailable.
799
+ */
800
+ export async function openCursorRunWithFallback(input: {
801
+ readonly origin: string;
802
+ readonly credential: string;
803
+ readonly outboundHttp2: ValidatedOutboundHttp2Bidi | undefined;
804
+ readonly outboundFetch: typeof fetch | undefined;
805
+ readonly signal: AbortSignal;
806
+ readonly clientVersion?: string;
807
+ readonly readIdleMs?: number;
808
+ /** Stable request id across H2 → H1 fallback and stream retries. */
809
+ readonly requestId?: string;
810
+ /** Force HTTP/1.1 after a mid-stream HTTP/2 failure. */
811
+ readonly forceH1?: boolean;
812
+ }): Promise<{ transport: CursorRunTransport; requestId: string }> {
813
+ const digest = credentialDigest(input.credential);
814
+ const h1Until = h1UntilByCredential.get(digest) ?? 0;
815
+ if (input.forceH1 === true) h1UntilByCredential.set(digest, Date.now() + H2_RETRY_AFTER_MS);
816
+ if (input.forceH1 === true || Date.now() < h1Until || input.outboundHttp2 === undefined) {
817
+ return openH1OrThrow(input, input.requestId ?? crypto.randomUUID(), digest);
818
+ }
819
+ let openedPromise: ReturnType<typeof openCursorRun> | undefined;
820
+ try {
821
+ openedPromise = openCursorRun({
822
+ origin: input.origin,
823
+ credential: input.credential,
824
+ outboundHttp2: input.outboundHttp2,
825
+ signal: input.signal,
826
+ ...(input.requestId === undefined ? {} : { requestId: input.requestId }),
827
+ ...(input.clientVersion === undefined ? {} : { clientVersion: input.clientVersion }),
828
+ ...(input.readIdleMs === undefined ? {} : { readIdleMs: input.readIdleMs }),
829
+ });
830
+ let headTimedOut = false;
831
+ let headTimer: ReturnType<typeof setTimeout> | undefined;
832
+ let opened: Awaited<typeof openedPromise>;
833
+ try {
834
+ opened = await Promise.race([
835
+ openedPromise,
836
+ new Promise<never>((_, reject) => {
837
+ headTimer = setTimeout(() => {
838
+ headTimedOut = true;
839
+ reject(Object.assign(new Error("HTTP/2 Run got no response head in 15s"), { name: "H2HeadTimeout" }));
840
+ }, H2_HEAD_WAIT_MS);
841
+ headTimer.unref?.();
842
+ }),
843
+ ]);
844
+ } finally {
845
+ clearTimeout(headTimer);
846
+ // A head that arrived after the timeout is a live stream nobody owns:
847
+ // abort it instead of leaking the socket.
848
+ if (headTimedOut) void openedPromise.then((o) => o.stream.abort(), () => undefined);
849
+ }
850
+ return {
851
+ transport: wrapHttp2Run(opened.stream, opened.write),
852
+ requestId: opened.requestId,
853
+ };
854
+ } catch (error) {
855
+ if (!isH2BlockedOpen(error)) throw error;
856
+ h1UntilByCredential.set(digest, Date.now() + H2_RETRY_AFTER_MS);
857
+ // The donor keeps one request id across the fallback: RunSSE's opener and
858
+ // every BidiAppend repeat the id the HTTP/2 Run was opened with.
859
+ return openH1OrThrow(
860
+ input,
861
+ input.requestId ?? openedPromise?.then((o) => o.requestId).catch(() => crypto.randomUUID()) ?? crypto.randomUUID(),
862
+ digest,
863
+ );
864
+ }
865
+ }
866
+
867
+ /** Opens the HTTP/1.1 fallback, refusing when no validated egress can carry it. */
868
+ async function openH1OrThrow(
869
+ input: {
870
+ readonly credential: string;
871
+ readonly outboundFetch: typeof fetch | undefined;
872
+ readonly signal: AbortSignal;
873
+ readonly clientVersion?: string;
874
+ readonly readIdleMs?: number;
875
+ },
876
+ requestId: string | Promise<string>,
877
+ digest: string,
878
+ ): Promise<{ transport: CursorRunTransport; requestId: string }> {
879
+ const id = await requestId;
880
+ if (input.outboundFetch === undefined) {
881
+ throw new GatewayError(
882
+ "admission_unavailable",
883
+ 503,
884
+ "Cursor fell back to HTTP/1.1 but no validated egress is configured for this route (request {requestId}). No bridge to cross — configure a pool first 🏗️",
885
+ { providerId: "cursor" },
886
+ );
887
+ }
888
+ const transport = await openCursorRunH1({
889
+ requestId: id,
890
+ credential: input.credential,
891
+ fetcher: input.outboundFetch,
892
+ signal: input.signal,
893
+ ...(input.clientVersion === undefined ? {} : { clientVersion: input.clientVersion }),
894
+ ...(input.readIdleMs === undefined ? {} : { readIdleMs: input.readIdleMs }),
895
+ });
896
+ void digest;
897
+ return { transport, requestId: id };
898
+ }