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,440 +0,0 @@
1
- import { mkdir, open, readFile, rename, stat, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import {
4
- accumulate,
5
- emptyBucket,
6
- mergeBucketsInternal,
7
- type CompactedBucket,
8
- } from "./event-compaction";
9
- import { TELEMETRY_EVENT_DIR, type TelemetryEventRecord } from "./event-log";
10
-
11
- /**
12
- * One sidecar index per day, beside that day's append-only event file.
13
- *
14
- * The event file grows all day and is never rewritten while it is open. The
15
- * index is the thing the dashboard reads: it holds the totals and the breakdown
16
- * counts, maintained incrementally from a byte watermark rather than rebuilt by
17
- * folding and deleting source files.
18
- *
19
- * The watermark is what makes that possible. Deciding which file was still
20
- * being written by comparing file *names* lets the open file be folded and
21
- * unlinked mid-write, so its requests never reach the summary. A byte offset
22
- * has no such ambiguity: everything below it is counted, everything above it is
23
- * not, and re-reading the tail is idempotent.
24
- */
25
-
26
- /**
27
- * An event whose tenant is known to be present.
28
- *
29
- * `tenantId` is optional on the record because a row can arrive without one,
30
- * but the index is keyed by tenant, so only rows carrying one are folded.
31
- */
32
- type FoldedEvent = TelemetryEventRecord & { readonly tenantId: string };
33
-
34
- /** Marker so a reader can tell an index from anything else in the directory. */
35
- const INDEX_KIND = "telemetry-day-index" as const;
36
-
37
- /** Version bumped whenever the shape changes; an unreadable version is ignored. */
38
- const INDEX_VERSION = 1 as const;
39
-
40
- /** The event file for one UTC day. */
41
- export function dayEventFileName(day: string): string {
42
- return `${day}.events.jsonl`;
43
- }
44
-
45
- /** The sidecar index for one UTC day. */
46
- export function dayIndexFileName(day: string): string {
47
- return `${day}.index.json`;
48
- }
49
-
50
- /** The UTC day (`2026-10-22`) a moment belongs to. */
51
- export function utcDay(at: Date): string {
52
- return at.toISOString().slice(0, 10);
53
- }
54
-
55
- /**
56
- * One hour's share of a bucket.
57
- *
58
- * The Traffic chart asks how a day was spent, which the totals alone cannot
59
- * answer. Twenty-four slots per bucket is small — four numbers each — so it is
60
- * held *inside* the index rather than as separate per-hour files.
61
- */
62
- /**
63
- * One named dimension's share of a bucket.
64
- *
65
- * The breakdown asks for tokens, errors and cost per model/provider/client/IP,
66
- * not merely how many requests each took. Counts alone would leave every
67
- * breakdown column reading zero, so each named entry carries the same running
68
- * sums the bucket does.
69
- */
70
- export interface IndexedDimension {
71
- readonly requests: number;
72
- readonly inputTokens: number;
73
- readonly cachedTokens: number;
74
- readonly outputTokens: number;
75
- readonly errors: number;
76
- readonly estimatedCostUsd: number;
77
- readonly tokensPerSecSum: number;
78
- readonly tokensPerSecCount: number;
79
- }
80
-
81
- export interface IndexedHour {
82
- readonly requests: number;
83
- readonly inputTokens: number;
84
- readonly cachedTokens: number;
85
- readonly outputTokens: number;
86
- }
87
-
88
- /** One tenant/key pair's running totals for the day. */
89
- export interface IndexedBucket extends CompactedBucket {
90
- readonly tenantId: string;
91
- readonly apiKeyId: string | null;
92
- /** Index 0-23, the UTC hour an event fell in. */
93
- readonly hours: readonly IndexedHour[];
94
- /** Per-name sums for each breakdown dimension. */
95
- readonly dimModel: Readonly<Record<string, IndexedDimension>>;
96
- readonly dimProvider: Readonly<Record<string, IndexedDimension>>;
97
- readonly dimClient: Readonly<Record<string, IndexedDimension>>;
98
- readonly dimClientIp: Readonly<Record<string, IndexedDimension>>;
99
- }
100
-
101
- /** The on-disk shape of a day index. */
102
- export interface DayIndex {
103
- readonly kind: typeof INDEX_KIND;
104
- readonly version: typeof INDEX_VERSION;
105
- readonly day: string;
106
- /**
107
- * Bytes of the event file already folded in.
108
- *
109
- * Everything past this offset is uncounted; a crash between append and index
110
- * write only ever leaves it behind, never ahead, so the tail is re-read.
111
- */
112
- readonly watermarkBytes: number;
113
- /** Totals per tenant/key. */
114
- readonly buckets: readonly IndexedBucket[];
115
- /** Wall-clock of the last fold, for operators reading the file by hand. */
116
- readonly updatedAt: string;
117
- }
118
-
119
- function bucketKey(tenantId: string, apiKeyId: string | null): string {
120
- return `${tenantId}|${apiKeyId ?? ""}`;
121
- }
122
-
123
- /** Twenty-four empty hour slots. */
124
- function emptyHours(): IndexedHour[] {
125
- return Array.from({ length: 24 }, () => ({
126
- requests: 0,
127
- inputTokens: 0,
128
- cachedTokens: 0,
129
- outputTokens: 0,
130
- }));
131
- }
132
-
133
- /** The UTC hour (0-23) of an ISO timestamp. */
134
- function hourOf(createdAt: string): number {
135
- const at = Date.parse(createdAt);
136
- if (!Number.isFinite(at)) return 0;
137
- return Number.parseInt(createdAt.slice(11, 13), 10) || 0;
138
- }
139
-
140
- /** An empty per-name entry. */
141
- function emptyDimension(): IndexedDimension {
142
- return {
143
- requests: 0,
144
- inputTokens: 0,
145
- cachedTokens: 0,
146
- outputTokens: 0,
147
- errors: 0,
148
- estimatedCostUsd: 0,
149
- tokensPerSecSum: 0,
150
- tokensPerSecCount: 0,
151
- };
152
- }
153
-
154
- /**
155
- * A cost as a number.
156
- *
157
- * Costs arrive as strings from `numeric` columns and from restored rows, and
158
- * `0 + "0.000966"` concatenates rather than sums. Coerced once, here, so the
159
- * per-name breakdown cannot accumulate a string the way it did.
160
- */
161
- function numericCost(value: unknown): number {
162
- const asNumber = typeof value === "number" ? value : Number(value);
163
- return Number.isFinite(asNumber) ? asNumber : 0;
164
- }
165
-
166
- /** Folds one event into one named dimension entry. */
167
- function foldDimension(
168
- into: Readonly<Record<string, IndexedDimension>> | undefined,
169
- name: string | null | undefined,
170
- event: FoldedEvent,
171
- ): Record<string, IndexedDimension> {
172
- const out: Record<string, IndexedDimension> = { ...(into ?? {}) };
173
- const key = typeof name === "string" ? name.trim() : "";
174
- if (key === "") return out;
175
- const prior = out[key] ?? emptyDimension();
176
- const failed = event.status === "failed" ? 1 : 0;
177
- out[key] = {
178
- requests: prior.requests + 1,
179
- inputTokens: prior.inputTokens + (event.inputTokens ?? 0),
180
- cachedTokens: prior.cachedTokens + (event.cachedInputTokens ?? 0),
181
- outputTokens: prior.outputTokens + (event.outputTokens ?? 0),
182
- errors: prior.errors + failed,
183
- estimatedCostUsd: prior.estimatedCostUsd + numericCost(event.estimatedCostUsd),
184
- tokensPerSecSum: prior.tokensPerSecSum + (event.tokensPerSec ?? 0),
185
- tokensPerSecCount: prior.tokensPerSecCount + (event.tokensPerSec === null ? 0 : 1),
186
- };
187
- return out;
188
- }
189
-
190
- /** Adds one event's tokens into its hour slot. */
191
- function withHour(
192
- hours: readonly IndexedHour[],
193
- event: FoldedEvent,
194
- ): IndexedHour[] {
195
- const base = hours.length === 24 ? [...hours] : emptyHours();
196
- const index = hourOf(event.createdAt);
197
- const prior = base[index] ?? { requests: 0, inputTokens: 0, cachedTokens: 0, outputTokens: 0 };
198
- base[index] = {
199
- requests: prior.requests + 1,
200
- inputTokens: prior.inputTokens + (event.inputTokens ?? 0),
201
- cachedTokens: prior.cachedTokens + (event.cachedInputTokens ?? 0),
202
- outputTokens: prior.outputTokens + (event.outputTokens ?? 0),
203
- };
204
- return base;
205
- }
206
-
207
- /** An empty index for a day with nothing counted yet. */
208
- export function emptyDayIndex(day: string): DayIndex {
209
- return {
210
- kind: INDEX_KIND,
211
- version: INDEX_VERSION,
212
- day,
213
- watermarkBytes: 0,
214
- buckets: [],
215
- updatedAt: new Date().toISOString(),
216
- };
217
- }
218
-
219
- /** True when the index is one this version can read. */
220
- function isDayIndex(value: unknown): value is DayIndex {
221
- if (typeof value !== "object" || value === null) return false;
222
- const candidate = value as Partial<DayIndex>;
223
- return (
224
- candidate.kind === INDEX_KIND &&
225
- candidate.version === INDEX_VERSION &&
226
- typeof candidate.day === "string" &&
227
- typeof candidate.watermarkBytes === "number" &&
228
- Array.isArray(candidate.buckets)
229
- );
230
- }
231
-
232
- /** Reads a day index, or an empty one when it is absent or unreadable. */
233
- export async function readDayIndex(day: string): Promise<DayIndex> {
234
- const text = await readFile(join(TELEMETRY_EVENT_DIR, dayIndexFileName(day)), "utf8").catch(
235
- () => undefined,
236
- );
237
- if (text === undefined) return emptyDayIndex(day);
238
- try {
239
- const parsed: unknown = JSON.parse(text);
240
- return isDayIndex(parsed) ? parsed : emptyDayIndex(day);
241
- } catch {
242
- // A torn index costs nothing to rebuild — the event file is the record — so
243
- // a parse failure starts over rather than failing the read.
244
- return emptyDayIndex(day);
245
- }
246
- }
247
-
248
- /**
249
- * Writes an index atomically.
250
- *
251
- * Written to a pid-suffixed temp file and renamed, so a reader never sees a
252
- * half-written index and a crash mid-write leaves the previous one in place.
253
- */
254
- export async function writeDayIndex(index: DayIndex): Promise<void> {
255
- await mkdir(TELEMETRY_EVENT_DIR, { recursive: true });
256
- const target = join(TELEMETRY_EVENT_DIR, dayIndexFileName(index.day));
257
- const temp = `${target}.${process.pid}.tmp`;
258
- await writeFile(temp, `${JSON.stringify(index)}\n`, "utf8");
259
- await rename(temp, target);
260
- }
261
-
262
- /** Byte size of a day's event file; 0 when it does not exist. */
263
- export async function dayEventFileBytes(day: string): Promise<number> {
264
- const size = await stat(join(TELEMETRY_EVENT_DIR, dayEventFileName(day)))
265
- .then((value) => value.size)
266
- .catch(() => undefined);
267
- return size ?? 0;
268
- }
269
-
270
- /**
271
- * Reads the uncounted tail of a day's event file.
272
- *
273
- * Only bytes past `fromBytes` are read, and a partial line at the very end is
274
- * dropped rather than counted: the writer may still be appending to it, and a
275
- * line that is not yet terminated is not yet an event.
276
- */
277
- async function* readTail(
278
- day: string,
279
- fromBytes: number,
280
- ): AsyncGenerator<{ readonly event: FoldedEvent; readonly endBytes: number }> {
281
- const handle = await open(join(TELEMETRY_EVENT_DIR, dayEventFileName(day)), "r").catch(
282
- () => undefined,
283
- );
284
- if (handle === undefined) return;
285
- try {
286
- const size = (await handle.stat().catch(() => undefined))?.size ?? 0;
287
- if (size <= fromBytes) return;
288
- const length = size - fromBytes;
289
- const buffer = Buffer.alloc(length);
290
- await handle.read(buffer, 0, length, fromBytes);
291
- const text = buffer.toString("utf8");
292
- const lines = text.split("\n");
293
- // The final element is either empty or a line the writer has not finished;
294
- // a unterminated line is not an event yet, so it is left for the next fold.
295
- if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
296
- else lines.pop();
297
- let consumed = 0;
298
- for (const line of lines) {
299
- consumed += Buffer.byteLength(line, "utf8") + 1;
300
- if (line.length === 0) continue;
301
- const event = parseEventLine(line);
302
- if (event !== undefined) yield { event, endBytes: fromBytes + consumed };
303
- }
304
- } finally {
305
- await handle.close().catch(() => undefined);
306
- }
307
- }
308
-
309
- /** Parses one event line, or `undefined` when it is not a usable event. */
310
- function parseEventLine(line: string): FoldedEvent | undefined {
311
- try {
312
- const parsed: unknown = JSON.parse(line);
313
- if (typeof parsed !== "object" || parsed === null) return undefined;
314
- const candidate = parsed as Record<string, unknown>;
315
- if (typeof candidate["tenantId"] !== "string") return undefined;
316
- if (typeof candidate["requestId"] !== "string") return undefined;
317
- if (typeof candidate["createdAt"] !== "string") return undefined;
318
- // `tenantId` is checked above but declared optional on the record, so the
319
- // narrow shape is asserted here rather than at every use site.
320
- return candidate as unknown as FoldedEvent;
321
- } catch {
322
- return undefined;
323
- }
324
- }
325
-
326
- /**
327
- * Folds every event written since the watermark into the day index.
328
- *
329
- * Safe to call as often as you like: the watermark advances only after the
330
- * index is on disk, so a crash means some events are folded twice at worst —
331
- * and folding is a sum, so that is corrected by running again with the tail,
332
- * not by any compensating logic.
333
- */
334
- export async function foldDayIndex(day: string): Promise<{ readonly folded: number }> {
335
- const index = await readDayIndex(day);
336
- const byKey = new Map<string, IndexedBucket>();
337
- for (const bucket of index.buckets) {
338
- byKey.set(bucketKey(bucket.tenantId, bucket.apiKeyId), bucket);
339
- }
340
-
341
- let watermark = index.watermarkBytes;
342
- let folded = 0;
343
- for await (const { event, endBytes } of readTail(day, index.watermarkBytes)) {
344
- const key = bucketKey(event.tenantId, event.apiKeyId ?? null);
345
- const prior = byKey.get(key);
346
- const base: IndexedBucket =
347
- prior ?? {
348
- ...emptyBucket(),
349
- tenantId: event.tenantId,
350
- apiKeyId: event.apiKeyId ?? null,
351
- hours: emptyHours(),
352
- dimModel: {},
353
- dimProvider: {},
354
- dimClient: {},
355
- dimClientIp: {},
356
- };
357
- byKey.set(key, {
358
- ...accumulate(base, event),
359
- tenantId: base.tenantId,
360
- apiKeyId: base.apiKeyId,
361
- hours: withHour(base.hours, event),
362
- dimModel: foldDimension(base.dimModel, event.requestedModel ?? event.reportedModel, event),
363
- dimProvider: foldDimension(base.dimProvider, event.providerId, event),
364
- dimClient: foldDimension(base.dimClient, event.sourceSurface, event),
365
- dimClientIp: foldDimension(base.dimClientIp, event.clientIp, event),
366
- });
367
- watermark = Math.max(watermark, endBytes);
368
- folded += 1;
369
- }
370
-
371
- if (folded === 0) return { folded: 0 };
372
- await writeDayIndex({
373
- kind: INDEX_KIND,
374
- version: INDEX_VERSION,
375
- day,
376
- watermarkBytes: watermark,
377
- buckets: [...byKey.values()],
378
- updatedAt: new Date().toISOString(),
379
- });
380
- return { folded };
381
- }
382
-
383
- /** The days whose index should be folded, newest last. */
384
- export async function listEventDays(): Promise<readonly string[]> {
385
- const { readdir } = await import("node:fs/promises");
386
- const files = await readdir(TELEMETRY_EVENT_DIR).catch(() => [] as string[]);
387
- const days = new Set<string>();
388
- for (const file of files) {
389
- if (file.endsWith(".events.jsonl")) days.add(file.slice(0, 10));
390
- }
391
- return [...days].sort();
392
- }
393
-
394
- /**
395
- * Every bucket for one tenant, across the days a period covers.
396
- *
397
- * Reads only the index files for those days — a few small JSON reads — rather
398
- * than parsing the event files, which is what made a usage query cost grow with
399
- * traffic.
400
- */
401
- export async function queryIndexedBuckets(options: {
402
- /** Omit to read every tenant — a caller that filters by key, not by tenant. */
403
- readonly tenantId?: string | undefined;
404
- readonly since?: Date | undefined;
405
- /** Fold each day's tail first, so a read never reports a stale total. */
406
- readonly foldFirst?: boolean;
407
- }): Promise<readonly IndexedBucket[]> {
408
- const days = await listEventDays();
409
- const sinceDay = options.since === undefined ? undefined : utcDay(options.since);
410
- const out: IndexedBucket[] = [];
411
- for (const day of days) {
412
- if (sinceDay !== undefined && day < sinceDay) continue;
413
- if (options.foldFirst === true) await foldDayIndex(day).catch(() => ({ folded: 0 }));
414
- const index = await readDayIndex(day);
415
- for (const bucket of index.buckets) {
416
- if (options.tenantId !== undefined && bucket.tenantId !== options.tenantId) continue;
417
- out.push(bucket);
418
- }
419
- }
420
- return out;
421
- }
422
-
423
- /** The days a period spans, oldest first. */
424
- export function daysSince(since: Date | undefined, now = new Date()): readonly string[] {
425
- const start = since === undefined ? now : since;
426
- const out: string[] = [];
427
- const cursor = new Date(Date.UTC(
428
- start.getUTCFullYear(),
429
- start.getUTCMonth(),
430
- start.getUTCDate(),
431
- ));
432
- const end = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
433
- while (cursor.getTime() <= end && out.length < 400) {
434
- out.push(utcDay(cursor));
435
- cursor.setUTCDate(cursor.getUTCDate() + 1);
436
- }
437
- return out;
438
- }
439
-
440
- export { mergeBucketsInternal as mergeBuckets };