cartethyia 2.7.9 → 2.8.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 (375) hide show
  1. package/.env.example +3 -2
  2. package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Cc0vKpMG.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-CXsaOAO2.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BeLpJ4ma.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-DnV0uZ3m.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-CBxAjo63.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
  16. package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CgoPc5B9.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
  20. package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-D9bFQern.js} +1 -1
  21. package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-B_p4tL8k.js} +1 -1
  23. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
  26. package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-D_0PiZtg.js} +1 -1
  27. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
  30. package/dist/dashboard/assets/{ProxyPage-BJ87Pp0U.js → ProxyPage-BwNC_-Cx.js} +1 -1
  31. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-CctTga2C.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
  36. package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage-febLU4r1.js} +1 -1
  37. package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-M6fvR5Wz.js} +1 -1
  39. package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
  40. package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-nyeuRqz9.js} +1 -1
  41. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-BFOlvrJC.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
  44. package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
  45. package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-CqTLw5e7.js +1 -0
  47. package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-C83EqLB0.js} +1 -1
  49. package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-D1xn7pIe.js} +1 -1
  51. package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-DU3Lq5Lm.js} +1 -1
  53. package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-Dw1U_drK.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-CEL5Sg_J.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-BjFR5UHy.js} +1 -1
  59. package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-HvBW0AjI.js} +1 -1
  61. package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
  62. package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
  63. package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
  64. package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-CECVELyy.js} +1 -1
  65. package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
  66. package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-DYYofQ7W.js} +1 -1
  67. package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
  68. package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-DLW4i8zK.js} +1 -1
  69. package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-BU64Lqvp.js → index-1-fCqe64.js} +2 -2
  71. package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-DGvU2oLK.js} +1 -1
  73. package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-BCVkdR9d.js → input-iNnO1X2K.js} +1 -1
  75. package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-0q8UGypb.js} +1 -1
  77. package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-C0w2bQjJ.js} +1 -1
  79. package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-Cae01iu8.js → live-M5aJ_xCa.js} +1 -1
  81. package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
  82. package/dist/dashboard/assets/{network-CRib7H_2.js → network-BKeQGT3K.js} +1 -1
  83. package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
  84. package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-CivAk3da.js} +1 -1
  85. package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
  86. package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-CpzvI5ep.js} +1 -1
  87. package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
  88. package/dist/dashboard/assets/providers/zro.svg +3 -0
  89. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  90. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  91. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  92. package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-BFQX2VBc.js} +1 -1
  93. package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
  94. package/dist/dashboard/assets/{select-ChoFWyKz.js → select-CXbck0jG.js} +2 -2
  95. package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
  96. package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-C_XvMHh9.js} +1 -1
  97. package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
  98. package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-DggRiCxE.js} +1 -1
  99. package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
  100. package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-ClFzfbYp.js} +1 -1
  101. package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
  102. package/dist/dashboard/assets/{switch-Db70BMix.js → switch-CTIFxKqS.js} +1 -1
  103. package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
  104. package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-BClG3yWj.js} +1 -1
  105. package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
  106. package/dist/dashboard/index.html +2 -2
  107. package/dist/dashboard/index.html.gz +0 -0
  108. package/dist/dashboard/precompressed-assets.json +54 -53
  109. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  110. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  111. package/package.json +1 -1
  112. package/src/boot/app.ts +10 -13
  113. package/src/boot/dependencies.ts +1 -1
  114. package/src/boot/flush.ts +20 -0
  115. package/src/boot/lifecycle.ts +7 -5
  116. package/src/config/routing.ts +26 -0
  117. package/src/config/spec-metadata.ts +72 -0
  118. package/src/config/spec.ts +20 -0
  119. package/src/console/api-keys/contracts.ts +12 -1
  120. package/src/console/api-keys/routes.ts +40 -28
  121. package/src/console/auth/account-reset.ts +7 -9
  122. package/src/console/backup/contracts.ts +4 -0
  123. package/src/console/backup/export-endpoint.ts +7 -5
  124. package/src/console/cli-tools/service.ts +6 -12
  125. package/src/console/console-router-guards.ts +23 -16
  126. package/src/console/domain-registration.ts +0 -2
  127. package/src/console/inferhub/normalize.ts +24 -28
  128. package/src/console/inferhub/types.ts +3 -0
  129. package/src/console/ip-bans/routes.ts +7 -13
  130. package/src/console/observability/store.ts +0 -53
  131. package/src/console/providers/catalog/contracts.ts +2 -2
  132. package/src/console/providers/catalog/discover-constants.ts +6 -0
  133. package/src/console/providers/catalog/routes.ts +1 -0
  134. package/src/console/providers/catalog/store.ts +21 -16
  135. package/src/console/quota/account-quota-global.ts +14 -24
  136. package/src/console/quota/account-quota-shared.ts +49 -0
  137. package/src/console/quota/account-quota-tenant.ts +6 -18
  138. package/src/console/quota/quota-view.ts +19 -3
  139. package/src/console/routing/pools/operations.ts +31 -46
  140. package/src/console/shared/errors.ts +33 -6
  141. package/src/main.ts +2 -3
  142. package/src/network/pool/byte-accounting.ts +3 -2
  143. package/src/network/pool/selector.ts +182 -39
  144. package/src/network/pool-cooldown-store.ts +89 -14
  145. package/src/network/pool-health.ts +49 -61
  146. package/src/network/rate-limit-verdict.ts +1 -1
  147. package/src/network/ssrf.ts +6 -6
  148. package/src/observability/event-log.ts +7 -361
  149. package/src/observability/performance-metrics.ts +9 -0
  150. package/src/observability/runtime-metrics.ts +14 -9
  151. package/src/observability/telemetry-buffer.ts +15 -25
  152. package/src/observability/telemetry-reconciliation.ts +9 -37
  153. package/src/observability/telemetry-row.ts +125 -0
  154. package/src/persistence/db-mode.ts +4 -1
  155. package/src/persistence/dialect.ts +18 -0
  156. package/src/persistence/migrate.ts +6 -3
  157. package/src/persistence/schema.ts +8 -0
  158. package/src/persistence/ttl-cache.ts +20 -2
  159. package/src/protocol/codecs.ts +1 -1
  160. package/src/protocol/dsml.ts +34 -86
  161. package/src/protocol/markup-stream-parser.ts +195 -0
  162. package/src/protocol/reasoning-text.ts +19 -0
  163. package/src/protocol/request/chat.ts +10 -10
  164. package/src/protocol/request/gemini.ts +8 -2
  165. package/src/protocol/request/messages.ts +3 -4
  166. package/src/protocol/response/chat.ts +12 -20
  167. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  168. package/src/protocol/response/responses.ts +18 -2
  169. package/src/protocol/stop-reason.ts +23 -1
  170. package/src/protocol/think-split.ts +112 -0
  171. package/src/protocol/translation/dialect/qwen.ts +26 -58
  172. package/src/providers/adapters/compatible-adapter.ts +71 -12
  173. package/src/providers/authentication/oauth-client.ts +1 -1
  174. package/src/providers/authentication/oauth-device-flow.ts +98 -8
  175. package/src/providers/authentication/oauth-refresh-service.ts +30 -49
  176. package/src/providers/configured-provider.ts +148 -0
  177. package/src/providers/default-registry.ts +61 -36
  178. package/src/providers/discovery/probe-phases.ts +14 -4
  179. package/src/providers/discovery/probing-service.ts +15 -2
  180. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  181. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  182. package/src/providers/integrations/antigravity/antigravity-protocol.ts +5 -1
  183. package/src/providers/integrations/buddy/buddy-chat-shared.ts +173 -17
  184. package/src/providers/integrations/buddy/buddy-identity.ts +19 -6
  185. package/src/providers/integrations/buddy/capabilities.ts +2 -2
  186. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +3 -0
  187. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -0
  188. package/src/providers/integrations/buddy/codebuddy/shared.ts +37 -22
  189. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  190. package/src/providers/integrations/cline/cline.ts +2 -2
  191. package/src/providers/integrations/commandcode/quota.ts +22 -28
  192. package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
  193. package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
  194. package/src/providers/integrations/cursor/cursor-request.ts +26 -12
  195. package/src/providers/integrations/cursor/cursor-shared.ts +7 -4
  196. package/src/providers/integrations/cursor/cursor.ts +89 -40
  197. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  198. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  199. package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
  200. package/src/providers/integrations/factory/factory-oauth.ts +17 -44
  201. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  202. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  203. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  204. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  205. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  206. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  207. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  208. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  209. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  210. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  211. package/src/providers/integrations/grok/grok-oauth.ts +48 -45
  212. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  213. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  214. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +9 -9
  215. package/src/providers/integrations/hermes/index.ts +2 -0
  216. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  217. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  218. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  219. package/src/providers/integrations/kiro/kiro.ts +15 -19
  220. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  221. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  222. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  223. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  224. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  225. package/src/providers/integrations/perch/perch-protocol.ts +0 -3
  226. package/src/providers/integrations/perch/perch-quota.ts +2 -16
  227. package/src/providers/integrations/perch/perch.ts +3 -13
  228. package/src/providers/integrations/qoder/claim-qoder-credits.ts +18 -59
  229. package/src/providers/integrations/qoder/qoder-adapter.ts +1 -12
  230. package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
  231. package/src/providers/integrations/qoder/qoder-catalog.ts +20 -5
  232. package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
  233. package/src/providers/integrations/qoder/qoder-checkin.ts +4 -4
  234. package/src/providers/integrations/qoder/qoder-grpc.ts +0 -1
  235. package/src/providers/integrations/qoder/qoder-ide.ts +0 -107
  236. package/src/providers/integrations/qoder/qoder.ts +0 -1
  237. package/src/providers/integrations/trae/trae-protocol.ts +9 -5
  238. package/src/providers/integrations/trae/trae-quota.ts +6 -28
  239. package/src/providers/integrations/trae/trae-realm.ts +38 -0
  240. package/src/providers/integrations/trae/trae.ts +7 -24
  241. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  242. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  243. package/src/providers/integrations/zro/zro.ts +204 -0
  244. package/src/providers/operations/account-error-classification.ts +199 -307
  245. package/src/providers/operations/account-health-service.ts +82 -1
  246. package/src/providers/operations/account-refresh-status.ts +22 -37
  247. package/src/providers/operations/account-tier.ts +15 -16
  248. package/src/providers/operations/client-version-resolver.ts +1 -1
  249. package/src/providers/operations/client-versions.ts +5 -51
  250. package/src/providers/operations/cooldown-rules.ts +11 -16
  251. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  252. package/src/providers/operations/provider-catalog-service.ts +1 -1
  253. package/src/providers/operations/provider-credential-service.ts +102 -32
  254. package/src/providers/operations/provider-rules/github.ts +48 -0
  255. package/src/providers/operations/provider-rules/grok.ts +33 -0
  256. package/src/providers/operations/provider-rules/inception.ts +49 -0
  257. package/src/providers/operations/provider-rules/index.ts +32 -0
  258. package/src/providers/operations/provider-rules/perch.ts +37 -0
  259. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  260. package/src/providers/operations/provider-rules/trae.ts +81 -0
  261. package/src/providers/provider-contracts.ts +4 -2
  262. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  263. package/src/providers/provider-metadata.ts +40 -10
  264. package/src/providers/provider-probe.ts +15 -1
  265. package/src/providers/provider-registry.ts +42 -33
  266. package/src/providers/quota/quota-limit-windows.ts +16 -5
  267. package/src/providers/quota/quota-support.ts +1 -1
  268. package/src/providers/quota/quota-window-parser.ts +90 -18
  269. package/src/providers/quota/refresh.ts +75 -18
  270. package/src/security/api-key-auth-cache.ts +1 -1
  271. package/src/security/api-key-auth.ts +6 -2
  272. package/src/transport/dispatch/attempt-finalize.ts +14 -13
  273. package/src/transport/dispatch/attempt-loop.ts +71 -17
  274. package/src/transport/dispatch/generation.ts +2 -8
  275. package/src/transport/dispatch/native-shared.ts +25 -0
  276. package/src/transport/dispatch/proxy-request.ts +8 -46
  277. package/src/transport/dispatch/responses-compact.ts +2 -8
  278. package/src/transport/dispatch/streaming/finalize.ts +9 -6
  279. package/src/transport/dispatch/streaming/stream.ts +8 -36
  280. package/src/transport/dispatch/systemone.ts +2 -8
  281. package/src/transport/dispatch/token-count.ts +2 -6
  282. package/src/transport/dispatch/upstream.ts +63 -0
  283. package/src/transport/drain-endpoint.ts +31 -4
  284. package/src/transport/failure-policy.ts +16 -30
  285. package/src/transport/guards/body-policy.ts +7 -1
  286. package/src/transport/guards/error-lifecycle.ts +13 -1
  287. package/src/transport/guards/request-context.ts +22 -14
  288. package/src/transport/request/preparer.ts +81 -71
  289. package/src/transport/routing/bounded-lru.ts +14 -0
  290. package/src/transport/routing/drain-reserve.ts +129 -0
  291. package/src/transport/routing/plan-gate.ts +7 -1
  292. package/src/transport/routing/round-robin.ts +60 -0
  293. package/src/transport/routing/route-catalog.ts +123 -111
  294. package/src/transport/routing/route-model.ts +51 -9
  295. package/src/transport/routing/router.ts +393 -32
  296. package/src/transport/surface/adapters.ts +11 -2
  297. package/src/transport/surface/chat/adapter.ts +2 -2
  298. package/src/transport/surface/chat/encode.ts +47 -4
  299. package/src/transport/surface/chat/parse.ts +2 -12
  300. package/src/transport/surface/completion.ts +44 -33
  301. package/src/transport/surface/content-parts.ts +21 -0
  302. package/src/transport/surface/messages/adapter.ts +7 -1
  303. package/src/transport/surface/messages/encode.ts +11 -3
  304. package/src/transport/surface/messages/parse.ts +2 -1
  305. package/src/transport/surface/messages/stream.ts +33 -17
  306. package/src/transport/surface/responses/encode.ts +22 -5
  307. package/src/transport/surface/responses/parse.ts +3 -11
  308. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
  309. package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
  310. package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
  311. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
  312. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
  313. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
  314. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
  315. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
  316. package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
  317. package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
  318. package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
  319. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
  320. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
  321. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
  322. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
  323. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
  324. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
  325. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
  326. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
  327. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
  328. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
  329. package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
  330. package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
  331. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
  332. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
  333. package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
  334. package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
  335. package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
  336. package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
  337. package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
  338. package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
  339. package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
  340. package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
  341. package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
  342. package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
  343. package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
  344. package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
  345. package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
  346. package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
  347. package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
  348. package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
  349. package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
  350. package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
  351. package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
  352. package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
  353. package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
  354. package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
  355. package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
  356. package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
  357. package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
  358. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  359. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  360. package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
  361. package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
  362. package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
  363. package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
  364. package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
  365. package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
  366. package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
  367. package/src/providers/bundled-discovery-providers.ts +0 -98
  368. package/src/providers/configured-openai-providers.ts +0 -120
  369. package/src/providers/operations/model-discovery-cache.ts +0 -34
  370. package/src/providers/operations/probe-fetch.ts +0 -15
  371. package/src/providers/operations/provider-version-cache.ts +0 -30
  372. package/src/providers/provider-capabilities.ts +0 -33
  373. package/src/providers/provider-credential-envelope.ts +0 -43
  374. /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-DIXLjojg.js} +0 -0
  375. /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-DIXLjojg.js.gz} +0 -0
@@ -167,6 +167,14 @@ export function materializeBuddyDeviceBinding(
167
167
  * the operator pinned them in `auth_state` (there is no reliable source to
168
168
  * invent them from). An opaque API key carries no uid and yields nothing.
169
169
  */
170
+ /** A short printable identity claim from a JWT payload, or `undefined`. */
171
+ function identityClaim(claims: Record<string, unknown> | undefined, name: string): string | undefined {
172
+ if (claims === undefined) return undefined;
173
+ const value = claims[name];
174
+ if (typeof value === "string" && value.trim().length > 0 && value.trim().length <= 128) return value.trim();
175
+ return undefined;
176
+ }
177
+
170
178
  export function buddyOAuthIdentityHeaders(
171
179
  context?: ProviderDispatchContext,
172
180
  ): Record<string, string> {
@@ -175,15 +183,20 @@ export function buddyOAuthIdentityHeaders(
175
183
  const uid = buddyAccountUid(token) ?? context?.credential.account_id ?? "";
176
184
  if (uid.length === 0) return {};
177
185
  const enterpriseId = buddyEnterprise(context);
186
+ // The reference gateway reads `id_source` / `token_source` from the JWT
187
+ // payload and the account record, not from stored auth state — an account
188
+ // authorized before either was persisted still carries them on the wire.
189
+ // Precedence matches the donor (`client_profiles._identity_claim_headers`):
190
+ // account fields first, then JWT claims, then legacy `auth_state`.
191
+ const claims = token.length > 0 ? decodeJwtPayload(token) : undefined;
178
192
  const state = context?.credential.auth_state;
179
193
  const idSource =
180
- typeof state?.["idSource"] === "string" && (state["idSource"] as string).trim().length > 0
181
- ? (state["idSource"] as string).trim()
182
- : undefined;
194
+ identityClaim(claims, "id_source") ??
195
+ identityClaim(claims, "idSource") ??
196
+ identityClaim(state as Record<string, unknown> | undefined, "idSource");
183
197
  const method =
184
- typeof state?.["authMethod"] === "string" && (state["authMethod"] as string).trim().length > 0
185
- ? (state["authMethod"] as string).trim()
186
- : undefined;
198
+ identityClaim(claims, "token_source") ??
199
+ identityClaim(state as Record<string, unknown> | undefined, "authMethod");
187
200
  const headers: Record<string, string> = { "X-User-Id": uid };
188
201
  if (method !== undefined) headers["X-Auth-Method"] = method;
189
202
  if (idSource !== undefined) headers["X-Id-Source"] = idSource;
@@ -1,8 +1,8 @@
1
1
  // dynamic-import-ok: lazy per-realm capability map. Each buddy regional
2
2
  // product (cb/cbcn) imports its adapter + oauth + discovery on first use so a
3
3
  // deployment that only serves one realm never drags the others into memory.
4
- import type { ProviderModuleCapabilities } from "../../provider-capabilities";
5
- import { oauthCapability, quotaCapability } from "../../provider-capabilities";
4
+ import type { ProviderModuleCapabilities } from "../../provider-registry";
5
+ import { oauthCapability, quotaCapability } from "../../provider-registry";
6
6
 
7
7
  type BuddyProviderId = "cb" | "cbcn";
8
8
 
@@ -168,6 +168,9 @@ export function createCodeBuddyCnAdapter(fetchImpl?: typeof fetch): ProviderAdap
168
168
  baseUrl: CODEBUDDY_CN_BASE_URL,
169
169
  variant: "CLI",
170
170
  prePayload: codeBuddyCnPrePayload,
171
+ // The CN CLI profile gzips the chat body (donor behavior). The console
172
+ // channel fallback is an intl-only recovery; CN has no second endpoint.
173
+ requestCompression: "gzip",
171
174
  ...(fetchImpl ? { fetchImpl } : {}),
172
175
  }),
173
176
  {
@@ -160,6 +160,10 @@ export function createCodeBuddyAdapter(fetchImpl?: typeof fetch): ProviderAdapte
160
160
  variant: "IDE",
161
161
  cliIdentity: true,
162
162
  prePayload: codeBuddyIntlPrePayload,
163
+ // The intl CLI profile gzips the chat body and recovers a channel-check
164
+ // 400 by retrying once on the console chat endpoint (donor behavior).
165
+ requestCompression: "gzip",
166
+ channelFallbackPath: "/console/chat/completions",
163
167
  ...(fetchImpl ? { fetchImpl } : {}),
164
168
  }),
165
169
  {
@@ -87,7 +87,7 @@ export async function codebuddyHeaders(
87
87
  };
88
88
  }
89
89
  const purpose = buddyAgentPurpose(context, request);
90
- return {
90
+ const headers: Record<string, string> = {
91
91
  accept: "text/event-stream",
92
92
  "User-Agent": ua,
93
93
  "X-Product": "SaaS",
@@ -100,28 +100,29 @@ export async function codebuddyHeaders(
100
100
  "X-Domain": codebuddyDomain(variant),
101
101
  "x-requested-with": "XMLHttpRequest",
102
102
  "x-codebuddy-request": "1",
103
- // The effective agent purpose, matching the reference's dynamic value.
104
- // The reference does not invent `conversation`; it sends the purpose only
105
- // when one exists.
106
- ...(purpose === undefined ? {} : { "X-Agent-Purpose": purpose }),
107
- // The upstream's channel check reads the turn-level aggregation key, and a
108
- // chat request without it is refused for a missing conversation id.
109
- ...conversation,
110
- // Per-account device binding (qimei36) — distinct device per account.
111
- ...(() => {
112
- const binding = resolveBuddyDeviceBinding(context);
113
- return binding === undefined ? {} : { "X-Device-Id-List": JSON.stringify({ qimei36: binding }) };
114
- })(),
115
- // The desktop client sends the device-token pair on both realms. Captured
116
- // token (auth_state / device-token file) when present, otherwise the
117
- // official failure placeholder. (CLI branch above already returned without
118
- // device headers.)
119
- ...(await buddyDeviceTokenHeaders({
120
- realm: identity === "IDE" ? "intl" : "cn",
121
- context,
122
- cliIdentity,
123
- })),
124
103
  };
104
+ // The effective agent purpose, matching the reference's dynamic value.
105
+ // The reference does not invent `conversation`; it sends the purpose only
106
+ // when one exists.
107
+ if (purpose !== undefined) headers["X-Agent-Purpose"] = purpose;
108
+ // The upstream's channel check reads the turn-level aggregation key, and a
109
+ // chat request without it is refused for a missing conversation id.
110
+ Object.assign(headers, conversation);
111
+ // Per-account device binding (qimei36) — distinct device per account.
112
+ const binding = resolveBuddyDeviceBinding(context);
113
+ if (binding !== undefined) {
114
+ headers["X-Device-Id-List"] = JSON.stringify({ qimei36: binding });
115
+ }
116
+ // The desktop client sends the device-token pair on both realms. Captured
117
+ // token (auth_state / device-token file) when present, otherwise the
118
+ // official failure placeholder. (CLI branch above already returned without
119
+ // device headers.)
120
+ Object.assign(headers, await buddyDeviceTokenHeaders({
121
+ realm: identity === "IDE" ? "intl" : "cn",
122
+ context,
123
+ cliIdentity,
124
+ }));
125
+ return headers;
125
126
  }
126
127
 
127
128
  /**
@@ -140,6 +141,14 @@ export function codebuddyAdapterConfig(args: {
140
141
  ) => void;
141
142
  fetchImpl?: typeof fetch;
142
143
  cliIdentity?: boolean;
144
+ /**
145
+ * Body compression for chat dispatches. The CodeBuddy CLI contract (donor
146
+ * `codebuddy2api`) gzips the JSON body for the CLI profiles and sends
147
+ * `Accept-Encoding: identity`. `"json"` (default) keeps plain JSON.
148
+ */
149
+ requestCompression?: "json" | "gzip";
150
+ /** When set, retry a channel-check 400 against this path (console fallback). */
151
+ channelFallbackPath?: string;
143
152
  }): OpenAICompatibleAdapterConfig {
144
153
  return withBearerAuthentication({
145
154
  provider_id: args.providerId,
@@ -147,6 +156,12 @@ export function codebuddyAdapterConfig(args: {
147
156
  buildExtraHeaders: (context, request) =>
148
157
  codebuddyHeaders(args.variant, context, request, args.cliIdentity),
149
158
  prePayload: args.prePayload,
159
+ ...(args.requestCompression === undefined
160
+ ? {}
161
+ : { request_compression: args.requestCompression }),
162
+ ...(args.channelFallbackPath === undefined
163
+ ? {}
164
+ : { channel_fallback_path: args.channelFallbackPath }),
150
165
  ...(args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}),
151
166
  });
152
167
  }
@@ -102,9 +102,13 @@ export async function fetchClaudeQuota(
102
102
  credential: string,
103
103
  fetcher: FetchLike,
104
104
  ): Promise<ProviderQuotaResult> {
105
+ // No module-local quota cache: the shared two-tier quota cache owns
106
+ // cross-call dedup, and a local tier keyed by credential only is never
107
+ // invalidated by `debitCachedQuota`, so a sweep within its TTL could
108
+ // resurrect a spent balance over the shared cache. Every call here is a
109
+ // fresh upstream read; the shared cache's in-flight dedup prevents
110
+ // duplicate traffic.
105
111
  const key = sha256Hex(credential);
106
- const cached = quotaCache.get(key);
107
- if (cached !== undefined) return cached;
108
112
  if (quotaCooldown.get(key) !== undefined) {
109
113
  throw new Error("Quota endpoint rejected request: rate limited (cooled down)");
110
114
  }
@@ -131,9 +135,7 @@ export async function fetchClaudeQuota(
131
135
  },
132
136
  observing,
133
137
  );
134
- const result = parseClaudeQuota(body);
135
- quotaCache.set(key, result);
136
- return result;
138
+ return parseClaudeQuota(body);
137
139
  } catch (error: unknown) {
138
140
  // Hammering the usage endpoint on every call is its own rate-limit
139
141
  // risk: back off briefly after a 429 instead of retrying into it.
@@ -143,21 +145,16 @@ export async function fetchClaudeQuota(
143
145
  }
144
146
 
145
147
  /**
146
- * Bounded per-credential quota cache (5 min) plus a short 429 cooldown
147
- * (3 min). Keys are credential hashes, never raw secrets; both caches are
148
+ * Short 429 cooldown (3 min). Keys are credential hashes, never raw secrets;
148
149
  * capped so credential fan-out cannot pin memory.
150
+ *
151
+ * The cooldown is an absolute window (`until = now + QUOTA_COOLDOWN_MS`), not a
152
+ * TTL from read time, so it is a sentinel whose own TTL *is* that window: the
153
+ * entry lives for exactly the cooldown and is gone the moment it closes.
149
154
  */
150
- const QUOTA_CACHE_TTL_MS = 5 * 60_000;
151
155
  const QUOTA_COOLDOWN_MS = 3 * 60_000;
152
- const QUOTA_CACHE_MAX_ENTRIES = 256;
153
- const quotaCache = new TtlCache<ProviderQuotaResult>({
154
- ttlMs: QUOTA_CACHE_TTL_MS,
155
- maxEntries: QUOTA_CACHE_MAX_ENTRIES,
156
- });
157
- // The cooldown is an absolute window (`until = now + QUOTA_COOLDOWN_MS`), not a
158
- // TTL from read time, so it is a sentinel whose own TTL *is* that window: the
159
- // entry lives for exactly the cooldown and is gone the moment it closes.
156
+ const QUOTA_COOLDOWN_MAX_ENTRIES = 256;
160
157
  const quotaCooldown = new TtlCache<true>({
161
158
  ttlMs: QUOTA_COOLDOWN_MS,
162
- maxEntries: QUOTA_CACHE_MAX_ENTRIES,
159
+ maxEntries: QUOTA_COOLDOWN_MAX_ENTRIES,
163
160
  });
@@ -1,12 +1,12 @@
1
1
  import { createApiKeyAdapter } from "../../configured-provider";
2
2
  import type { ProviderDispatchTarget, ProviderAdapter, ProviderDispatchContext } from "../../provider-contracts";
3
3
  import type { ModelDefinition } from "../../provider-model-definition";
4
- import { unwrapProviderToken } from "../../provider-credential-envelope";
4
+ import { unwrapProviderToken } from "../../provider-credentials";
5
5
  import { GatewayError } from "../../../protocol/gateway-error";
6
6
  import type { CanonicalRequest } from "../../../protocol/canonical-model";
7
7
  import { completeRequiredSchema, isRecord } from "../../../protocol/codecs";
8
8
  import { providerBaseUrl } from "../../provider-metadata";
9
- import { getCachedModelDiscovery } from "../../operations/model-discovery-cache";
9
+ import { getCachedModelDiscovery } from "../../operations/provider-caches";
10
10
  import { defineModel } from "../../provider-model-definition";
11
11
  import { clineReasoningReplayPrepares } from "../buddy/buddy-chat-shared";
12
12
  import { clinePlatform, clinePlatformVersion } from "../../operations/cli-platform";
@@ -15,6 +15,8 @@
15
15
  * independent: retain the balance or subscription when the other is unavailable.
16
16
  */
17
17
  import type { FetchLike, ProviderQuotaResult, ProviderQuotaWindow } from "../../quota/quota-contracts";
18
+ import { isoDate } from "../../quota/quota-contracts";
19
+ import { asCoercedNumber, asRecord } from "../../../shared/coerce";
18
20
 
19
21
  const COMMANDCODE_API_BASE = "https://api.commandcode.ai" as const;
20
22
  const REQUEST_TIMEOUT_MS = 15_000;
@@ -73,29 +75,21 @@ async function readJson(
73
75
  }
74
76
  }
75
77
 
76
- function recordValue(value: unknown): Record<string, unknown> | undefined {
77
- return typeof value === "object" && value !== null && !Array.isArray(value)
78
- ? (value as Record<string, unknown>)
79
- : undefined;
80
- }
81
-
78
+ /** Numeric value coerced via the shared helper, or `null` (shared sentinel). */
82
79
  function numberOrNull(value: unknown): number | null {
83
- if (typeof value === "number" && Number.isFinite(value)) return value;
84
- if (typeof value === "string" && value.trim() && Number.isFinite(Number(value))) {
85
- return Number(value);
86
- }
87
- return null;
80
+ return asCoercedNumber(value) ?? null;
88
81
  }
89
82
 
83
+ /**
84
+ * ISO timestamp from a numeric epoch (seconds vs ms via the 1e12 boundary) or
85
+ * a parseable date string, else `null` — the shared `isoDate` semantics. The
86
+ * one local delta kept: a non-positive numeric is `null` (no deadline) rather
87
+ * than an epoch 0/negative instant.
88
+ */
90
89
  function isoTimestamp(value: unknown): string | null {
91
90
  const numeric = numberOrNull(value);
92
- if (numeric !== null) {
93
- if (numeric <= 0) return null;
94
- const date = new Date(numeric > 1_000_000_000_000 ? numeric : numeric * 1000);
95
- return Number.isFinite(date.getTime()) ? date.toISOString() : null;
96
- }
97
- if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) return null;
98
- return new Date(value).toISOString();
91
+ if (numeric !== null && numeric <= 0) return null;
92
+ return isoDate(value);
99
93
  }
100
94
 
101
95
  function withQuery(path: string, params: Readonly<Record<string, string | null>>): string {
@@ -149,8 +143,8 @@ function windowLimits(
149
143
  limits: Record<string, unknown>,
150
144
  ): readonly ProviderQuotaWindow[] {
151
145
  const out: ProviderQuotaWindow[] = [];
152
- const fiveHour = recordValue(limits["fiveHour"]);
153
- const weekly = recordValue(limits["weekly"]);
146
+ const fiveHour = asRecord(limits["fiveHour"]);
147
+ const weekly = asRecord(limits["weekly"]);
154
148
  const push = (source: Record<string, unknown> | undefined, kind: string, label: string) => {
155
149
  if (source === undefined) return;
156
150
  const used = numberOrNull(source["used"]) ?? 0;
@@ -191,9 +185,9 @@ export async function fetchCommandCodeQuota(
191
185
  };
192
186
  }
193
187
 
194
- const whoamiBody = whoami.ok ? recordValue(whoami.value) : undefined;
195
- const user = recordValue(whoamiBody?.["user"]);
196
- const organization = recordValue(whoamiBody?.["org"]);
188
+ const whoamiBody = whoami.ok ? asRecord(whoami.value) : undefined;
189
+ const user = asRecord(whoamiBody?.["user"]);
190
+ const organization = asRecord(whoamiBody?.["org"]);
197
191
  const orgId = typeof organization?.["id"] === "string" ? organization["id"] : null;
198
192
  const accountLabel =
199
193
  (typeof user?.["email"] === "string" && user["email"]) ||
@@ -215,8 +209,8 @@ export async function fetchCommandCodeQuota(
215
209
  };
216
210
  }
217
211
 
218
- const subscriptionBody = subscriptionRaw.ok ? recordValue(subscriptionRaw.value) : undefined;
219
- const planRecord = recordValue(subscriptionBody?.["data"]);
212
+ const subscriptionBody = subscriptionRaw.ok ? asRecord(subscriptionRaw.value) : undefined;
213
+ const planRecord = asRecord(subscriptionBody?.["data"]);
220
214
  const planId = typeof planRecord?.["planId"] === "string" ? planRecord["planId"] : null;
221
215
  const parsedPlan = planId === null ? null : commandCodePlan(planId);
222
216
  const plan =
@@ -225,12 +219,12 @@ export async function fetchCommandCodeQuota(
225
219
  (creditsRaw.ok ? "Credits" : null);
226
220
  const periodEnd = isoTimestamp(planRecord?.["currentPeriodEnd"]);
227
221
 
228
- const creditsBody = creditsRaw.ok ? recordValue(creditsRaw.value) : undefined;
222
+ const creditsBody = creditsRaw.ok ? asRecord(creditsRaw.value) : undefined;
229
223
  const windows: ProviderQuotaWindow[] = [];
230
224
  if (creditsBody !== undefined) {
231
- const credits = recordValue(creditsBody["credits"]);
225
+ const credits = asRecord(creditsBody["credits"]);
232
226
  if (credits !== undefined) windows.push(...creditWindows(credits, parsedPlan?.cap ?? null, periodEnd));
233
- const limits = recordValue(creditsBody["windowLimits"]);
227
+ const limits = asRecord(creditsBody["windowLimits"]);
234
228
  if (limits !== undefined) windows.push(...windowLimits(limits));
235
229
  }
236
230
 
@@ -41,7 +41,6 @@ import {
41
41
  CURSOR_WEBSITE_URL,
42
42
  SESSION_REFRESH_TIMEOUT_MS,
43
43
  SIGN_IN_POLICY_VIOLATION,
44
- TOKEN_EXPIRY_THRESHOLD_S,
45
44
  isExchangeableApiKey,
46
45
  } from "./cursor-shared";
47
46
 
@@ -92,13 +91,6 @@ export function decodeJwtExpiryMs(token: string): number | undefined {
92
91
  return exp * 1000;
93
92
  }
94
93
 
95
- /** True when a JWT expires within the refresh threshold. */
96
- export function isExpiringSoon(token: string, thresholdSeconds = TOKEN_EXPIRY_THRESHOLD_S): boolean {
97
- const expiry = decodeJwtExpiryMs(token);
98
- if (expiry === undefined) return false;
99
- return expiry - Date.now() < thresholdSeconds * 1000;
100
- }
101
-
102
94
  /**
103
95
  * The account identity Cursor itself asserts, from the access token's `sub`.
104
96
  *
@@ -389,15 +389,22 @@ function globFiles(args: Record<string, unknown>, root: string): Record<string,
389
389
  return { files: out };
390
390
  }
391
391
 
392
- /** Minimal glob matcher: `*` (within a segment), `**` (any depth), `?`. */
392
+ /**
393
+ * Minimal glob matcher: `*` (within a segment), `**` (any depth), `?`.
394
+ * The value is normalized to forward slashes first, so a Windows `\` path
395
+ * matches exactly like a POSIX `/` path — otherwise `*.ts` matches
396
+ * `src\a.ts` on Windows but not `src/a.ts`, and the same pattern behaves
397
+ * differently depending on which separator `relative()` happened to emit.
398
+ */
393
399
  function simpleGlobMatch(pattern: string, value: string): boolean {
400
+ const normalized = value.replace(/\\/g, "/");
394
401
  const re = pattern
395
402
  .replace(/[.+^${}()|[\]\\]/g, "\\$&")
396
403
  .replace(/\*\*/g, "__GLOBSTAR__")
397
404
  .replace(/\*/g, "[^/]*")
398
405
  .replace(/__GLOBSTAR__/g, ".*")
399
406
  .replace(/\?/g, ".");
400
- return new RegExp(`^${re}$`).test(value);
407
+ return new RegExp(`^${re}$`).test(normalized);
401
408
  }
402
409
 
403
410
  function grepFiles(args: Record<string, unknown>, root: string, pi: boolean): Record<string, unknown> {
@@ -219,7 +219,9 @@ export function systemPromptFrom(request: CanonicalRequest): string | undefined
219
219
  */
220
220
  export function historyFromRequest(request: CanonicalRequest): CursorHistoryEntry[] {
221
221
  const entries: CursorHistoryEntry[] = [];
222
- for (const message of request.messages) {
222
+ const liveIndex = lastLiveUserMessageIndex(request);
223
+ for (const [index, message] of request.messages.entries()) {
224
+ if (index === liveIndex) continue;
223
225
  const text = messageText(message);
224
226
  if (text.trim().length === 0) continue;
225
227
  if (message.role === "assistant") entries.push({ role: "assistant", content: text });
@@ -235,9 +237,6 @@ export function buildRunRequest(input: {
235
237
  readonly text: string;
236
238
  readonly modelId: string;
237
239
  readonly conversationId: string;
238
- /** Stable parent group; unlike the conversation id it survives a rebase. */
239
- readonly conversationGroupId?: string;
240
- readonly runId?: string;
241
240
  readonly messageId?: string;
242
241
  readonly history?: readonly CursorHistoryEntry[];
243
242
  /** Cursor-declared parameter tuple (effort/context/fast) for this model. */
@@ -277,12 +276,17 @@ export function buildRunRequest(input: {
277
276
  userMessageAction["request_context"] = requestContext;
278
277
  const runRequest: Record<string, unknown> = {
279
278
  conversation_id: input.conversationId,
280
- conversation_group_id: input.conversationGroupId ?? input.conversationId,
281
- run_id: input.runId ?? messageId,
282
279
  action:
283
280
  input.action === "resume"
284
281
  ? { resume_action: { request_context: requestContext } }
285
282
  : { user_message_action: userMessageAction },
283
+ // `model_details` (#3) carries the safe wire id; `requested_model` (#9)
284
+ // carries the parameter tuple. Matches `agent.proto` exactly — the old
285
+ // schema invented extra fields that have no wire meaning.
286
+ model_details: {
287
+ model_id: input.modelId,
288
+ max_mode: input.maxMode ?? false,
289
+ },
286
290
  requested_model: {
287
291
  model_id: input.modelId,
288
292
  max_mode: input.maxMode ?? false,
@@ -290,8 +294,6 @@ export function buildRunRequest(input: {
290
294
  },
291
295
  conversation_state: seeded ? buildSeedConversationState() : input.conversationState,
292
296
  mcp_tools: { mcp_tools: [] },
293
- unknown_flag: 0,
294
- field_12: 0,
295
297
  };
296
298
  return encodeMessage("AgentClientMessage", { run_request: runRequest });
297
299
  }
@@ -309,9 +311,12 @@ export function buildExecStreamClose(execId: number, execIdString?: string): Uin
309
311
  }
310
312
 
311
313
  /** Stops the live Run, as Cursor CLI does before closing the stream. */
312
- export function buildCancelAction(reason: string): Uint8Array {
314
+ export function buildCancelAction(_reason: string): Uint8Array {
315
+ // `agent.proto` CancelAction is empty — the oneof presence is the signal,
316
+ // and the reason travels out-of-band (stream close). Sending a `reason`
317
+ // field would encode bytes the server does not expect.
313
318
  return encodeMessage("AgentClientMessage", {
314
- conversation_action: { cancel_action: { reason } },
319
+ conversation_action: { cancel_action: {} },
315
320
  });
316
321
  }
317
322
 
@@ -530,12 +535,21 @@ function toolResultText(part: Extract<ContentPart, { kind: "toolResult" }>): str
530
535
  * only the current prompt.
531
536
  */
532
537
  export function liveUserText(request: CanonicalRequest): string {
538
+ const index = lastLiveUserMessageIndex(request);
539
+ if (index === -1) return "";
540
+ const message = request.messages[index];
541
+ if (message === undefined) return "";
542
+ return messageText(message);
543
+ }
544
+
545
+ /** Index of the current user/tool turn Cursor should send as the live prompt. */
546
+ function lastLiveUserMessageIndex(request: CanonicalRequest): number {
533
547
  for (let index = request.messages.length - 1; index >= 0; index -= 1) {
534
548
  const message = request.messages[index];
535
549
  if (message === undefined) continue;
536
550
  if (message.role !== "user" && message.role !== "tool") continue;
537
551
  const text = messageText(message);
538
- if (text.trim().length > 0) return text;
552
+ if (text.trim().length > 0) return index;
539
553
  }
540
- return "";
554
+ return -1;
541
555
  }
@@ -83,11 +83,14 @@ export const SESSION_REFRESH_TIMEOUT_MS = 20_000;
83
83
 
84
84
  /**
85
85
  * How long a live Cursor Run is held open waiting for the client's next
86
- * tool-result request. Matches the donor's continuation default: a tool round
87
- * the client never answers must not hold an HTTP/2 stream, an account lease, or
88
- * a pool slot for the life of the process.
86
+ * tool-result request. Matches the donor's relay hold (`CURSOR_RELAY_IDLE_
87
+ * TIMEOUT_MS` in oh-my-pi: 30 minutes): a tool round the client never answers
88
+ * must not hold an HTTP/2 stream, an account lease, or a pool slot for the life
89
+ * of the process, but a human-approved tool round often takes longer than ten
90
+ * minutes — a shorter TTL expired the Run mid-approval and forced the client to
91
+ * resend the whole conversation.
89
92
  */
90
- export const RUN_IDLE_TTL_MS = 10 * 60_000;
93
+ export const RUN_IDLE_TTL_MS = 30 * 60_000;
91
94
 
92
95
  /**
93
96
  * Any HTTPS subdomain of `cursor.sh` is an acceptable Run host: the region