cartethyia 2.5.3 → 2.5.6

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 (456) hide show
  1. package/.env.example +13 -4
  2. package/README.md +2 -2
  3. package/dist/dashboard/assets/{BannedPage-hfCzi-Ux.js → BannedPage-CADDq4IU.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-DEDyKBlC.js → CliToolsPage-DuO1Z7dq.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-bsLuZmYH.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-_UWhwziJ.js → CustomizationPage-BR5HHvbi.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-DDVIfZGW.js → LoginPage-V5faV9V2.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
  19. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
  20. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
  25. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js +1 -0
  26. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
  27. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
  28. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-D6pmQ86L.js → SetupPage-UB690SE2.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-DWvKiuSl.js → SortableList-BLujdX7N.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-CmkUrgdj.js → StudioPage-D5C2raAc.js} +3 -3
  38. package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
  39. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
  40. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
  41. package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
  42. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  43. package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
  44. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  45. package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-C9FD_JwJ.js} +1 -1
  46. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  47. package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-CR72dasT.js} +1 -1
  48. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  49. package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-CI9_eSfO.js} +1 -1
  50. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  51. package/dist/dashboard/assets/{card--AtFedAq.js → card-DS7CQEsO.js} +1 -1
  52. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  53. package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-CNqzrfdb.js} +1 -1
  54. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  55. package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-611pSsaX.js} +1 -1
  56. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  57. package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
  58. package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
  59. package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
  60. package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
  61. package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
  62. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  63. package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
  64. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-B9Xvtnfj.js +1 -0
  66. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
  68. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
  70. package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
  71. package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-sltk1qUi.js} +1 -1
  72. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  73. package/dist/dashboard/assets/{index-BydyKaDz.js → index-CmxZr14F.js} +2 -2
  74. package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
  75. package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-BhY2sSnO.js} +1 -1
  76. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  77. package/dist/dashboard/assets/{input-BG41et9G.js → input-V1umFiBk.js} +1 -1
  78. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  79. package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
  80. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  81. package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-1zzZYM0B.js} +1 -1
  82. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  83. package/dist/dashboard/assets/{live-BJGYRBnR.js → live-BivGfuUs.js} +1 -1
  84. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  85. package/dist/dashboard/assets/model-bans-CmffjLuG.js +1 -0
  86. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  87. package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-46P0oWMe.js} +1 -1
  88. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  89. package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
  90. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  91. package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-BFhGpwR1.js} +1 -1
  92. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  93. package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-CAb4F37R.js} +1 -1
  94. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  95. package/dist/dashboard/assets/{select-BO6h8yNv.js → select-BYR_5K2c.js} +2 -2
  96. package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
  97. package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-DNX9sydi.js} +1 -1
  98. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  99. package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-BHTfXdz_.js} +1 -1
  100. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  101. package/dist/dashboard/assets/{state-DYdQmdAl.js → state-mPDRlQkl.js} +1 -1
  102. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  103. package/dist/dashboard/assets/switch-DOmJxg7H.js +1 -0
  104. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  105. package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
  106. package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
  107. package/dist/dashboard/assets/tokens-COzario9.js +1 -0
  108. package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
  109. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
  110. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
  111. package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-Dem0lXQj.js} +1 -1
  112. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  113. package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
  114. package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
  115. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  116. package/dist/dashboard/index.html +1 -1
  117. package/dist/dashboard/index.html.gz +0 -0
  118. package/dist/dashboard/precompressed-assets.json +57 -57
  119. package/migrations/README.md +52 -52
  120. package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
  121. package/package.json +96 -95
  122. package/scripts/cli/admin.ts +125 -0
  123. package/scripts/cli/autostart.ts +237 -0
  124. package/scripts/cli/{ui/background.ts → background.ts} +1 -1
  125. package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -248
  126. package/scripts/cli/doctor.ts +37 -0
  127. package/scripts/cli/{support/env.ts → env.ts} +99 -99
  128. package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
  129. package/scripts/cli/instance.ts +224 -0
  130. package/scripts/cli/main.ts +88 -18
  131. package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
  132. package/scripts/cli/port.ts +100 -0
  133. package/scripts/cli/{support/services.ts → services.ts} +88 -88
  134. package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
  135. package/scripts/cli/{command/start.ts → start.ts} +10 -5
  136. package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
  137. package/scripts/cli/tray/index.ts +62 -12
  138. package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
  139. package/scripts/cli/update.ts +40 -0
  140. package/scripts/cli/version.ts +67 -0
  141. package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
  142. package/src/boot/app.ts +11 -7
  143. package/src/boot/dependencies.ts +50 -19
  144. package/src/boot/timeout.ts +2 -13
  145. package/src/config/cooldowns.ts +186 -138
  146. package/src/config/network.ts +8 -14
  147. package/src/config/providers.ts +7 -10
  148. package/src/config/runtime.ts +76 -76
  149. package/src/config/security.ts +15 -0
  150. package/src/config/spec.ts +52 -7
  151. package/src/config/telemetry.ts +26 -0
  152. package/src/config/timeouts.ts +0 -6
  153. package/src/console/admin/model-abuse/contracts.ts +28 -6
  154. package/src/console/admin/model-abuse/routes.ts +56 -3
  155. package/src/console/api-keys/bucket-spend.ts +15 -4
  156. package/src/console/api-keys/contracts.ts +410 -414
  157. package/src/console/api-keys/routes.ts +881 -880
  158. package/src/console/auth/credentials.ts +3 -3
  159. package/src/console/auth/setup-service.ts +2 -2
  160. package/src/console/backup/contracts.ts +15 -2
  161. package/src/console/backup/export-endpoint.ts +6 -2
  162. package/src/console/backup/nine-router.ts +2 -5
  163. package/src/console/backup/service.ts +4 -0
  164. package/src/console/backup/store.ts +192 -29
  165. package/src/console/backup/validate.ts +3 -45
  166. package/src/console/cli-tools/fs-ops.ts +487 -487
  167. package/src/console/console-router-guards.ts +1 -1
  168. package/src/console/console-router.ts +8 -0
  169. package/src/console/data-management/contracts.ts +100 -0
  170. package/src/console/data-management/operations.ts +234 -0
  171. package/src/console/data-management/routes.ts +103 -0
  172. package/src/console/domain-registration.ts +45 -1
  173. package/src/console/observability/contracts.ts +524 -485
  174. package/src/console/observability/live.ts +22 -0
  175. package/src/console/observability/store.ts +1138 -1018
  176. package/src/console/observability/usage-dimensions.ts +4 -4
  177. package/src/console/providers/catalog/catalog-projections.ts +6 -7
  178. package/src/console/providers/catalog/contracts.ts +6 -0
  179. package/src/console/providers/catalog/model-operations.ts +5 -2
  180. package/src/console/providers/catalog/provider-operations.ts +24 -4
  181. package/src/console/providers/catalog/public-model-store.ts +8 -11
  182. package/src/console/providers/catalog/routes.ts +7 -0
  183. package/src/console/providers/catalog/store.ts +1553 -1532
  184. package/src/console/providers/oauth/routes.ts +8 -12
  185. package/src/console/quota/account-quota-shared.ts +52 -39
  186. package/src/console/quota/account-quota-tenant.ts +6 -6
  187. package/src/console/quota/quota-view.ts +1 -1
  188. package/src/console/quota/refresh.ts +441 -429
  189. package/src/console/remote-support/contracts.ts +61 -0
  190. package/src/console/remote-support/routes.ts +65 -0
  191. package/src/console/remote-support/service.ts +207 -0
  192. package/src/console/response-compression.ts +0 -3
  193. package/src/console/routing/pools/import-parse.ts +206 -0
  194. package/src/console/routing/pools/operations.ts +543 -501
  195. package/src/console/routing/pools/probe-result.ts +3 -2
  196. package/src/console/routing/pools/probe-verdict.ts +132 -132
  197. package/src/console/routing/pools/routes.ts +181 -164
  198. package/src/console/routing/pools/store.ts +8 -8
  199. package/src/console/settings/contracts.ts +95 -2
  200. package/src/console/settings/store.ts +81 -3
  201. package/src/console/shared/cursor.ts +8 -8
  202. package/src/console/shared/errors.ts +1 -3
  203. package/src/console/updates/routes.ts +16 -0
  204. package/src/main.ts +10 -0
  205. package/src/network/http2-bidi.ts +2 -4
  206. package/src/network/pool/agent.ts +478 -478
  207. package/src/network/pool/selector.ts +5 -4
  208. package/src/network/retry.ts +1 -2
  209. package/src/network/ssrf.ts +5 -4
  210. package/src/observability/event-compaction.ts +317 -0
  211. package/src/observability/event-day-index.ts +440 -0
  212. package/src/observability/event-log.ts +155 -6
  213. package/src/observability/logger.ts +6 -6
  214. package/src/observability/payload-capture.ts +158 -33
  215. package/src/observability/payload-store.ts +82 -61
  216. package/src/observability/redaction.ts +5 -6
  217. package/src/observability/request-notify.ts +43 -0
  218. package/src/observability/telemetry-status.ts +8 -9
  219. package/src/observability/token-speed.ts +5 -4
  220. package/src/persistence/db-mode.ts +7 -7
  221. package/src/persistence/health-event-flush.ts +36 -0
  222. package/src/persistence/readiness.ts +1 -1
  223. package/src/persistence/schema.ts +75 -29
  224. package/src/persistence/shared-cache.ts +1 -2
  225. package/src/persistence/stores/api-key-store.ts +18 -6
  226. package/src/persistence/stores/share-store.ts +4 -3
  227. package/src/persistence/stores/telemetry-store.ts +33 -11
  228. package/src/protocol/canonical-model.ts +11 -3
  229. package/src/protocol/codecs.ts +139 -6
  230. package/src/protocol/gateway-error.ts +6 -4
  231. package/src/protocol/image-clamp.ts +19 -1
  232. package/src/protocol/registry.ts +8 -0
  233. package/src/protocol/request/chat.ts +21 -1
  234. package/src/protocol/request/codex.ts +10 -1
  235. package/src/protocol/request/gemini.ts +9 -2
  236. package/src/protocol/request/messages.ts +18 -7
  237. package/src/protocol/request/responses.ts +9 -1
  238. package/src/protocol/response/chat.ts +34 -4
  239. package/src/protocol/response/codex.ts +7 -4
  240. package/src/protocol/response/dsml.ts +140 -28
  241. package/src/protocol/response/responses.ts +11 -5
  242. package/src/protocol/stream-error-frames.ts +4 -5
  243. package/src/providers/adapters/compatible-adapter.ts +3 -0
  244. package/src/providers/authentication/jwt-validator.ts +9 -10
  245. package/src/providers/authentication/oauth-refresh-service.ts +9 -11
  246. package/src/providers/bundled-discovery-providers.ts +122 -119
  247. package/src/providers/configured-openai-providers.ts +4 -4
  248. package/src/providers/default-registry.ts +5 -4
  249. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  250. package/src/providers/discovery/catalog/catalog-format.ts +3 -6
  251. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  252. package/src/providers/discovery/openai-model-discovery.ts +5 -0
  253. package/src/providers/discovery/openrouter-free-roster.ts +15 -17
  254. package/src/providers/discovery/probe-phases.ts +4 -4
  255. package/src/providers/discovery/probe-wire.ts +6 -13
  256. package/src/providers/discovery/probing-service.ts +1117 -1045
  257. package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
  258. package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
  259. package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
  260. package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
  261. package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
  262. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
  263. package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
  264. package/src/providers/integrations/buddy/buddy-media.ts +2 -2
  265. package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
  266. package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
  267. package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
  268. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
  269. package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
  270. package/src/providers/integrations/claude/claude.ts +608 -608
  271. package/src/providers/integrations/cline/cline-quota.ts +27 -20
  272. package/src/providers/integrations/cline/cline.ts +537 -539
  273. package/src/providers/integrations/codex/codex-headers.ts +25 -39
  274. package/src/providers/integrations/codex/codex-oauth.ts +1 -1
  275. package/src/providers/integrations/codex/codex.ts +2 -29
  276. package/src/providers/integrations/commandcode/commandcode.ts +674 -675
  277. package/src/providers/integrations/commandcode/discovery.ts +117 -116
  278. package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
  279. package/src/providers/integrations/cursor/cursor-quota.ts +309 -0
  280. package/src/providers/integrations/cursor/cursor-request.ts +616 -471
  281. package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
  282. package/src/providers/integrations/cursor/cursor.ts +1333 -960
  283. package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
  284. package/src/providers/integrations/factory/factory-models.ts +3 -5
  285. package/src/providers/integrations/factory/factory.ts +8 -1
  286. package/src/providers/integrations/gemini.ts +6 -1
  287. package/src/providers/integrations/grok/grok-install-id.ts +2 -3
  288. package/src/providers/integrations/grok/grok-quota.ts +279 -177
  289. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
  290. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
  291. package/src/providers/integrations/grok/grok.ts +6 -7
  292. package/src/providers/integrations/hermes.ts +14 -18
  293. package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
  294. package/src/providers/integrations/kiro/kiro.ts +1 -1
  295. package/src/providers/integrations/mistral.ts +6 -6
  296. package/src/providers/integrations/opencode/fingerprint.ts +3 -3
  297. package/src/providers/integrations/opencode/provider.ts +19 -20
  298. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
  299. package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
  300. package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
  301. package/src/providers/integrations/qoder/qoder.ts +884 -865
  302. package/src/providers/integrations/shared/connect.ts +6 -6
  303. package/src/providers/integrations/trae/trae-catalog.ts +1 -2
  304. package/src/providers/integrations/trae/trae-quota.ts +384 -0
  305. package/src/providers/integrations/trae/trae.ts +2 -2
  306. package/src/providers/integrations/zcode/oauth.ts +6 -1
  307. package/src/providers/operations/account-health-service.ts +75 -27
  308. package/src/providers/operations/account-reset-service.ts +1 -1
  309. package/src/providers/operations/client-versions.ts +6 -6
  310. package/src/providers/operations/custom-cli-headers.ts +3 -4
  311. package/src/providers/operations/provider-catalog-seeder.ts +95 -95
  312. package/src/providers/operations/provider-catalog-service.ts +3 -4
  313. package/src/providers/provider-contracts.ts +7 -0
  314. package/src/providers/provider-metadata.ts +10 -8
  315. package/src/providers/provider-registry.ts +6 -7
  316. package/src/providers/provider-usage.ts +1 -1
  317. package/src/providers/quota/quota-support.ts +8 -0
  318. package/src/security/abuse.ts +15 -10
  319. package/src/security/access-control.ts +144 -146
  320. package/src/security/admission/contracts.ts +11 -0
  321. package/src/security/admission/in-memory-store.ts +4 -3
  322. package/src/security/admission/lease-sweep.ts +6 -6
  323. package/src/security/admission/reasons.ts +5 -1
  324. package/src/security/admission/redis-store.ts +211 -210
  325. package/src/security/admission/service.ts +4 -0
  326. package/src/security/api-key-auth.ts +27 -17
  327. package/src/security/ip-boundary.ts +155 -15
  328. package/src/security/model-abuse.ts +366 -94
  329. package/src/security/outbound-headers.ts +3 -4
  330. package/src/shared/sleep.ts +4 -0
  331. package/src/transport/dispatch/attempt-finalize.ts +47 -5
  332. package/src/transport/dispatch/attempt-loop.ts +531 -530
  333. package/src/transport/dispatch/generation.ts +6 -0
  334. package/src/transport/dispatch/proxy-request.ts +24 -1
  335. package/src/transport/dispatch/ratelimit-capture.ts +37 -0
  336. package/src/transport/dispatch/stream-bridge.ts +1 -3
  337. package/src/transport/dispatch/streaming/finalize.ts +1 -1
  338. package/src/transport/dispatch/streaming/stream.ts +19 -6
  339. package/src/transport/dispatch/upstream.ts +9 -0
  340. package/src/transport/failure-policy.ts +162 -8
  341. package/src/transport/guards/auth-guard.ts +7 -4
  342. package/src/transport/guards/concurrency-guard.ts +0 -5
  343. package/src/transport/guards/error-lifecycle.ts +13 -6
  344. package/src/transport/guards/pipeline.ts +15 -3
  345. package/src/transport/guards/request-context.ts +8 -2
  346. package/src/transport/request/inflight.ts +5 -6
  347. package/src/transport/request/preparer.ts +9 -8
  348. package/src/transport/request/state.ts +21 -8
  349. package/src/transport/request/tenant-preferences.ts +60 -0
  350. package/src/transport/routing/admission.ts +4 -0
  351. package/src/transport/routing/route-catalog.ts +65 -10
  352. package/src/transport/routing/route-model.ts +46 -14
  353. package/src/transport/routing/router.ts +21 -14
  354. package/src/transport/surface/completion.ts +2 -3
  355. package/src/transport/surface/dialects.ts +5 -6
  356. package/src/transport/surface/responses/encode.ts +38 -0
  357. package/src/transport/surface/responses/parse.ts +4 -0
  358. package/src/transport/translation/capabilities.ts +2 -3
  359. package/src/transport/translation/quirks.ts +3 -3
  360. package/src/workers/buddy-growth-tasks.ts +5 -10
  361. package/src/workers/daily-checkin.ts +17 -23
  362. package/src/workers/quota-refresh-sweep.ts +26 -26
  363. package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
  364. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +0 -1
  365. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
  366. package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
  367. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +0 -1
  368. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
  369. package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
  370. package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
  371. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +0 -1
  372. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
  373. package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
  374. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +0 -1
  375. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
  376. package/dist/dashboard/assets/OverviewPage-B53YYMng.js +0 -1
  377. package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
  378. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +0 -3
  379. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
  380. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +0 -1
  381. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
  382. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +0 -7
  383. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
  384. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +0 -1
  385. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
  386. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +0 -1
  387. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
  388. package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
  389. package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
  390. package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
  391. package/dist/dashboard/assets/UsagePage-B47uOydr.js +0 -56
  392. package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
  393. package/dist/dashboard/assets/api-keys-uNywYlfv.js +0 -1
  394. package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
  395. package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
  396. package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
  397. package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
  398. package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
  399. package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
  400. package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
  401. package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
  402. package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
  403. package/dist/dashboard/assets/dialog-BtpsJU98.js +0 -1
  404. package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
  405. package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
  406. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  407. package/dist/dashboard/assets/drawer-WwwtzihF.js +0 -1
  408. package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
  409. package/dist/dashboard/assets/entry-CkryrIea.js +0 -4
  410. package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
  411. package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
  412. package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
  413. package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
  414. package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
  415. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  416. package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
  417. package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
  418. package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
  419. package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
  420. package/dist/dashboard/assets/layout-C8mer_Ak.js +0 -1
  421. package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
  422. package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
  423. package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
  424. package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
  425. package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
  426. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  427. package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
  428. package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
  429. package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
  430. package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
  431. package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
  432. package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
  433. package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
  434. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  435. package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
  436. package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
  437. package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
  438. package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
  439. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
  440. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  441. package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
  442. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  443. package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
  444. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  445. package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
  446. package/scripts/cli/command/admin.ts +0 -65
  447. package/scripts/cli/command/doctor.ts +0 -257
  448. package/scripts/cli/command/update.ts +0 -23
  449. package/scripts/cli/gateway/drain-token.ts +0 -78
  450. package/scripts/cli/gateway/instance-lock.ts +0 -105
  451. package/scripts/cli/gateway/stop.ts +0 -51
  452. package/scripts/cli/support/bun.ts +0 -27
  453. package/scripts/cli/support/version.ts +0 -37
  454. /package/dist/dashboard/assets/{app-CafWtVXA.js → app-BIt446yE.js} +0 -0
  455. /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-BIt446yE.js.gz} +0 -0
  456. /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
@@ -278,7 +278,7 @@ export class NetworkPoolSelector {
278
278
  // One script, not `SET` then `SADD`: a crash between the two left the
279
279
  // marker unindexed (invisible to the pool listing) or the index
280
280
  // pointing at a marker that expired. The script also arms the index
281
- // set's own TTL, which previously had none and so grew without bound.
281
+ // set's own TTL, which must be armed or the index grows without bound.
282
282
  await redisEvalNumber(
283
283
  this.redis,
284
284
  COOLDOWN_FLAG_SCRIPT,
@@ -458,9 +458,10 @@ export class NetworkPoolSelector {
458
458
  /**
459
459
  * Removes one cooldown marker and its index entry in one atomic step.
460
460
  *
461
- * The two writes used to be separate `DEL` + `SREM`, so a crash between them
462
- * left the index listing a provider whose marker was gone — a pool would
463
- * report a cooldown that no longer applied until the index entry expired.
461
+ * The two writes must not be separate `DEL` + `SREM`: a crash between them
462
+ * would leave the index listing a provider whose marker is gone — a pool
463
+ * would report a cooldown that no longer applies until the index entry
464
+ * expires.
464
465
  */
465
466
  private async clearRedisCooldown(poolId: string, providerId: string): Promise<void> {
466
467
  if (!this.redis) return;
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { exponentialBackoff, type BackoffOptions } from "./backoff";
10
+ import { sleep as defaultSleep } from "../shared/sleep";
10
11
 
11
12
  export interface RetryOptions {
12
13
  readonly maxRetries?: number;
@@ -18,8 +19,6 @@ export interface RetryOptions {
18
19
  readonly shouldRetry?: (error: unknown, attempt: number) => boolean;
19
20
  }
20
21
 
21
- const defaultSleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
22
-
23
22
  /**
24
23
  * Runs `fn`, retrying on failure with exponential backoff (100 ms, 200 ms,
25
24
  * 400 ms, …) capped at `maxDelayMs` and jittered by up to 25% to avoid a
@@ -73,10 +73,11 @@ function cidrContains(address: string, cidr: string): boolean {
73
73
  const [network, prefixText] = cidr.split("/");
74
74
  if (!network || prefixText === undefined) return false;
75
75
  // A present-but-blank prefix (`"10.0.0.0/"`) must be refused, not coerced.
76
- // `Number("")` is 0 and `""` is not `undefined`, so the guard below used to fall
77
- // through with `prefix = 0` — and a /0 admits every address of its family. An
78
- // operator's typo therefore silently widened the allowlist to the whole IPv4
79
- // space, including the link-local metadata endpoint this guard exists to refuse.
76
+ // `Number("")` is 0 and `""` is not `undefined`, so a plain truthiness check
77
+ // here would leave `prefix = 0` — and a /0 admits every address of its family.
78
+ // An operator's typo would therefore silently widen the allowlist to the whole
79
+ // IPv4 space, including the link-local metadata endpoint this guard exists to
80
+ // refuse.
80
81
  //
81
82
  // Requiring a run of digits (after trimming) also refuses the exotic spellings
82
83
  // `Number` accepts for zero — `-0`, `+0`, `0x0`, `0b0`, `0o0`, `0.0` — all of
@@ -0,0 +1,317 @@
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
+ }