cartethyia 2.5.9 → 2.6.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 (386) hide show
  1. package/.env.example +15 -42
  2. package/README.md +11 -10
  3. package/dist/dashboard/assets/{BannedPage-Brvuj_Eb.js → BannedPage-l6XMnfr9.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-CqfXtI3y.js → CliToolDetailPage-DwOzAW45.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-GQeS91bg.js → CliToolsPage-BT3Up3QA.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
  9. package/dist/dashboard/assets/{CombosPage-BDRsTXBB.js → CombosPage-6kYIUpzR.js} +1 -1
  10. package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-zP8AFHX0.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-BmfrNZ14.js → CustomizationPage-C7m-H1Rz.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-DoreIgm8.js → LoginPage-CbphLvjg.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
  19. package/dist/dashboard/assets/{ModelPicker-C7pDb41R.js → ModelPicker-DVny4S5_.js} +1 -1
  20. package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
  25. package/dist/dashboard/assets/{ProvidersPage-BnoPgA7w.js → ProvidersPage-Dfb2896L.js} +1 -1
  26. package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
  27. package/dist/dashboard/assets/{ProxyPage-Bq_TGgdb.js → ProxyPage-BaCIdAUt.js} +1 -1
  28. package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-Day3mCOn.js → SetupPage-D4g6vOGc.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-DQxllnB5.js → SortableList-C93i14BD.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-uMExoWu1.js → StudioPage-SgQpymjm.js} +1 -1
  38. package/dist/dashboard/assets/{StudioPage-uMExoWu1.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
  39. package/dist/dashboard/assets/{UsagePage-DXa1oR-4.js → UsagePage-BFME-eba.js} +2 -2
  40. package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
  41. package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
  42. package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
  43. package/dist/dashboard/assets/{app-BIt446yE.js → app-2KhtHCv9.js} +1 -1
  44. package/dist/dashboard/assets/{app-BIt446yE.js.gz → app-2KhtHCv9.js.gz} +0 -0
  45. package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
  46. package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
  48. package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-CR72dasT.js → badge-CKsF123d.js} +1 -1
  50. package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-CI9_eSfO.js → banner-8j7uChA9.js} +1 -1
  52. package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
  53. package/dist/dashboard/assets/{card-DS7CQEsO.js → card-CdsSlWA3.js} +1 -1
  54. package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
  55. package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
  56. package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
  57. package/dist/dashboard/assets/{clipboard-button-611pSsaX.js → clipboard-button-uL5aYLfW.js} +1 -1
  58. package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
  59. package/dist/dashboard/assets/{dialog-k37exjNt.js → dialog-C07hr5_G.js} +1 -1
  60. package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
  61. package/dist/dashboard/assets/{drawer-CX7N8ucR.js → drawer-BPO1TcKH.js} +1 -1
  62. package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-CTP8AyiS.js → entry-Bz_CtDfC.js} +1 -1
  64. package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
  66. package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
  68. package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
  69. package/dist/dashboard/assets/{github-badge-sltk1qUi.js → github-badge-DmQreSDd.js} +1 -1
  70. package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
  71. package/dist/dashboard/assets/{index-z3zDOSMM.js → index-Bblu6erR.js} +2 -2
  72. package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
  73. package/dist/dashboard/assets/{inline-BhY2sSnO.js → inline-C9dCYaBZ.js} +1 -1
  74. package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
  75. package/dist/dashboard/assets/{input-V1umFiBk.js → input-CjCnXweJ.js} +1 -1
  76. package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
  77. package/dist/dashboard/assets/{layout-BBOYycyS.js → layout-V6y8DAkl.js} +1 -1
  78. package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
  79. package/dist/dashboard/assets/{list-row-1zzZYM0B.js → list-row-CM1AaG_y.js} +1 -1
  80. package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
  81. package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
  82. package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
  83. package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
  84. package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
  86. package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
  87. package/dist/dashboard/assets/{numbers-DkfMR5Qs.js → numbers-BOevJzdz.js} +1 -1
  88. package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
  89. package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
  90. package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
  91. package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
  92. package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
  93. package/dist/dashboard/assets/{select-C6MMiaio.js → select-C980c4cz.js} +1 -1
  94. package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
  95. package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
  96. package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
  97. package/dist/dashboard/assets/{stack-BHTfXdz_.js → stack-_gfTdLVB.js} +1 -1
  98. package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
  99. package/dist/dashboard/assets/{state-mPDRlQkl.js → state-2c5V4zo7.js} +1 -1
  100. package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
  101. package/dist/dashboard/assets/{switch-DOmJxg7H.js → switch-Dkke7Naf.js} +1 -1
  102. package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
  103. package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
  104. package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
  105. package/dist/dashboard/index.html +1 -1
  106. package/dist/dashboard/index.html.gz +0 -0
  107. package/dist/dashboard/precompressed-assets.json +51 -51
  108. package/migrations/README.md +52 -52
  109. package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
  110. package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
  111. package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
  112. package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  113. package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
  114. package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
  115. package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
  116. package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  117. package/package.json +96 -97
  118. package/scripts/cli/admin.ts +126 -125
  119. package/scripts/cli/autostart.ts +237 -237
  120. package/scripts/cli/background.ts +120 -110
  121. package/scripts/cli/bin/carte.cjs +8 -8
  122. package/scripts/cli/bin/cartethyia.cjs +167 -167
  123. package/scripts/cli/console-menu.ts +308 -275
  124. package/scripts/cli/controller.ts +145 -0
  125. package/scripts/cli/doctor.ts +1 -1
  126. package/scripts/cli/env.ts +99 -99
  127. package/scripts/cli/gateway.ts +264 -261
  128. package/scripts/{health.ts → cli/health.ts} +4 -4
  129. package/scripts/cli/instance.ts +234 -224
  130. package/scripts/cli/main.ts +188 -164
  131. package/scripts/cli/platform.ts +64 -64
  132. package/scripts/cli/prompt.ts +275 -107
  133. package/scripts/cli/services.ts +88 -88
  134. package/scripts/cli/setup.ts +445 -445
  135. package/scripts/cli/start.ts +85 -82
  136. package/scripts/cli/startup-signal.ts +23 -23
  137. package/scripts/cli/telemetry.ts +70 -0
  138. package/scripts/cli/tray/index.ts +263 -263
  139. package/scripts/cli/tray/windows.ps1 +107 -107
  140. package/scripts/cli/tray.ts +192 -208
  141. package/scripts/cli/update.ts +39 -39
  142. package/scripts/cli/version.ts +67 -67
  143. package/scripts/cli/wordmark.ts +58 -58
  144. package/src/boot/app.ts +16 -1
  145. package/src/boot/dependencies.ts +128 -25
  146. package/src/config/package-paths.ts +24 -0
  147. package/src/config/resources.ts +137 -0
  148. package/src/config/runtime.ts +133 -76
  149. package/src/config/spec.ts +30 -29
  150. package/src/config/telemetry.ts +0 -25
  151. package/src/console/api-keys/bucket-spend.ts +14 -17
  152. package/src/console/api-keys/contracts.ts +410 -410
  153. package/src/console/api-keys/routes.ts +12 -0
  154. package/src/console/audit/store.ts +2 -3
  155. package/src/console/auth/setup-service.ts +8 -1
  156. package/src/console/backup/routes.ts +7 -0
  157. package/src/console/backup/service.ts +463 -463
  158. package/src/console/backup/store.ts +16 -11
  159. package/src/console/backup/validate.ts +1 -4
  160. package/src/console/cli-tools/fs-ops.ts +18 -2
  161. package/src/console/data-management/contracts.ts +1 -8
  162. package/src/console/data-management/operations.ts +61 -48
  163. package/src/console/data-management/routes.ts +4 -2
  164. package/src/console/domain-registration.ts +25 -3
  165. package/src/console/inferhub/client.ts +139 -0
  166. package/src/console/inferhub/key-store.ts +129 -0
  167. package/src/console/inferhub/normalize.ts +347 -0
  168. package/src/console/inferhub/routes.ts +411 -0
  169. package/src/console/inferhub/store.ts +109 -0
  170. package/src/console/inferhub/types.ts +183 -0
  171. package/src/console/observability/contracts.ts +2 -41
  172. package/src/console/observability/store.ts +176 -371
  173. package/src/console/providers/catalog/public-model-store.ts +0 -0
  174. package/src/console/providers/catalog/store.ts +23 -36
  175. package/src/console/quota/account-quota-shared.ts +52 -52
  176. package/src/console/quota/account-quota-tenant.ts +16 -24
  177. package/src/console/quota/refresh.ts +441 -441
  178. package/src/console/remote-support/service.ts +270 -114
  179. package/src/console/routing/pools/import-parse.ts +206 -206
  180. package/src/console/routing/pools/operations.ts +543 -543
  181. package/src/console/routing/pools/probe-verdict.ts +132 -132
  182. package/src/console/routing/pools/routes.ts +181 -181
  183. package/src/console/routing/pools/store.ts +1 -1
  184. package/src/console/settings/contracts.ts +42 -40
  185. package/src/console/settings/store.ts +25 -25
  186. package/src/console/share/share-stats.ts +22 -4
  187. package/src/console/share/share-usage.ts +30 -4
  188. package/src/console/shared/cursor.ts +15 -0
  189. package/src/console/updates/repository-updates.ts +40 -39
  190. package/src/console/updates/routes.ts +12 -0
  191. package/src/main.ts +13 -5
  192. package/src/network/pool/resolver.ts +1104 -1104
  193. package/src/observability/client-name.ts +22 -0
  194. package/src/observability/event-log.ts +5 -29
  195. package/src/observability/event-query.ts +690 -0
  196. package/src/observability/log-ring.ts +26 -2
  197. package/src/observability/log-sanitize.ts +118 -0
  198. package/src/observability/logger.ts +17 -13
  199. package/src/observability/metrics.ts +35 -0
  200. package/src/observability/payload-capture.ts +114 -41
  201. package/src/observability/payload-store.ts +249 -24
  202. package/src/observability/raw-event-writer.ts +118 -0
  203. package/src/observability/runtime-metrics.ts +71 -1
  204. package/src/observability/telemetry-buffer.ts +100 -17
  205. package/src/observability/telemetry-reconciliation.ts +417 -0
  206. package/src/persistence/boot.ts +20 -9
  207. package/src/persistence/db-pg.ts +13 -0
  208. package/src/persistence/db-sqlite.ts +569 -497
  209. package/src/persistence/dialect.ts +3 -2
  210. package/src/persistence/migrate.ts +181 -181
  211. package/src/persistence/schema.ts +1496 -1406
  212. package/src/persistence/shared-cache.ts +5 -1
  213. package/src/persistence/stores/api-key-store.ts +581 -581
  214. package/src/persistence/stores/telemetry-store.ts +124 -63
  215. package/src/protocol/reasoning-text.ts +30 -0
  216. package/src/protocol/registry.ts +2 -0
  217. package/src/protocol/response/chat.ts +33 -7
  218. package/src/protocol/response/codex.ts +14 -2
  219. package/src/protocol/response/messages.ts +5 -2
  220. package/src/providers/authentication/oauth-flow-store.ts +3 -10
  221. package/src/providers/bundled-discovery-providers.ts +122 -122
  222. package/src/providers/default-registry.ts +14 -0
  223. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  224. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  225. package/src/providers/discovery/probe-phases.ts +43 -7
  226. package/src/providers/discovery/probing-service.ts +70 -32
  227. package/src/providers/integrations/antigravity/antigravity.ts +9 -2
  228. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
  229. package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
  230. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
  231. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
  232. package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
  233. package/src/providers/integrations/buddy/capabilities.ts +4 -0
  234. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
  235. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
  236. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
  237. package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
  238. package/src/providers/integrations/claude/claude-quota.ts +2 -2
  239. package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
  240. package/src/providers/integrations/claude/claude.ts +3 -4
  241. package/src/providers/integrations/cline/cline.ts +94 -8
  242. package/src/providers/integrations/codex/codex.ts +61 -37
  243. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
  244. package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
  245. package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
  246. package/src/providers/integrations/cursor/cursor-request.ts +11 -0
  247. package/src/providers/integrations/cursor/cursor.ts +29 -17
  248. package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
  249. package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
  250. package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
  251. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
  252. package/src/providers/integrations/github/github.ts +3 -1
  253. package/src/providers/integrations/grok/grok-quota.ts +279 -279
  254. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
  255. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
  256. package/src/providers/integrations/hermes.ts +7 -1
  257. package/src/providers/integrations/inferhub.ts +201 -73
  258. package/src/providers/integrations/kilo/kilo.ts +152 -152
  259. package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
  260. package/src/providers/integrations/opencode/provider.ts +2 -0
  261. package/src/providers/integrations/openrouter/provider.ts +129 -129
  262. package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
  263. package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
  264. package/src/providers/integrations/trae/trae-headers.ts +17 -48
  265. package/src/providers/integrations/trae/trae-oauth.ts +4 -26
  266. package/src/providers/integrations/trae/trae-quota.ts +389 -384
  267. package/src/providers/integrations/trae/trae-realm.ts +15 -2
  268. package/src/providers/integrations/trae/trae.ts +22 -24
  269. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
  270. package/src/providers/integrations/zcode/oauth.ts +1 -1
  271. package/src/providers/operations/account-health-service.ts +605 -279
  272. package/src/providers/provider-metadata.ts +12 -1
  273. package/src/providers/provider-model-definition.ts +373 -373
  274. package/src/providers/provider-probe.ts +3 -0
  275. package/src/providers/quota/quota-contracts.ts +3 -7
  276. package/src/providers/search/search-provider.ts +2 -6
  277. package/src/security/admission/contracts.ts +10 -0
  278. package/src/security/admission/in-memory-store.ts +89 -14
  279. package/src/security/admission/lua.ts +191 -182
  280. package/src/security/admission/redis-store.ts +30 -0
  281. package/src/security/admission/service.ts +18 -0
  282. package/src/security/api-key-auth-cache.ts +96 -0
  283. package/src/security/api-key-auth.ts +11 -100
  284. package/src/security/client-router-fingerprint.ts +3 -7
  285. package/src/security/ip-boundary.ts +7 -3
  286. package/src/shared/coerce.ts +67 -0
  287. package/src/shared/hash.ts +6 -0
  288. package/src/shared/time.ts +7 -0
  289. package/src/transport/dispatch/attempt-finalize.ts +26 -138
  290. package/src/transport/dispatch/attempt-loop.ts +103 -81
  291. package/src/transport/dispatch/capture-policy.ts +78 -0
  292. package/src/transport/dispatch/proxy-request.ts +4 -2
  293. package/src/transport/dispatch/streaming/encoder.ts +111 -5
  294. package/src/transport/dispatch/streaming/stream.ts +19 -2
  295. package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
  296. package/src/transport/failure-policy.ts +27 -0
  297. package/src/transport/guards/abuse-guard.ts +2 -2
  298. package/src/transport/guards/auth-guard.ts +17 -0
  299. package/src/transport/guards/body-policy.ts +3 -2
  300. package/src/transport/guards/concurrency-guard.ts +89 -13
  301. package/src/transport/guards/early-rejection-capture.ts +43 -3
  302. package/src/transport/guards/error-lifecycle.ts +10 -2
  303. package/src/transport/guards/guard-errors.ts +23 -0
  304. package/src/transport/guards/headroom-guard.ts +41 -7
  305. package/src/transport/guards/pipeline.ts +24 -0
  306. package/src/transport/guards/request-context.ts +2 -1
  307. package/src/transport/routing/admission.ts +51 -9
  308. package/src/transport/routing/bounded-lru.ts +49 -0
  309. package/src/transport/routing/candidate-index.ts +88 -0
  310. package/src/transport/routing/reservations.ts +121 -7
  311. package/src/transport/routing/round-robin.ts +10 -22
  312. package/src/transport/routing/route-catalog.ts +26 -13
  313. package/src/transport/routing/route-model.ts +22 -2
  314. package/src/transport/routing/router.ts +53 -31
  315. package/src/transport/surface/chat/encode.ts +2 -3
  316. package/src/transport/surface/chat/parse.ts +2 -1
  317. package/src/transport/surface/dialects.ts +11 -3
  318. package/src/transport/surface/messages/encode.ts +2 -1
  319. package/src/transport/surface/messages/parse.ts +3 -1
  320. package/src/transport/surface/messages/stream.ts +2 -6
  321. package/src/workers/maintenance-lease.ts +200 -0
  322. package/src/workers/scheduler.ts +101 -1
  323. package/dist/dashboard/assets/BannedPage-Brvuj_Eb.js.gz +0 -0
  324. package/dist/dashboard/assets/CliToolDetailPage-CqfXtI3y.js.gz +0 -0
  325. package/dist/dashboard/assets/CliToolsPage-GQeS91bg.js.gz +0 -0
  326. package/dist/dashboard/assets/CombosPage-BDRsTXBB.js.gz +0 -0
  327. package/dist/dashboard/assets/ConsoleLogPage-zP8AFHX0.js.gz +0 -0
  328. package/dist/dashboard/assets/CustomizationPage-BmfrNZ14.js.gz +0 -0
  329. package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js +0 -1
  330. package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js.gz +0 -0
  331. package/dist/dashboard/assets/LoginPage-DoreIgm8.js.gz +0 -0
  332. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  333. package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js +0 -1
  334. package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js.gz +0 -0
  335. package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js +0 -3
  336. package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js.gz +0 -0
  337. package/dist/dashboard/assets/ProvidersPage-BnoPgA7w.js.gz +0 -0
  338. package/dist/dashboard/assets/ProxyPage-Bq_TGgdb.js.gz +0 -0
  339. package/dist/dashboard/assets/QuotaPage-D9DHXvom.js +0 -1
  340. package/dist/dashboard/assets/QuotaPage-D9DHXvom.js.gz +0 -0
  341. package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js +0 -1
  342. package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js.gz +0 -0
  343. package/dist/dashboard/assets/SetupPage-Day3mCOn.js.gz +0 -0
  344. package/dist/dashboard/assets/SortableList-DQxllnB5.js.gz +0 -0
  345. package/dist/dashboard/assets/UsagePage-DXa1oR-4.js.gz +0 -0
  346. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  347. package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
  348. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  349. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  350. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  351. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  352. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  353. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  354. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  355. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  356. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  357. package/dist/dashboard/assets/entry-B9Xvtnfj.js +0 -1
  358. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  359. package/dist/dashboard/assets/entry-ByMGkR3C.js +0 -4
  360. package/dist/dashboard/assets/entry-ByMGkR3C.js.gz +0 -0
  361. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  362. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  363. package/dist/dashboard/assets/index-z3zDOSMM.js.gz +0 -0
  364. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  365. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  366. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  367. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  368. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  369. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  370. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  371. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  372. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  373. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  374. package/dist/dashboard/assets/select-C6MMiaio.js.gz +0 -0
  375. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  376. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  377. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  378. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  379. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  380. package/scripts/build/aot.ts +0 -143
  381. package/scripts/build/build.ts +0 -24
  382. package/scripts/build/compile.ts +0 -88
  383. package/scripts/build/restart.ts +0 -120
  384. package/scripts/build/start.ts +0 -25
  385. package/src/observability/event-compaction.ts +0 -317
  386. package/src/observability/event-day-index.ts +0 -440
@@ -1,120 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * restart.ts — rebuild and restart the compiled gateway gracefully.
4
- *
5
- * Why this exists: a hard kill (`taskkill /F`, `kill -9`) truncates every
6
- * in-flight response. On Linux the process handles SIGTERM/SIGINT and drains,
7
- * but Windows has no deliverable catchable signal — `process.kill(pid,
8
- * "SIGTERM")` of a console-less Bun process runs no JS handler (measured). So
9
- * this script stops the running instance through the drain endpoint when
10
- * `CARTETHYIA_DRAIN_TOKEN` is set (loopback + `x-drain-token`), and only falls
11
- * back to a hard kill when no token is configured.
12
- *
13
- * Env:
14
- * CARTETHYIA_DRAIN_TOKEN token for POST /admin/drain (required for graceful stop)
15
- * PORT gateway port (default 12800)
16
- * DRAIN_WAIT_MS how long to wait for the drain to finish (default 30000)
17
- */
18
- import { compiledBinaryPath } from "./compile";
19
-
20
- function intEnv(name: string, fallback: number): number {
21
- const raw = process.env[name];
22
- if (raw === undefined || raw.trim() === "") return fallback;
23
- const value = Number(raw);
24
- return Number.isFinite(value) && value > 0 ? Math.trunc(value) : fallback;
25
- }
26
-
27
- const PORT = intEnv("PORT", 12800);
28
- const DRAIN_WAIT_MS = intEnv("DRAIN_WAIT_MS", 30_000);
29
-
30
- /** PIDs currently listening on `port` (best-effort, platform-specific). */
31
- async function listeningPids(port: number): Promise<number[]> {
32
- try {
33
- if (process.platform === "win32") {
34
- const proc = Bun.spawnSync(["netstat", "-ano", "-p", "TCP"]);
35
- const out = new TextDecoder().decode(proc.stdout);
36
- const pids = new Set<number>();
37
- for (const line of out.split(/\r?\n/)) {
38
- if (!line.includes(`:${port} `) || !line.includes("LISTENING")) continue;
39
- const pid = Number(line.trim().split(/\s+/).pop());
40
- if (Number.isInteger(pid) && pid > 0) pids.add(pid);
41
- }
42
- return [...pids];
43
- }
44
- const proc = Bun.spawnSync(["lsof", "-ti", `tcp:${port}`, "-sTCP:LISTEN"]);
45
- return new TextDecoder()
46
- .decode(proc.stdout)
47
- .split(/\s+/)
48
- .map(Number)
49
- .filter((n) => Number.isInteger(n) && n > 0);
50
- } catch {
51
- return [];
52
- }
53
- }
54
-
55
- async function waitForPortFree(port: number, timeoutMs: number): Promise<boolean> {
56
- const deadline = Date.now() + timeoutMs;
57
- while (Date.now() < deadline) {
58
- if ((await listeningPids(port)).length === 0) return true;
59
- await new Promise((r) => setTimeout(r, 250));
60
- }
61
- return (await listeningPids(port)).length === 0;
62
- }
63
-
64
- async function stopGracefully(): Promise<boolean> {
65
- const token = process.env.CARTETHYIA_DRAIN_TOKEN?.trim();
66
- if (token === undefined || token.length === 0) {
67
- console.warn("[restart] CARTETHYIA_DRAIN_TOKEN unset — cannot drain gracefully");
68
- return false;
69
- }
70
- try {
71
- const response = await fetch(`http://127.0.0.1:${PORT}/admin/drain`, {
72
- method: "POST",
73
- headers: { "x-drain-token": token },
74
- });
75
- if (!response.ok) {
76
- console.warn(`[restart] drain request returned HTTP ${response.status}`);
77
- return false;
78
- }
79
- console.log("[restart] drain acknowledged; waiting for the process to exit");
80
- return await waitForPortFree(PORT, DRAIN_WAIT_MS);
81
- } catch (error) {
82
- console.warn(`[restart] drain request failed: ${(error as Error).message}`);
83
- return false;
84
- }
85
- }
86
-
87
- async function hardKill(): Promise<void> {
88
- const pids = await listeningPids(PORT);
89
- if (pids.length === 0) return;
90
- console.warn(`[restart] force-killing pid(s) ${pids.join(", ")} on :${PORT}`);
91
- for (const pid of pids) {
92
- try {
93
- process.kill(pid, "SIGKILL");
94
- } catch {
95
- // Already gone.
96
- }
97
- }
98
- await waitForPortFree(PORT, 5_000);
99
- }
100
-
101
- async function main(): Promise<number> {
102
- const graceful = await stopGracefully();
103
- if (!graceful) await hardKill();
104
-
105
- console.log("[restart] building");
106
- const build = Bun.spawnSync(["bun", "run", "build"], { stdout: "inherit", stderr: "inherit" });
107
- if (build.exitCode !== 0) {
108
- console.error("[restart] build failed");
109
- return build.exitCode ?? 1;
110
- }
111
-
112
- const binary = compiledBinaryPath();
113
- console.log(`[restart] starting ${binary}`);
114
- const child = Bun.spawn([binary], { stdin: "ignore", stdout: "inherit", stderr: "inherit" });
115
- return await child.exited;
116
- }
117
-
118
- if (import.meta.main) {
119
- process.exit(await main());
120
- }
@@ -1,25 +0,0 @@
1
- /** Runs the compiled binary from the repository root. */
2
- import { compiledBinaryPath } from "./compile";
3
-
4
- /** Runs the compiled binary with inherited stdio; resolves to its exit code. */
5
- export async function startBinary(): Promise<number> {
6
- const binary = compiledBinaryPath();
7
- if (!(await Bun.file(binary).exists())) {
8
- throw new Error(`${binary} is missing; run bun run build first`);
9
- }
10
-
11
- process.env.NODE_ENV ??= "production";
12
- process.env.CARTETHYIA_SERVER_MAX_BODY_BYTES ??= String(8 * 1024 * 1024 * 1024);
13
-
14
- const child = Bun.spawn([binary], {
15
- env: process.env,
16
- stdin: "inherit",
17
- stdout: "inherit",
18
- stderr: "inherit",
19
- });
20
- return await child.exited;
21
- }
22
-
23
- if (import.meta.main) {
24
- process.exit(await startBinary());
25
- }
@@ -1,317 +0,0 @@
1
- /**
2
- * Bucket primitives for the per-day telemetry index.
3
- *
4
- * Each day's events are folded into a `CompactedBucket` via `accumulate()` and
5
- * stored as a `.index.json` file alongside the source `.events.jsonl`. Buckets
6
- * are pure accumulators: every field is a running sum, min, or max — nothing
7
- * is averaged or estimated away. `mergeBucketsInternal` adds two buckets so
8
- * partial indexes can be merged without re-reading source files.
9
- */
10
- import type { TelemetryEventRecord } from "./event-log";
11
-
12
- /**
13
- * Histogram bucket edges in milliseconds.
14
- *
15
- * Fixed rather than adaptive: a reader that wants P50/P95/P99 has to be able to
16
- * interpret buckets it did not write, and an adaptive layout would have to be
17
- * stored alongside every row. These follow the gateway's own latency
18
- * histogram, so a compacted period and a live one read comparably.
19
- */
20
- export const LATENCY_BUCKETS_MS = [
21
- 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000,
22
- ] as const;
23
-
24
- export interface CompactedBucket {
25
- readonly requests: number;
26
- readonly inputTokens: number;
27
- readonly cachedTokens: number;
28
- readonly outputTokens: number;
29
- readonly reasoningTokens: number;
30
- readonly estimatedCostUsd: number;
31
- readonly creditUsed: number;
32
- readonly succeeded: number;
33
- readonly failed: number;
34
- readonly cancelled: number;
35
- readonly truncated: number;
36
- readonly latencySum: number;
37
- readonly latencyCount: number;
38
- readonly latencyMinMs: number | null;
39
- readonly latencyMaxMs: number | null;
40
- /** Counts per fixed latency bucket; `+Inf` is the last, overflow, bucket. */
41
- readonly latencyHistogram: readonly number[];
42
- readonly tokensPerSecSum: number;
43
- readonly tokensPerSecCount: number;
44
- /** Requests whose cost could not be priced. */
45
- readonly unpriced: number;
46
- /**
47
- * Requests per breakdown dimension, kept so the Usage breakdown survives the
48
- * source files being deleted.
49
- *
50
- * Only the *counts* are kept, never the events: a day's breakdown asks how
51
- * many requests went to a model, not which ones. That is what makes this
52
- * small enough to keep — a full event is ~1KB, a count is a few bytes.
53
- */
54
- readonly byModel: Readonly<Record<string, number>>;
55
- readonly byProvider: Readonly<Record<string, number>>;
56
- readonly byClient: Readonly<Record<string, number>>;
57
- readonly byClientIp: Readonly<Record<string, number>>;
58
- /**
59
- * Requests per HTTP status, keyed by the status as a decimal string.
60
- *
61
- * Kept as counts for the same reason the other dimensions are: the Usage
62
- * summary's status chips and the request-list filters read these, and without
63
- * them every chip reads zero once the day's events have been folded — the
64
- * numbers were on the source events, which no longer exist.
65
- */
66
- readonly byStatus: Readonly<Record<string, number>>;
67
- /**
68
- * Sum and count of gateway routing delay, so `avgResolveMs` survives
69
- * compaction. Kept as a sum rather than an average of averages for the same
70
- * reason the latency sum is: an average of per-file averages drifts as soon
71
- * as the files hold different request counts.
72
- */
73
- readonly resolveSumMs: number;
74
- readonly resolveCount: number;
75
- }
76
-
77
- /**
78
- * A number from a value that may be a numeric string; 0 when unusable.
79
- *
80
- * Exported so readers of a compacted bucket coerce its fields the same way the
81
- * fold does: a second local copy of "is this a number" is how one caller ends
82
- * up summing `undefined` into NaN while another reads it as zero.
83
- */
84
- export function numeric(value: unknown): number {
85
- const asNumber = typeof value === "number" ? value : Number(value);
86
- return Number.isFinite(asNumber) ? asNumber : 0;
87
- }
88
-
89
- /**
90
- * The two fields added after the first index format, read defensively.
91
- *
92
- * An index file written before they existed carries neither, and `undefined +
93
- 0` is `NaN` — which then propagates straight through every later fold and
94
- * merge, so one old day turns the whole period's `avgResolveMs` into `NaN`.
95
- * Reading them as 0/{} is the same answer an empty day gives, and a folded
96
- * day that predates the field genuinely has no resolve measurement to report.
97
- */
98
- function readResolveSum(bucket: CompactedBucket): number {
99
- return numeric(bucket.resolveSumMs);
100
- }
101
- function readResolveCount(bucket: CompactedBucket): number {
102
- return numeric(bucket.resolveCount);
103
- }
104
-
105
- /** Adds one occurrence to a count map, ignoring an absent key. */
106
- function bump(
107
- counts: Readonly<Record<string, number>> | undefined,
108
- key: string | null | undefined,
109
- ): Record<string, number> {
110
- const out: Record<string, number> = { ...(counts ?? {}) };
111
- const name = typeof key === "string" ? key.trim() : "";
112
- if (name !== "") out[name] = (out[name] ?? 0) + 1;
113
- return out;
114
- }
115
-
116
- /** Adds two count maps. */
117
- function mergeCounts(
118
- a: Readonly<Record<string, number>> | undefined,
119
- b: Readonly<Record<string, number>> | undefined,
120
- ): Record<string, number> {
121
- const out: Record<string, number> = { ...(a ?? {}) };
122
- for (const [key, value] of Object.entries(b ?? {})) out[key] = (out[key] ?? 0) + value;
123
- return out;
124
- }
125
-
126
- export function emptyBucket(): CompactedBucket {
127
- return {
128
- byModel: {},
129
- byProvider: {},
130
- byClient: {},
131
- byClientIp: {},
132
- byStatus: {},
133
- resolveSumMs: 0,
134
- resolveCount: 0,
135
- requests: 0,
136
- inputTokens: 0,
137
- cachedTokens: 0,
138
- outputTokens: 0,
139
- reasoningTokens: 0,
140
- estimatedCostUsd: 0,
141
- creditUsed: 0,
142
- succeeded: 0,
143
- failed: 0,
144
- cancelled: 0,
145
- truncated: 0,
146
- latencySum: 0,
147
- latencyCount: 0,
148
- latencyMinMs: null,
149
- latencyMaxMs: null,
150
- latencyHistogram: new Array<number>(LATENCY_BUCKETS_MS.length + 1).fill(0),
151
- tokensPerSecSum: 0,
152
- tokensPerSecCount: 0,
153
- unpriced: 0,
154
- };
155
- }
156
-
157
- function bucketIndex(latencyMs: number): number {
158
- for (let index = 0; index < LATENCY_BUCKETS_MS.length; index += 1) {
159
- if (latencyMs <= LATENCY_BUCKETS_MS[index]!) return index;
160
- }
161
- return LATENCY_BUCKETS_MS.length;
162
- }
163
-
164
- /**
165
- * Folds one request row into a bucket.
166
- *
167
- * Every field is a running sum or a min/max, never an average of averages: an
168
- * average of per-file averages would drift from the true mean as soon as the
169
- * files held different request counts, and the whole point of compaction is
170
- * that the totals survive intact.
171
- */
172
- export function accumulate(
173
- into: CompactedBucket,
174
- event: TelemetryEventRecord,
175
- ): CompactedBucket {
176
- const histogram = [...into.latencyHistogram];
177
- const latency = event.latencyMs;
178
- if (latency !== null && Number.isFinite(latency)) {
179
- histogram[bucketIndex(latency)] = (histogram[bucketIndex(latency)] ?? 0) + 1;
180
- }
181
- // A cost can arrive as a string: Postgres `numeric` columns come back that
182
- // way, and so do restored rows. Adding one to a number concatenates instead of
183
- // summing, which turned a cost of 0.000966 into "00.000966". Coerced here so
184
- // every downstream sum is arithmetic.
185
- const priced = numeric(event.estimatedCostUsd);
186
- const resolve = event.resolveMs;
187
- const hasResolve = resolve !== null && resolve !== undefined && Number.isFinite(resolve);
188
- return {
189
- byModel: bump(into.byModel, event.requestedModel ?? event.reportedModel),
190
- byProvider: bump(into.byProvider, event.providerId),
191
- byClient: bump(into.byClient, event.sourceSurface),
192
- byClientIp: bump(into.byClientIp, event.clientIp),
193
- // Keyed on the HTTP status, not the internal one: the chips and the filters
194
- // are the status the client was given.
195
- byStatus: bump(into.byStatus, event.httpStatus?.toString()),
196
- resolveSumMs: readResolveSum(into) + (hasResolve ? resolve : 0),
197
- resolveCount: readResolveCount(into) + (hasResolve ? 1 : 0),
198
- requests: into.requests + 1,
199
- inputTokens: into.inputTokens + (event.inputTokens ?? 0),
200
- cachedTokens: into.cachedTokens + (event.cachedInputTokens ?? 0),
201
- outputTokens: into.outputTokens + (event.outputTokens ?? 0),
202
- reasoningTokens: into.reasoningTokens + (event.reasoningTokens ?? 0),
203
- estimatedCostUsd: into.estimatedCostUsd + priced,
204
- creditUsed: into.creditUsed + (event.creditUsed ?? 0),
205
- succeeded: into.succeeded + (event.status === "completed" ? 1 : 0),
206
- failed: into.failed + (event.status === "failed" ? 1 : 0),
207
- cancelled: into.cancelled + (event.status === "cancelled" ? 1 : 0),
208
- truncated: into.truncated + (event.status === "truncated" ? 1 : 0),
209
- latencySum: into.latencySum + (latency ?? 0),
210
- latencyCount: into.latencyCount + (latency === null ? 0 : 1),
211
- latencyMinMs:
212
- latency === null
213
- ? into.latencyMinMs
214
- : into.latencyMinMs === null
215
- ? latency
216
- : Math.min(into.latencyMinMs, latency),
217
- latencyMaxMs:
218
- latency === null
219
- ? into.latencyMaxMs
220
- : into.latencyMaxMs === null
221
- ? latency
222
- : Math.max(into.latencyMaxMs, latency),
223
- latencyHistogram: histogram,
224
- tokensPerSecSum: into.tokensPerSecSum + (event.tokensPerSec ?? 0),
225
- tokensPerSecCount: into.tokensPerSecCount + (event.tokensPerSec === null ? 0 : 1),
226
- // A completed request with no cost is unpriced, not free. Counting it keeps
227
- // the dashboard's "partial cost" warning honest after compaction.
228
- unpriced: into.unpriced + (event.status === "completed" && priced === 0 ? 1 : 0),
229
- };
230
- }
231
-
232
- /**
233
- * An approximate percentile over a compacted histogram.
234
- *
235
- * Returns the upper edge of the bucket that contains it — never a value
236
- * between edges, because a histogram cannot be more precise than the bucket
237
- * that holds the sample. `null` when there are no samples.
238
- *
239
- * The overflow bucket is the last one and has no upper edge, so it returns
240
- * {@link HISTOGRAM_OVERFLOW_MS} — the top edge of the last real bucket — rather
241
- * than `null`. Returning `null` there was read as "no data" by every caller,
242
- * which collapsed a P95 of 40 s to `0 ms`: the slowest, most interesting
243
- * percentiles were the ones reported as zero. Callers that can do better pass
244
- * the period's `latencyMaxMs` as `overflowMs` and get that instead.
245
- */
246
- export function percentileFromHistogram(
247
- histogram: readonly number[],
248
- percentile: number,
249
- overflowMs?: number | null,
250
- ): number | null {
251
- const total = histogram.reduce((sum, count) => sum + count, 0);
252
- if (total === 0) return null;
253
- const target = Math.ceil((percentile / 100) * total);
254
- let seen = 0;
255
- for (let index = 0; index < histogram.length; index += 1) {
256
- seen += histogram[index] ?? 0;
257
- if (seen >= target) {
258
- // The bucket's upper edge: a percentile from a histogram can never be
259
- // more precise than the bucket that contains it.
260
- if (index < LATENCY_BUCKETS_MS.length) return LATENCY_BUCKETS_MS[index]!;
261
- // Above the last edge. `latencyMaxMs` is the honest answer when the
262
- // caller has it — it is the real slowest request in the period, so a
263
- // percentile past the last edge is at most that. Falling back to the
264
- // last edge rather than 0 keeps the figure in the right order of
265
- // magnitude even when no max was recorded.
266
- return overflowMs ?? HISTOGRAM_OVERFLOW_MS;
267
- }
268
- }
269
- return null;
270
- }
271
-
272
- /** Top edge of the last real latency bucket; the floor for an overflow answer. */
273
- export const HISTOGRAM_OVERFLOW_MS = LATENCY_BUCKETS_MS[LATENCY_BUCKETS_MS.length - 1]!;
274
-
275
- /** Newline count of a file, so a torn or unparsable line is detectable. */
276
- /** Adds two buckets. Exported so the day index can merge without duplicating it. */
277
- export function mergeBucketsInternal(a: CompactedBucket, b: CompactedBucket): CompactedBucket {
278
- const histogram = a.latencyHistogram.map((count, index) => count + (b.latencyHistogram[index] ?? 0));
279
- return {
280
- byModel: mergeCounts(a.byModel, b.byModel),
281
- byProvider: mergeCounts(a.byProvider, b.byProvider),
282
- byClient: mergeCounts(a.byClient, b.byClient),
283
- byClientIp: mergeCounts(a.byClientIp, b.byClientIp),
284
- byStatus: mergeCounts(a.byStatus, b.byStatus),
285
- resolveSumMs: readResolveSum(a) + readResolveSum(b),
286
- resolveCount: readResolveCount(a) + readResolveCount(b),
287
- requests: a.requests + b.requests,
288
- inputTokens: a.inputTokens + b.inputTokens,
289
- cachedTokens: a.cachedTokens + b.cachedTokens,
290
- outputTokens: a.outputTokens + b.outputTokens,
291
- reasoningTokens: a.reasoningTokens + b.reasoningTokens,
292
- estimatedCostUsd: a.estimatedCostUsd + b.estimatedCostUsd,
293
- creditUsed: a.creditUsed + b.creditUsed,
294
- succeeded: a.succeeded + b.succeeded,
295
- failed: a.failed + b.failed,
296
- cancelled: a.cancelled + b.cancelled,
297
- truncated: a.truncated + b.truncated,
298
- latencySum: a.latencySum + b.latencySum,
299
- latencyCount: a.latencyCount + b.latencyCount,
300
- latencyMinMs:
301
- a.latencyMinMs === null
302
- ? b.latencyMinMs
303
- : b.latencyMinMs === null
304
- ? a.latencyMinMs
305
- : Math.min(a.latencyMinMs, b.latencyMinMs),
306
- latencyMaxMs:
307
- a.latencyMaxMs === null
308
- ? b.latencyMaxMs
309
- : b.latencyMaxMs === null
310
- ? a.latencyMaxMs
311
- : Math.max(a.latencyMaxMs, b.latencyMaxMs),
312
- latencyHistogram: histogram,
313
- tokensPerSecSum: a.tokensPerSecSum + b.tokensPerSecSum,
314
- tokensPerSecCount: a.tokensPerSecCount + b.tokensPerSecCount,
315
- unpriced: a.unpriced + b.unpriced,
316
- };
317
- }