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,315 @@
1
+ /**
2
+ * Compaction for hourly telemetry files: old per-request rows become one
3
+ * summary row per (tenant, API key, day).
4
+ *
5
+ * The problem this solves is growth, and growth alone. Hourly JSONL files were
6
+ * never deleted, so every read of the usage list scanned every file ever
7
+ * written — the cost grew with total volume, not with the number of users. An
8
+ * install serving 20 users got slower purely by serving them longer.
9
+ *
10
+ * Compaction is **accumulation, not sampling**. A compacted row carries the
11
+ * running sums of everything it replaced, so a total that read 2B tokens
12
+ * before compaction still reads 2B after it. Nothing is estimated and nothing
13
+ * is averaged away — the per-request rows are gone, their totals are not.
14
+ *
15
+ * What compaction cannot preserve is anything that needs the individual row:
16
+ * the request list, per-request filters, and the detail drawer. Latency is the
17
+ * interesting middle case — a percentile needs every sample, so compaction
18
+ * keeps a fixed histogram instead. P50 stays available as an approximation and
19
+ * is marked as one rather than silently reported as exact.
20
+ *
21
+ * Retention becomes tiered:
22
+ *
23
+ * 0 .. N days hourly files, complete
24
+ * N .. M days compacted daily summary
25
+ * beyond M deleted
26
+ */
27
+ import type { TelemetryEventRecord } from "./event-log";
28
+ import type { CartethyiaDatabase } from "../persistence/db-handle";
29
+ import { consoleSettings } from "../persistence/schema";
30
+ import { resolveTelemetryWindowMinutes } from "../config/telemetry";
31
+
32
+ /**
33
+ * The compaction age to apply on a pass with no explicit age given.
34
+ *
35
+ * Hourly files are **not** partitioned per tenant — one file per hour holds
36
+ * every tenant's rows — so a per-tenant age cannot be honoured per file. The
37
+ * safest reading of several tenants' preferences is the **largest**: compacting
38
+ * smaller than one tenant asked for moves its requests into the summary before
39
+ * that tenant expected, losing the per-request view of them, while keeping them
40
+ * longer than another wanted only costs disk and a slower scan. Losing detail
41
+ * is not reversible; using more disk is.
42
+ *
43
+ * Falls back to the deployment default when no tenant overrides, and never
44
+ * exceeds the retention window (compacting at or past the delete age would
45
+ * fold a file and discard it in the same pass).
46
+ */
47
+ export async function resolveEffectiveWindowMinutes(db: CartethyiaDatabase): Promise<number> {
48
+ const fallback = resolveTelemetryWindowMinutes();
49
+ try {
50
+ const rows = await db
51
+ .select({ preferences: consoleSettings.preferences })
52
+ .from(consoleSettings);
53
+ let widest = fallback;
54
+ for (const row of rows) {
55
+ const minutes = row.preferences?.telemetryWindowMinutes;
56
+ if (typeof minutes === "number" && Number.isFinite(minutes) && minutes > widest) {
57
+ widest = minutes;
58
+ }
59
+ }
60
+ return Math.floor(widest);
61
+ } catch {
62
+ return fallback;
63
+ }
64
+ }
65
+
66
+ /** Discriminator so a reader can tell a summary from a request row at a glance. */
67
+ const COMPACTED_KIND = "compacted" as const;
68
+
69
+ /**
70
+ * Histogram bucket edges in milliseconds.
71
+ *
72
+ * Fixed rather than adaptive: a reader that wants P50/P95/P99 has to be able to
73
+ * interpret buckets it did not write, and an adaptive layout would have to be
74
+ * stored alongside every row. These follow the gateway's own latency
75
+ * histogram, so a compacted period and a live one read comparably.
76
+ */
77
+ export const LATENCY_BUCKETS_MS = [
78
+ 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000,
79
+ ] as const;
80
+
81
+ export interface CompactedBucket {
82
+ readonly requests: number;
83
+ readonly inputTokens: number;
84
+ readonly cachedTokens: number;
85
+ readonly outputTokens: number;
86
+ readonly reasoningTokens: number;
87
+ readonly estimatedCostUsd: number;
88
+ readonly creditUsed: number;
89
+ readonly succeeded: number;
90
+ readonly failed: number;
91
+ readonly cancelled: number;
92
+ readonly truncated: number;
93
+ readonly latencySum: number;
94
+ readonly latencyCount: number;
95
+ readonly latencyMinMs: number | null;
96
+ readonly latencyMaxMs: number | null;
97
+ /** Counts per fixed latency bucket; `+Inf` is the last, overflow, bucket. */
98
+ readonly latencyHistogram: readonly number[];
99
+ readonly tokensPerSecSum: number;
100
+ readonly tokensPerSecCount: number;
101
+ /** Requests whose cost could not be priced. */
102
+ readonly unpriced: number;
103
+ /**
104
+ * Requests per breakdown dimension, kept so the Usage breakdown survives the
105
+ * source files being deleted.
106
+ *
107
+ * Only the *counts* are kept, never the events: a day's breakdown asks how
108
+ * many requests went to a model, not which ones. That is what makes this
109
+ * small enough to keep — a full event is ~1KB, a count is a few bytes.
110
+ */
111
+ readonly byModel: Readonly<Record<string, number>>;
112
+ readonly byProvider: Readonly<Record<string, number>>;
113
+ readonly byClient: Readonly<Record<string, number>>;
114
+ readonly byClientIp: Readonly<Record<string, number>>;
115
+ }
116
+
117
+ /** One compacted row: the totals of everything it replaced, for one key/day. */
118
+ export interface CompactedRow extends CompactedBucket {
119
+ readonly kind: typeof COMPACTED_KIND;
120
+ /** ISO day (UTC) this row summarises. */
121
+ readonly day: string;
122
+ readonly tenantId: string;
123
+ readonly apiKeyId: string | null;
124
+ }
125
+
126
+ /** A number from a value that may be a numeric string; 0 when unusable. */
127
+ function numeric(value: unknown): number {
128
+ const asNumber = typeof value === "number" ? value : Number(value);
129
+ return Number.isFinite(asNumber) ? asNumber : 0;
130
+ }
131
+
132
+ /** Adds one occurrence to a count map, ignoring an absent key. */
133
+ function bump(
134
+ counts: Readonly<Record<string, number>> | undefined,
135
+ key: string | null | undefined,
136
+ ): Record<string, number> {
137
+ const out: Record<string, number> = { ...(counts ?? {}) };
138
+ const name = typeof key === "string" ? key.trim() : "";
139
+ if (name !== "") out[name] = (out[name] ?? 0) + 1;
140
+ return out;
141
+ }
142
+
143
+ /** Adds two count maps. */
144
+ function mergeCounts(
145
+ a: Readonly<Record<string, number>> | undefined,
146
+ b: Readonly<Record<string, number>> | undefined,
147
+ ): Record<string, number> {
148
+ const out: Record<string, number> = { ...(a ?? {}) };
149
+ for (const [key, value] of Object.entries(b ?? {})) out[key] = (out[key] ?? 0) + value;
150
+ return out;
151
+ }
152
+
153
+ export function emptyBucket(): CompactedBucket {
154
+ return {
155
+ byModel: {},
156
+ byProvider: {},
157
+ byClient: {},
158
+ byClientIp: {},
159
+ requests: 0,
160
+ inputTokens: 0,
161
+ cachedTokens: 0,
162
+ outputTokens: 0,
163
+ reasoningTokens: 0,
164
+ estimatedCostUsd: 0,
165
+ creditUsed: 0,
166
+ succeeded: 0,
167
+ failed: 0,
168
+ cancelled: 0,
169
+ truncated: 0,
170
+ latencySum: 0,
171
+ latencyCount: 0,
172
+ latencyMinMs: null,
173
+ latencyMaxMs: null,
174
+ latencyHistogram: new Array<number>(LATENCY_BUCKETS_MS.length + 1).fill(0),
175
+ tokensPerSecSum: 0,
176
+ tokensPerSecCount: 0,
177
+ unpriced: 0,
178
+ };
179
+ }
180
+
181
+ function bucketIndex(latencyMs: number): number {
182
+ for (let index = 0; index < LATENCY_BUCKETS_MS.length; index += 1) {
183
+ if (latencyMs <= LATENCY_BUCKETS_MS[index]!) return index;
184
+ }
185
+ return LATENCY_BUCKETS_MS.length;
186
+ }
187
+
188
+ /**
189
+ * Folds one request row into a bucket.
190
+ *
191
+ * Every field is a running sum or a min/max, never an average of averages: an
192
+ * average of per-file averages would drift from the true mean as soon as the
193
+ * files held different request counts, and the whole point of compaction is
194
+ * that the totals survive intact.
195
+ */
196
+ export function accumulate(
197
+ into: CompactedBucket,
198
+ event: TelemetryEventRecord,
199
+ ): CompactedBucket {
200
+ const histogram = [...into.latencyHistogram];
201
+ const latency = event.latencyMs;
202
+ if (latency !== null && Number.isFinite(latency)) {
203
+ histogram[bucketIndex(latency)] = (histogram[bucketIndex(latency)] ?? 0) + 1;
204
+ }
205
+ // A cost can arrive as a string: Postgres `numeric` columns come back that
206
+ // way, and so do restored rows. Adding one to a number concatenates instead of
207
+ // summing, which turned a cost of 0.000966 into "00.000966". Coerced here so
208
+ // every downstream sum is arithmetic.
209
+ const priced = numeric(event.estimatedCostUsd);
210
+ return {
211
+ byModel: bump(into.byModel, event.requestedModel ?? event.reportedModel),
212
+ byProvider: bump(into.byProvider, event.providerId),
213
+ byClient: bump(into.byClient, event.sourceSurface),
214
+ byClientIp: bump(into.byClientIp, event.clientIp),
215
+ requests: into.requests + 1,
216
+ inputTokens: into.inputTokens + (event.inputTokens ?? 0),
217
+ cachedTokens: into.cachedTokens + (event.cachedInputTokens ?? 0),
218
+ outputTokens: into.outputTokens + (event.outputTokens ?? 0),
219
+ reasoningTokens: into.reasoningTokens + (event.reasoningTokens ?? 0),
220
+ estimatedCostUsd: into.estimatedCostUsd + priced,
221
+ creditUsed: into.creditUsed + (event.creditUsed ?? 0),
222
+ succeeded: into.succeeded + (event.status === "completed" ? 1 : 0),
223
+ failed: into.failed + (event.status === "failed" ? 1 : 0),
224
+ cancelled: into.cancelled + (event.status === "cancelled" ? 1 : 0),
225
+ truncated: into.truncated + (event.status === "truncated" ? 1 : 0),
226
+ latencySum: into.latencySum + (latency ?? 0),
227
+ latencyCount: into.latencyCount + (latency === null ? 0 : 1),
228
+ latencyMinMs:
229
+ latency === null
230
+ ? into.latencyMinMs
231
+ : into.latencyMinMs === null
232
+ ? latency
233
+ : Math.min(into.latencyMinMs, latency),
234
+ latencyMaxMs:
235
+ latency === null
236
+ ? into.latencyMaxMs
237
+ : into.latencyMaxMs === null
238
+ ? latency
239
+ : Math.max(into.latencyMaxMs, latency),
240
+ latencyHistogram: histogram,
241
+ tokensPerSecSum: into.tokensPerSecSum + (event.tokensPerSec ?? 0),
242
+ tokensPerSecCount: into.tokensPerSecCount + (event.tokensPerSec === null ? 0 : 1),
243
+ // A completed request with no cost is unpriced, not free. Counting it keeps
244
+ // the dashboard's "partial cost" warning honest after compaction.
245
+ unpriced: into.unpriced + (event.status === "completed" && priced === 0 ? 1 : 0),
246
+ };
247
+ }
248
+
249
+ /**
250
+ * An approximate percentile over a compacted histogram.
251
+ *
252
+ * Returns the upper edge of the bucket that contains it — never a value
253
+ * between edges, because a histogram cannot be more precise than the bucket
254
+ * that holds the sample. `null` when there are no samples, and `null` when the
255
+ * percentile falls in the overflow bucket, whose upper edge is unbounded.
256
+ */
257
+ export function percentileFromHistogram(
258
+ histogram: readonly number[],
259
+ percentile: number,
260
+ ): (typeof LATENCY_BUCKETS_MS)[number] | null {
261
+ const total = histogram.reduce((sum, count) => sum + count, 0);
262
+ if (total === 0) return null;
263
+ const target = Math.ceil((percentile / 100) * total);
264
+ let seen = 0;
265
+ for (let index = 0; index < histogram.length; index += 1) {
266
+ seen += histogram[index] ?? 0;
267
+ if (seen >= target) {
268
+ // The bucket's upper edge: a percentile from a histogram can never be
269
+ // more precise than the bucket that contains it.
270
+ return index < LATENCY_BUCKETS_MS.length ? LATENCY_BUCKETS_MS[index]! : null;
271
+ }
272
+ }
273
+ return null;
274
+ }
275
+
276
+ /** Newline count of a file, so a torn or unparsable line is detectable. */
277
+ /** Adds two buckets. Exported so the day index can merge without duplicating it. */
278
+ export function mergeBucketsInternal(a: CompactedBucket, b: CompactedBucket): CompactedBucket {
279
+ const histogram = a.latencyHistogram.map((count, index) => count + (b.latencyHistogram[index] ?? 0));
280
+ return {
281
+ byModel: mergeCounts(a.byModel, b.byModel),
282
+ byProvider: mergeCounts(a.byProvider, b.byProvider),
283
+ byClient: mergeCounts(a.byClient, b.byClient),
284
+ byClientIp: mergeCounts(a.byClientIp, b.byClientIp),
285
+ requests: a.requests + b.requests,
286
+ inputTokens: a.inputTokens + b.inputTokens,
287
+ cachedTokens: a.cachedTokens + b.cachedTokens,
288
+ outputTokens: a.outputTokens + b.outputTokens,
289
+ reasoningTokens: a.reasoningTokens + b.reasoningTokens,
290
+ estimatedCostUsd: a.estimatedCostUsd + b.estimatedCostUsd,
291
+ creditUsed: a.creditUsed + b.creditUsed,
292
+ succeeded: a.succeeded + b.succeeded,
293
+ failed: a.failed + b.failed,
294
+ cancelled: a.cancelled + b.cancelled,
295
+ truncated: a.truncated + b.truncated,
296
+ latencySum: a.latencySum + b.latencySum,
297
+ latencyCount: a.latencyCount + b.latencyCount,
298
+ latencyMinMs:
299
+ a.latencyMinMs === null
300
+ ? b.latencyMinMs
301
+ : b.latencyMinMs === null
302
+ ? a.latencyMinMs
303
+ : Math.min(a.latencyMinMs, b.latencyMinMs),
304
+ latencyMaxMs:
305
+ a.latencyMaxMs === null
306
+ ? b.latencyMaxMs
307
+ : b.latencyMaxMs === null
308
+ ? a.latencyMaxMs
309
+ : Math.max(a.latencyMaxMs, b.latencyMaxMs),
310
+ latencyHistogram: histogram,
311
+ tokensPerSecSum: a.tokensPerSecSum + b.tokensPerSecSum,
312
+ tokensPerSecCount: a.tokensPerSecCount + b.tokensPerSecCount,
313
+ unpriced: a.unpriced + b.unpriced,
314
+ };
315
+ }