cartethyia 2.5.2 → 2.5.4

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 (277) hide show
  1. package/dist/dashboard/assets/{BannedPage-MsQaeQTs.js → BannedPage-C2orBsI2.js} +1 -1
  2. package/dist/dashboard/assets/BannedPage-C2orBsI2.js.gz +0 -0
  3. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +1 -0
  4. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolsPage-DfWHTJ7v.js → CliToolsPage-BV6pxVQo.js} +1 -1
  6. package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
  7. package/dist/dashboard/assets/{CombosPage-BrCBQDAc.js → CombosPage-Bsan4679.js} +1 -1
  8. package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
  9. package/dist/dashboard/assets/{ConsoleLogPage-C6Uh33er.js → ConsoleLogPage-CW-wPmJ6.js} +1 -1
  10. package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
  11. package/dist/dashboard/assets/{CustomizationPage-8aS9hyJu.js → CustomizationPage-0p8uX7vB.js} +1 -1
  12. package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
  13. package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js +1 -0
  14. package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
  15. package/dist/dashboard/assets/{LoginPage-C8hKyXBe.js → LoginPage-BvzCx8Kr.js} +1 -1
  16. package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
  17. package/dist/dashboard/assets/{ModelPicker-CWkTUtpX.js → ModelPicker-BY1aKEkP.js} +1 -1
  18. package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
  19. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +1 -0
  20. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
  21. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +3 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
  23. package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js +1 -0
  24. package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
  25. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +7 -0
  26. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
  27. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +1 -0
  28. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
  29. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +1 -0
  30. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
  31. package/dist/dashboard/assets/{SetupPage-CXjUlfqr.js → SetupPage-DDbMQsoo.js} +1 -1
  32. package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
  33. package/dist/dashboard/assets/{SortableList-C-_oD3F2.js → SortableList-CB3Z7VTf.js} +1 -1
  34. package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
  35. package/dist/dashboard/assets/{StudioPage-u9IxmYOI.js → StudioPage-D95UCPHI.js} +3 -3
  36. package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
  37. package/dist/dashboard/assets/{UsagePage-BEVLVrwP.js → UsagePage-Cnae9SWA.js} +13 -13
  38. package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
  39. package/dist/dashboard/assets/{api-keys-uNywYlfv.js → api-keys-VY68Mhd1.js} +1 -1
  40. package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
  41. package/dist/dashboard/assets/app-COlK95oi.css +2 -0
  42. package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
  43. package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-Chi7pCnt.js} +1 -1
  44. package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
  45. package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-D4pQapKj.js} +1 -1
  46. package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
  47. package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-DH6cg-_B.js} +1 -1
  48. package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
  49. package/dist/dashboard/assets/{card--AtFedAq.js → card-BKqlUOe4.js} +1 -1
  50. package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
  51. package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-fTgljTGA.js} +1 -1
  52. package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
  53. package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-D5Ys_sM0.js} +1 -1
  54. package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.js.gz +0 -0
  55. package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
  56. package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
  57. package/dist/dashboard/assets/{dialog-BtpsJU98.js → dialog-BYj-8WuK.js} +1 -1
  58. package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
  59. package/dist/dashboard/assets/{drawer-WwwtzihF.js → drawer-CmXIZ6H1.js} +1 -1
  60. package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
  61. package/dist/dashboard/assets/entry-CBg5zeu8.js +1 -0
  62. package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
  63. package/dist/dashboard/assets/entry-CQBr6U9c.js +1 -0
  64. package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-CgdSAzEV.js +4 -0
  66. package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
  67. package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-BlptL8Uf.js} +1 -1
  68. package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
  69. package/dist/dashboard/assets/{index-DCBtROoK.js → index-js6TJvdr.js} +2 -2
  70. package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
  71. package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-DUWDSmDV.js} +1 -1
  72. package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
  73. package/dist/dashboard/assets/{input-BG41et9G.js → input-LVjgZq4f.js} +1 -1
  74. package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
  75. package/dist/dashboard/assets/{layout-C8mer_Ak.js → layout-CPfxpsfy.js} +1 -1
  76. package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
  77. package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-DupvLfDx.js} +1 -1
  78. package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
  79. package/dist/dashboard/assets/{live-BJGYRBnR.js → live-C4pEJUqI.js} +1 -1
  80. package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
  81. package/dist/dashboard/assets/model-bans-BRiuufkB.js +1 -0
  82. package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
  83. package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-B4ROwk8L.js} +1 -1
  84. package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
  85. package/dist/dashboard/assets/numbers-YNcHvRY7.js +2 -0
  86. package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
  87. package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-gmjzC3RS.js} +1 -1
  88. package/dist/dashboard/assets/{query-keys-D7wX2vM5.js.gz → query-keys-gmjzC3RS.js.gz} +0 -0
  89. package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-C7tBT5o-.js} +1 -1
  90. package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
  91. package/dist/dashboard/assets/{select-DsX5QT8K.js → select-WUCXorXw.js} +2 -2
  92. package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
  93. package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-C_zKl1GS.js} +1 -1
  94. package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
  95. package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-_3O0F8Xn.js} +1 -1
  96. package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
  97. package/dist/dashboard/assets/{state-DYdQmdAl.js → state-BgTxHcJS.js} +1 -1
  98. package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
  99. package/dist/dashboard/assets/switch-DEjTtOWf.js +1 -0
  100. package/dist/dashboard/assets/switch-DEjTtOWf.js.gz +0 -0
  101. package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
  102. package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
  103. package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-BWSC0UAS.js} +1 -1
  104. package/dist/dashboard/assets/useMutation-BWSC0UAS.js.gz +0 -0
  105. package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
  106. package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
  107. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  108. package/dist/dashboard/index.html +1 -1
  109. package/dist/dashboard/index.html.gz +0 -0
  110. package/dist/dashboard/precompressed-assets.json +54 -54
  111. package/package.json +1 -1
  112. package/scripts/cli/autostart.ts +237 -0
  113. package/scripts/cli/command/console-menu.ts +244 -246
  114. package/scripts/cli/command/tray.ts +208 -204
  115. package/scripts/cli/command/update.ts +27 -10
  116. package/scripts/cli/main.ts +28 -1
  117. package/src/boot/app.ts +693 -693
  118. package/src/boot/dependencies.ts +646 -616
  119. package/src/config/spec.ts +81 -2
  120. package/src/config/telemetry.ts +46 -0
  121. package/src/console/api-keys/bucket-spend.ts +15 -4
  122. package/src/console/backup/contracts.ts +15 -2
  123. package/src/console/backup/routes.ts +52 -1
  124. package/src/console/backup/service.ts +60 -4
  125. package/src/console/console-router.ts +8 -0
  126. package/src/console/data-management/contracts.ts +93 -0
  127. package/src/console/data-management/operations.ts +229 -0
  128. package/src/console/data-management/routes.ts +104 -0
  129. package/src/console/domain-registration.ts +39 -0
  130. package/src/console/observability/contracts.ts +524 -485
  131. package/src/console/observability/store.ts +1115 -1018
  132. package/src/console/providers/catalog/contracts.ts +6 -0
  133. package/src/console/providers/catalog/provider-operations.ts +20 -0
  134. package/src/console/providers/catalog/routes.ts +7 -0
  135. package/src/console/providers/catalog/store.ts +22 -1
  136. package/src/console/quota/account-quota-shared.ts +52 -39
  137. package/src/console/quota/refresh.ts +441 -429
  138. package/src/console/routing/pools/import-parse.ts +206 -0
  139. package/src/console/routing/pools/operations.ts +543 -501
  140. package/src/console/routing/pools/routes.ts +181 -164
  141. package/src/console/settings/contracts.ts +37 -0
  142. package/src/console/settings/store.ts +51 -0
  143. package/src/console/share/share-router.ts +6 -9
  144. package/src/console/updates/routes.ts +16 -0
  145. package/src/main.ts +10 -0
  146. package/src/observability/event-compaction.ts +315 -0
  147. package/src/observability/event-day-index.ts +442 -0
  148. package/src/observability/event-log.ts +152 -3
  149. package/src/observability/payload-capture.ts +373 -344
  150. package/src/observability/payload-store.ts +440 -366
  151. package/src/observability/telemetry-status.ts +28 -16
  152. package/src/persistence/health-event-flush.ts +36 -0
  153. package/src/persistence/schema.ts +1402 -1360
  154. package/src/persistence/stores/api-key-store.ts +6 -0
  155. package/src/persistence/stores/share-store.ts +583 -556
  156. package/src/persistence/stores/telemetry-store.ts +237 -214
  157. package/src/protocol/codecs.ts +1 -2
  158. package/src/protocol/image-clamp.ts +19 -1
  159. package/src/protocol/registry.ts +8 -0
  160. package/src/protocol/request/gemini.ts +9 -2
  161. package/src/protocol/request/messages.ts +14 -2
  162. package/src/protocol/response/gemini.ts +18 -4
  163. package/src/providers/adapters/compatible-adapter.ts +3 -0
  164. package/src/providers/bundled-discovery-providers.ts +122 -119
  165. package/src/providers/configured-openai-providers.ts +136 -134
  166. package/src/providers/default-registry.ts +5 -4
  167. package/src/providers/discovery/openai-model-discovery.ts +5 -0
  168. package/src/providers/discovery/openrouter-free-roster.ts +15 -17
  169. package/src/providers/integrations/antigravity/antigravity-quota.ts +11 -3
  170. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +4 -4
  171. package/src/providers/integrations/buddy/workbuddy/shared.ts +201 -200
  172. package/src/providers/integrations/cline/cline-quota.ts +132 -128
  173. package/src/providers/integrations/codex/codex-headers.ts +25 -39
  174. package/src/providers/integrations/codex/codex.ts +2 -29
  175. package/src/providers/integrations/cursor/cursor-quota.ts +310 -0
  176. package/src/providers/integrations/cursor/cursor-request.ts +616 -471
  177. package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
  178. package/src/providers/integrations/cursor/cursor.ts +1485 -960
  179. package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
  180. package/src/providers/integrations/factory/factory.ts +8 -1
  181. package/src/providers/integrations/gemini.ts +6 -1
  182. package/src/providers/integrations/grok/grok-quota.ts +279 -177
  183. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
  184. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
  185. package/src/providers/integrations/opencode/provider.ts +531 -532
  186. package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
  187. package/src/providers/integrations/tokenharbor.ts +2 -2
  188. package/src/providers/integrations/trae/trae-catalog.ts +5 -3
  189. package/src/providers/integrations/trae/trae-quota.ts +384 -0
  190. package/src/providers/operations/account-health-service.ts +1523 -1457
  191. package/src/providers/operations/client-versions.ts +9 -2
  192. package/src/providers/provider-contracts.ts +7 -0
  193. package/src/providers/provider-metadata.ts +13 -1
  194. package/src/providers/quota/quota-support.ts +8 -0
  195. package/src/security/api-key-auth.ts +504 -494
  196. package/src/transport/dispatch/attempt-finalize.ts +46 -5
  197. package/src/transport/dispatch/generation.ts +6 -0
  198. package/src/transport/dispatch/proxy-request.ts +33 -1
  199. package/src/transport/dispatch/ratelimit-capture.ts +37 -0
  200. package/src/transport/dispatch/streaming/stream.ts +30 -2
  201. package/src/transport/dispatch/upstream.ts +9 -0
  202. package/src/transport/request/preparer.ts +7 -6
  203. package/src/transport/request/state.ts +9 -0
  204. package/src/transport/request/tenant-preferences.ts +60 -0
  205. package/src/transport/routing/route-catalog.ts +854 -799
  206. package/dist/dashboard/assets/BannedPage-MsQaeQTs.js.gz +0 -0
  207. package/dist/dashboard/assets/CliToolDetailPage-BO9nKg9w.js +0 -1
  208. package/dist/dashboard/assets/CliToolDetailPage-BO9nKg9w.js.gz +0 -0
  209. package/dist/dashboard/assets/CliToolsPage-DfWHTJ7v.js.gz +0 -0
  210. package/dist/dashboard/assets/CombosPage-BrCBQDAc.js.gz +0 -0
  211. package/dist/dashboard/assets/ConsoleLogPage-C6Uh33er.js.gz +0 -0
  212. package/dist/dashboard/assets/CustomizationPage-8aS9hyJu.js.gz +0 -0
  213. package/dist/dashboard/assets/HealthEventsModal-qomXThxX.js +0 -1
  214. package/dist/dashboard/assets/HealthEventsModal-qomXThxX.js.gz +0 -0
  215. package/dist/dashboard/assets/LoginPage-C8hKyXBe.js.gz +0 -0
  216. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
  217. package/dist/dashboard/assets/OverviewPage-C-ExCy1V.js +0 -1
  218. package/dist/dashboard/assets/OverviewPage-C-ExCy1V.js.gz +0 -0
  219. package/dist/dashboard/assets/ProviderDetailPage-tr811uaF.js +0 -3
  220. package/dist/dashboard/assets/ProviderDetailPage-tr811uaF.js.gz +0 -0
  221. package/dist/dashboard/assets/ProvidersPage-BS9IEpvv.js +0 -1
  222. package/dist/dashboard/assets/ProvidersPage-BS9IEpvv.js.gz +0 -0
  223. package/dist/dashboard/assets/ProxyPage-Rn_qXScb.js +0 -7
  224. package/dist/dashboard/assets/ProxyPage-Rn_qXScb.js.gz +0 -0
  225. package/dist/dashboard/assets/QuotaPage-BihkgQSq.js +0 -1
  226. package/dist/dashboard/assets/QuotaPage-BihkgQSq.js.gz +0 -0
  227. package/dist/dashboard/assets/SettingsPage-DhiEjm6T.js +0 -1
  228. package/dist/dashboard/assets/SettingsPage-DhiEjm6T.js.gz +0 -0
  229. package/dist/dashboard/assets/SetupPage-CXjUlfqr.js.gz +0 -0
  230. package/dist/dashboard/assets/SortableList-C-_oD3F2.js.gz +0 -0
  231. package/dist/dashboard/assets/StudioPage-u9IxmYOI.js.gz +0 -0
  232. package/dist/dashboard/assets/UsagePage-BEVLVrwP.js.gz +0 -0
  233. package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
  234. package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
  235. package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
  236. package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
  237. package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
  238. package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
  239. package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
  240. package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
  241. package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
  242. package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
  243. package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
  244. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  245. package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
  246. package/dist/dashboard/assets/entry-Dj9AR0gY.js +0 -4
  247. package/dist/dashboard/assets/entry-Dj9AR0gY.js.gz +0 -0
  248. package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
  249. package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
  250. package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
  251. package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
  252. package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
  253. package/dist/dashboard/assets/index-DCBtROoK.js.gz +0 -0
  254. package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
  255. package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
  256. package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
  257. package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
  258. package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
  259. package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
  260. package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
  261. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  262. package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
  263. package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
  264. package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
  265. package/dist/dashboard/assets/select-DsX5QT8K.js.gz +0 -0
  266. package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
  267. package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
  268. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  269. package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
  270. package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
  271. package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
  272. package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
  273. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
  274. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  275. package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
  276. /package/dist/dashboard/assets/{app-CafWtVXA.js → app-DCQZRmHY.js} +0 -0
  277. /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-DCQZRmHY.js.gz} +0 -0
@@ -0,0 +1,310 @@
1
+ // Cursor quota.
2
+ //
3
+ // Two surfaces, and the second exists because the first is no longer
4
+ // authoritative on its own:
5
+ //
6
+ // - `{baseUrl}/auth/usage` is Cursor's request-count API from before
7
+ // usage-based plans. Current plans answer it with an uncapped, always-zero
8
+ // `gpt-4` bucket, so a naive read of it reports "no usage" for an account
9
+ // that is in fact spending against a dollar cap.
10
+ // - `cursor.com/api/usage-summary` is the current surface: `individualUsage`
11
+ // with `overall`, `plan` and `onDemand` buckets, in **USD cents**.
12
+ //
13
+ // The donor therefore reads both and prefers the summary when it answers. That
14
+ // preference is kept here: the legacy response is still the only one an api-key
15
+ // account can get, so it cannot simply be dropped, but it must not be allowed
16
+ // to overwrite a real dollar balance with a zero.
17
+ //
18
+ // The summary is only reachable for an OAuth account on the default host, and
19
+ // it needs the user id out of the access token — which is why it is attempted
20
+ // conditionally rather than assumed.
21
+
22
+ import type {
23
+ FetchLike,
24
+ ProviderQuotaResult,
25
+ ProviderQuotaWindow,
26
+ } from "../../quota/quota-contracts";
27
+ import {
28
+ cleanError,
29
+ isoDate,
30
+ number,
31
+ percentWindow,
32
+ quotaRecord,
33
+ record,
34
+ text,
35
+ } from "../../quota/quota-contracts";
36
+ import type { QuotaCollectionContext } from "../../quota/quota-support";
37
+ import { cursorAccountIdentity } from "./cursor-auth";
38
+ import { CURSOR_API_HOST, CURSOR_WEBSITE_HOST } from "./cursor-shared";
39
+
40
+ /** Timestamp keys naming the end of the current window. */
41
+ const RESET_END_KEYS: readonly string[] = [
42
+ "billingCycleEnd",
43
+ "endOfMonth",
44
+ "resetsAt",
45
+ "nextReset",
46
+ ];
47
+
48
+ /** Timestamp keys naming the start, which imply a window one month long. */
49
+ const RESET_START_KEYS: readonly string[] = [
50
+ "startOfMonth",
51
+ "billingCycleStart",
52
+ "startOfBillingCycle",
53
+ ];
54
+
55
+ /**
56
+ * The window's reset, or `null`.
57
+ *
58
+ * A start timestamp implies a monthly window, so it is advanced by one month
59
+ * rather than reported as-is — reporting the start as the reset would say the
60
+ * window had already lapsed.
61
+ */
62
+ function resetsAt(source: Record<string, unknown>): string | null {
63
+ for (const key of RESET_END_KEYS) {
64
+ const value = isoDate(source[key]);
65
+ if (value) return value;
66
+ }
67
+ for (const key of RESET_START_KEYS) {
68
+ const value = isoDate(source[key]);
69
+ if (!value) continue;
70
+ const date = new Date(value);
71
+ if (Number.isNaN(date.getTime())) continue;
72
+ date.setUTCMonth(date.getUTCMonth() + 1);
73
+ return date.toISOString();
74
+ }
75
+ return null;
76
+ }
77
+
78
+ /**
79
+ * A Cursor cents bucket: `used`/`limit`/`remaining` in USD cents.
80
+ *
81
+ * An absent limit is a real shape, not a broken one — some plans carry no cap
82
+ * and only report what has been spent. Those are emitted used-only rather than
83
+ * dropped, which is what used to collapse an account to "no usage data".
84
+ */
85
+ function centsWindow(
86
+ kind: string,
87
+ label: string,
88
+ source: Record<string, unknown>,
89
+ reset: string | null,
90
+ ): ProviderQuotaWindow | null {
91
+ const limit = number(source["limit"] ?? source["maxRequestUsage"] ?? source["amountLimit"] ?? source["usdLimit"]);
92
+ const used = number(source["used"] ?? source["numRequests"] ?? source["amountUsed"] ?? source["usdUsed"]);
93
+ const remaining = number(source["remaining"]);
94
+ if (used === null && remaining === null && limit === null) return null;
95
+ const usedValue = used ?? (remaining !== null && limit !== null ? limit - remaining : null);
96
+ return percentWindow(kind, label, null, reset, usedValue, limit, "independent");
97
+ }
98
+
99
+ /** The legacy `/auth/usage` shape: one object per request bucket. */
100
+ function parseLegacyUsage(payload: unknown): readonly ProviderQuotaWindow[] {
101
+ const root = quotaRecord(payload);
102
+ const reset = resetsAt(root);
103
+ const windows: ProviderQuotaWindow[] = [];
104
+ for (const [key, value] of Object.entries(root)) {
105
+ const bucket = record(value);
106
+ if (!bucket) continue;
107
+ const window = centsWindow(`cursor:requests:${key.toLowerCase()}`, `${key} requests`, bucket, reset);
108
+ if (!window) continue;
109
+ // A bucket with no cap and nothing used is not a balance. Usage-based plans
110
+ // answer this surface with `gpt-4: {numRequests: 0, maxRequestUsage: null}`
111
+ // while their real spend sits on the summary — showing the empty row put a
112
+ // meaningless "gpt-4 requests" window above the number that matters, on
113
+ // exactly the accounts denominated in dollars.
114
+ if (window.limit === null && (window.used ?? 0) === 0) continue;
115
+ windows.push(window);
116
+ }
117
+ return windows;
118
+ }
119
+
120
+ /**
121
+ * The `usage-summary` shape.
122
+ *
123
+ * `overall` is the spend against the plan; `plan` carries the included rails;
124
+ * `onDemand` is spend beyond them. All three are kept when present — dropping
125
+ * on-demand because the plan bucket answered would hide the part of the bill
126
+ * that is actually growing.
127
+ */
128
+ function parseUsageSummary(payload: unknown): {
129
+ readonly windows: readonly ProviderQuotaWindow[];
130
+ readonly plan: string | null;
131
+ } {
132
+ const root = record(payload) ?? {};
133
+ const individual = record(root["individualUsage"]);
134
+ if (!individual) return { windows: [], plan: null };
135
+ const reset = resetsAt(root);
136
+ const windows: ProviderQuotaWindow[] = [];
137
+
138
+ // The plan bucket is denominated two ways and the reference reads the
139
+ // percentages, not the dollar figures: an included plan carries `limit: 0`
140
+ // because there is no dollar cap to spend against, so reading `used`/`limit`
141
+ // reported every such account as `0 / 0` — a balance that looks empty and
142
+ // means nothing.
143
+ const plan = record(individual["plan"]);
144
+ const limitCents = number(plan?.["limit"]);
145
+ const limitUsd = limitCents !== null && limitCents > 0 ? limitCents / 100 : null;
146
+
147
+ const percentWindowFor = (
148
+ kind: string,
149
+ label: string,
150
+ percent: number | null,
151
+ withLimit: boolean,
152
+ ): ProviderQuotaWindow | null => {
153
+ if (percent === null) return null;
154
+ const fraction = Math.max(0, percent) / 100;
155
+ if (withLimit && limitUsd !== null) {
156
+ return percentWindow(kind, label, percent, reset, limitUsd * fraction, limitUsd, "independent");
157
+ }
158
+ // No dollar cap, so the percentage is the whole reading: reported against a
159
+ // 100-point scale rather than as a dollar figure the plan does not have.
160
+ return percentWindow(kind, label, percent, reset, percent, 100, "independent");
161
+ };
162
+
163
+ const auto = percentWindowFor(
164
+ "cursor:usd:individual-auto",
165
+ "Cursor Models",
166
+ number(plan?.["autoPercentUsed"]),
167
+ false,
168
+ );
169
+ if (auto) windows.push(auto);
170
+ const api = percentWindowFor(
171
+ "cursor:usd:individual-api",
172
+ "Other Models",
173
+ number(plan?.["apiPercentUsed"]),
174
+ true,
175
+ );
176
+ if (api) windows.push(api);
177
+ if (!auto && !api) {
178
+ const fallback =
179
+ percentWindowFor(
180
+ "cursor:usd:individual-plan",
181
+ "Personal Usage",
182
+ number(plan?.["totalPercentUsed"]),
183
+ true,
184
+ ) ?? centsWindow("cursor:usd:individual-plan", "Personal Usage", plan ?? {}, reset);
185
+ if (fallback) windows.push(fallback);
186
+ }
187
+
188
+ // On-demand is spend beyond the included rails. Kept even when the plan
189
+ // bucket answered, because it is the part of the bill that grows.
190
+ const onDemand = record(individual["onDemand"]);
191
+ if (onDemand && onDemand["enabled"] !== false) {
192
+ const window = centsWindow("cursor:usd:individual-on-demand", "On-demand usage", onDemand, reset);
193
+ if (window) windows.push(window);
194
+ }
195
+
196
+ return { windows, plan: text(individual["planName"] ?? root["membershipType"]) };
197
+ }
198
+
199
+ /**
200
+ * Reads one Cursor account's quota.
201
+ *
202
+ * The summary is preferred when it answers, because the legacy surface reports
203
+ * a permanently-zero bucket on exactly the plans that have a dollar cap. The
204
+ * legacy read is still attempted: it is the only surface an api-key account
205
+ * can reach.
206
+ */
207
+ function authStateUserId(context: QuotaCollectionContext | undefined): string | undefined {
208
+ const raw = context?.auth_state ?? {};
209
+ for (const key of ["userId", "user_id", "uid", "sub"] as const) {
210
+ const value = raw[key];
211
+ if (typeof value === "string" && value.trim()) return value.trim();
212
+ }
213
+ return undefined;
214
+ }
215
+
216
+ /**
217
+ * A stable display identity derived from the credential.
218
+ *
219
+ * Cursor's access token carries the account's real subject
220
+ * (`google-oauth2|user_01K3...`), and that was being shown as the account
221
+ * label — an identity, not a name. It is reduced to a short opaque tag here so
222
+ * the console can tell two Cursor accounts apart without publishing either
223
+ * one's subject.
224
+ *
225
+ * Derived rather than random, so the same account keeps the same tag across
226
+ * refreshes; a tag that changed every read would be worse than the identity it
227
+ * replaced.
228
+ */
229
+ function cursorAccountTag(credential: string): string {
230
+ let hash = 0x811c9dc5;
231
+ for (let index = 0; index < credential.length; index += 1) {
232
+ hash ^= credential.charCodeAt(index);
233
+ hash = Math.imul(hash, 0x01000193) >>> 0;
234
+ }
235
+ return `cursor-${hash.toString(36).padStart(7, "0").slice(0, 7)}`;
236
+ }
237
+
238
+ export async function fetchCursorQuota(
239
+ credential: string,
240
+ fetcher: FetchLike,
241
+ context?: QuotaCollectionContext,
242
+ ): Promise<ProviderQuotaResult> {
243
+ const raw = context?.auth_state ?? {};
244
+ const baseUrl =
245
+ typeof raw["apiEndpoint"] === "string" && raw["apiEndpoint"].trim()
246
+ ? raw["apiEndpoint"].trim().replace(/\/+$/, "")
247
+ : `https://${CURSOR_API_HOST}`;
248
+ const errors: string[] = [];
249
+
250
+ const call = async (url: string, headers: Record<string, string>): Promise<unknown> => {
251
+ const response = await fetcher(url, { method: "GET", headers });
252
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
253
+ return await response.json();
254
+ };
255
+
256
+ const legacy = await call(`${baseUrl}/auth/usage`, {
257
+ accept: "application/json",
258
+ authorization: `Bearer ${credential}`,
259
+ }).then(
260
+ (payload) => parseLegacyUsage(payload),
261
+ (error: unknown) => {
262
+ errors.push(`auth/usage: ${cleanError(error)}`);
263
+ return null;
264
+ },
265
+ );
266
+
267
+ // Attempted for every credential kind, not just OAuth. The legacy surface is
268
+ // the only one an api-key account is *guaranteed*, but gating the summary on
269
+ // `credential_kind` left api-key accounts showing only `gpt-4 requests` — a
270
+ // bucket that reads zero on exactly the plans that have a dollar cap. If the
271
+ // summary answers, it is the better reading whatever kind the row claims.
272
+ const userId = cursorAccountIdentity(credential) ?? authStateUserId(context);
273
+ let summary: { readonly windows: readonly ProviderQuotaWindow[]; readonly plan: string | null } | null = null;
274
+ if (userId && baseUrl === `https://${CURSOR_API_HOST}`) {
275
+ // This surface authenticates by session cookie, not bearer: it answers 401
276
+ // `not_authenticated` for every Authorization header, including a valid
277
+ // one. The cookie is the user id and the token joined by `::`.
278
+ summary = await call(`https://${CURSOR_WEBSITE_HOST}/api/usage-summary`, {
279
+ accept: "application/json",
280
+ cookie: `WorkosCursorSessionToken=${encodeURIComponent(`${userId}::${credential}`)}`,
281
+ }).then(
282
+ (payload) => parseUsageSummary(payload),
283
+ (error: unknown) => {
284
+ errors.push(`usage-summary: ${cleanError(error)}`);
285
+ return null;
286
+ },
287
+ );
288
+ }
289
+
290
+ const summaryWindows = summary?.windows ?? [];
291
+ const windows = summaryWindows.length > 0 ? summaryWindows : (legacy ?? []);
292
+ if (windows.length === 0) {
293
+ return {
294
+ source: "cursor",
295
+ plan: summary?.plan ?? null,
296
+ windows: [],
297
+ accountLabel: cursorAccountTag(credential),
298
+ error: errors.join("; ") || "Cursor quota endpoints did not answer.",
299
+ };
300
+ }
301
+ return {
302
+ source: "cursor",
303
+ plan: summary?.plan ?? null,
304
+ windows,
305
+ accountLabel: cursorAccountTag(credential),
306
+ // Partial reads keep the error so the console can name the quiet surface,
307
+ // but a summary that answered is enough to report a balance.
308
+ error: summaryWindows.length > 0 && errors.length > 0 ? errors.join("; ") : null,
309
+ };
310
+ }