cartethyia 2.7.3 → 2.7.5

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 (625) hide show
  1. package/.env.example +2 -0
  2. package/README.md +17 -5
  3. package/dist/dashboard/assets/{BannedPage-2TJD37Hb.js → BannedPage-Cj15GsMp.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
  5. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +1 -0
  6. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolDetailPage-xefJNRmJ.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
  8. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  9. package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.js → CliToolsPage-BIccZmeL.js} +1 -1
  10. package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
  11. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +1 -0
  12. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js +1 -0
  14. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
  15. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js +1 -0
  16. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js +656 -0
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-C4w7vea-.js → LoginPage-DkXb6dx-.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BtPcAfot.js → ModelPicker-DnQPSIA8.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +5 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
  31. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js +9 -0
  32. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-BQS0Rx_u.js → SetupPage-WTa0sktC.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-TP7voAQ0.js → StudioPage-BfAb5-o_.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-l29JROPv.js +1 -0
  44. package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
  45. package/dist/dashboard/assets/app--PJWq8_C.css +2 -0
  46. package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-C0YYuZJ4.js → assert-C0K-c4eo.js} +1 -1
  48. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-DfbhQU4k.js} +1 -1
  50. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-DMrMPmth.js → banner-BllT4LcF.js} +1 -1
  52. package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
  53. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  54. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  55. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  56. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  57. package/dist/dashboard/assets/{card-Be5Uzamx.js → card-CtkX66Wd.js} +1 -1
  58. package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
  59. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
  60. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
  61. package/dist/dashboard/assets/{cli-tools-muFRphDu.js → cli-tools-CQXVMn4P.js} +1 -1
  62. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.js → clipboard-button-BQqBQd2n.js} +1 -1
  64. package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
  65. package/dist/dashboard/assets/{dialog-C9omdjQt.js → dialog-BEK7KP0H.js} +1 -1
  66. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-Bm8WKaGY.js → drawer-BrTEZ8zu.js} +1 -1
  68. package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-CHWxbS56.js +1 -0
  70. package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
  71. package/dist/dashboard/assets/{entry-q5HwZ8nN.js → entry-EBHJ3DrE.js} +1 -1
  72. package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-LCS85hiq.js +4 -0
  74. package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
  75. package/dist/dashboard/assets/{formatters-BTScrQpe.js → formatters-BAACA727.js} +1 -1
  76. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  77. package/dist/dashboard/assets/{github-badge-Orjuzemr.js → github-badge-YSCE4UC2.js} +1 -1
  78. package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
  79. package/dist/dashboard/assets/{index-cPrX5vn6.js → index-FQlj0HUT.js} +2 -2
  80. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-C4gU5iOL.js} +1 -1
  82. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-CU1Rgf3p.js → input-BHHX_Tmm.js} +1 -1
  84. package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
  85. package/dist/dashboard/assets/{layout-C_7A6H55.js → layout-CajrYUnD.js} +1 -1
  86. package/dist/dashboard/assets/{layout-C_7A6H55.js.gz → layout-CajrYUnD.js.gz} +0 -0
  87. package/dist/dashboard/assets/{list-row-CzFvogbj.js → list-row-Bx2I7XUe.js} +1 -1
  88. package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
  89. package/dist/dashboard/assets/live-DNNqRbj2.js +1 -0
  90. package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
  91. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +1 -0
  92. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  93. package/dist/dashboard/assets/{mutation-JJGQm8TD.js → mutation-Rr0TIvcj.js} +1 -1
  94. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  95. package/dist/dashboard/assets/network-C5UBACrJ.js +1 -0
  96. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  97. package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
  98. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  99. package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
  100. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  101. package/dist/dashboard/assets/{query-keys-CLaNz8Xz.js → query-keys-BloKtaXJ.js} +1 -1
  102. package/dist/dashboard/assets/query-keys-BloKtaXJ.js.gz +0 -0
  103. package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
  104. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  105. package/dist/dashboard/assets/{routing-BEH32QOC.js → routing-in0f_IfK.js} +1 -1
  106. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  107. package/dist/dashboard/assets/{select-B3iTetHN.js → select-BLULNqTp.js} +2 -2
  108. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  109. package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
  110. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  111. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  112. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  113. package/dist/dashboard/assets/{stack-KrOrm7JZ.js → stack-D2lIR0tg.js} +1 -1
  114. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  115. package/dist/dashboard/assets/{state-DLS7IDMy.js → state-DTARjfiW.js} +1 -1
  116. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  117. package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-c0oPSnqW.js} +1 -1
  118. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  119. package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.js → useMutation-m8WGNPrz.js} +1 -1
  120. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  121. package/dist/dashboard/console-backgrounds.jpg +0 -0
  122. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  123. package/dist/dashboard/index.html +2 -2
  124. package/dist/dashboard/index.html.gz +0 -0
  125. package/dist/dashboard/precompressed-assets.json +59 -57
  126. package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +9 -10
  127. package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
  128. package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
  129. package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
  130. package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
  131. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  132. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  133. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  134. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  135. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  136. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  137. package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +9 -10
  138. package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
  139. package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
  140. package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
  141. package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
  142. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  143. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  144. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  145. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  146. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  147. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  148. package/package.json +4 -5
  149. package/scripts/cli/update.ts +15 -41
  150. package/src/boot/app.ts +67 -10
  151. package/src/boot/dependencies.ts +786 -732
  152. package/src/boot/lifecycle.ts +11 -12
  153. package/src/config/cooldowns.ts +34 -55
  154. package/src/config/network.ts +8 -4
  155. package/src/config/package-paths.ts +1 -1
  156. package/src/config/providers.ts +13 -14
  157. package/src/config/resources.ts +0 -5
  158. package/src/config/runtime.ts +2 -5
  159. package/src/config/spec.ts +26 -6
  160. package/src/console/api-keys/ban-constants.ts +38 -0
  161. package/src/console/api-keys/contracts.ts +92 -31
  162. package/src/console/api-keys/routes.ts +131 -6
  163. package/src/console/audit/routes.ts +3 -3
  164. package/src/console/auth/access.ts +4 -4
  165. package/src/console/auth/account-reset.ts +5 -5
  166. package/src/console/auth/audit-recorder.ts +1 -1
  167. package/src/console/auth/lockout.ts +7 -3
  168. package/src/console/auth/row-coercion.ts +57 -12
  169. package/src/console/auth/session-resolver.ts +1 -1
  170. package/src/console/auth/session-service.ts +8 -2
  171. package/src/console/auth/session.ts +40 -34
  172. package/src/console/auth/setup-service.ts +4 -3
  173. package/src/console/auth/user-store.ts +1 -1
  174. package/src/console/backup/9router-map.ts +0 -3
  175. package/src/console/backup/contracts.ts +11 -9
  176. package/src/console/backup/export-endpoint.ts +11 -12
  177. package/src/console/backup/nine-router.ts +1 -7
  178. package/src/console/backup/routes.ts +32 -36
  179. package/src/console/backup/service.ts +468 -468
  180. package/src/console/backup/store.ts +47 -29
  181. package/src/console/backup/validate.ts +11 -1
  182. package/src/console/cli-tools/contracts.ts +7 -7
  183. package/src/console/cli-tools/injectors/cline.ts +110 -110
  184. package/src/console/cli-tools/injectors/codex.ts +181 -181
  185. package/src/console/cli-tools/injectors/copilot.ts +108 -108
  186. package/src/console/cli-tools/injectors/driver.ts +247 -247
  187. package/src/console/cli-tools/injectors/jcode.ts +115 -115
  188. package/src/console/cli-tools/injectors/openclaw.ts +161 -161
  189. package/src/console/cli-tools/injectors/opencode.ts +153 -153
  190. package/src/console/cli-tools/routes.ts +4 -4
  191. package/src/console/cli-tools/service.ts +1 -1
  192. package/src/console/cli-tools/store.ts +2 -2
  193. package/src/console/console-router-guards.ts +2 -2
  194. package/src/console/console-router.ts +36 -4
  195. package/src/console/dashboard-assets.ts +9 -9
  196. package/src/console/data-management/routes.ts +3 -3
  197. package/src/console/domain-registration.ts +18 -4
  198. package/src/console/inferhub/client.ts +0 -7
  199. package/src/console/inferhub/key-store.ts +1 -1
  200. package/src/console/inferhub/normalize.ts +2 -2
  201. package/src/console/inferhub/routes.ts +1 -1
  202. package/src/console/inferhub/types.ts +1 -1
  203. package/src/console/observability/contracts.ts +5 -1
  204. package/src/console/observability/live.ts +34 -21
  205. package/src/console/observability/logs.ts +4 -4
  206. package/src/console/observability/performance.ts +3 -3
  207. package/src/console/observability/routes.ts +3 -3
  208. package/src/console/observability/sse.ts +62 -8
  209. package/src/console/observability/store.ts +36 -20
  210. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  211. package/src/console/providers/catalog/contracts.ts +82 -22
  212. package/src/console/providers/catalog/model-operations.ts +1 -1
  213. package/src/console/providers/catalog/provider-operations.ts +110 -17
  214. package/src/console/providers/catalog/public-model-store.ts +0 -0
  215. package/src/console/providers/catalog/routes.ts +30 -6
  216. package/src/console/providers/catalog/store-helpers.ts +349 -326
  217. package/src/console/providers/catalog/store.ts +1662 -1292
  218. package/src/console/providers/detail/routes.ts +4 -4
  219. package/src/console/providers/oauth/callback-listener.ts +100 -18
  220. package/src/console/providers/oauth/routes.ts +95 -16
  221. package/src/console/quota/account-quota-global.ts +41 -3
  222. package/src/console/quota/account-quota-shared.ts +10 -3
  223. package/src/console/quota/account-quota-tenant.ts +285 -67
  224. package/src/console/quota/account-test-egress.ts +4 -4
  225. package/src/console/quota/daily-growth-job.ts +308 -0
  226. package/src/console/quota/native-identity.ts +2 -2
  227. package/src/console/quota/quota-view.ts +1 -1
  228. package/src/console/remote-support/routes.ts +1 -1
  229. package/src/console/remote-support/service.ts +2 -2
  230. package/src/console/routing/model/contracts.ts +1 -1
  231. package/src/console/routing/model/routes.ts +3 -3
  232. package/src/console/routing/pools/contracts.ts +13 -10
  233. package/src/console/routing/pools/import-parse.ts +3 -3
  234. package/src/console/routing/pools/operations.ts +14 -7
  235. package/src/console/routing/pools/probe-verdict.ts +8 -3
  236. package/src/console/routing/pools/relay-deploy.ts +12 -12
  237. package/src/console/routing/pools/routes.ts +3 -3
  238. package/src/console/routing/pools/store.ts +21 -1
  239. package/src/console/settings/contracts.ts +29 -2
  240. package/src/console/settings/routes.ts +3 -3
  241. package/src/console/settings/store.ts +20 -0
  242. package/src/console/share/share-router.ts +112 -44
  243. package/src/console/share/share-stats.ts +109 -39
  244. package/src/console/share/share-usage.ts +1 -1
  245. package/src/console/shared/errors.ts +2 -2
  246. package/src/console/shared/object.ts +18 -0
  247. package/src/console/shared/read-through-cache.ts +42 -6
  248. package/src/console/shared/transaction.ts +14 -0
  249. package/src/console/system-prompt/contracts.ts +89 -12
  250. package/src/console/system-prompt/routes.ts +98 -45
  251. package/src/console/updates/apply-update.ts +244 -0
  252. package/src/console/updates/routes.ts +256 -98
  253. package/src/main.ts +11 -5
  254. package/src/network/http2-bidi.ts +1 -1
  255. package/src/network/ip-utils.ts +298 -0
  256. package/src/network/outbound-fetch.ts +76 -5
  257. package/src/network/pool/agent.ts +7 -6
  258. package/src/network/pool/byte-accounting.ts +1 -1
  259. package/src/network/pool/resolver.ts +55 -51
  260. package/src/network/pool/selector.ts +150 -136
  261. package/src/network/pool-cooldown-store.ts +71 -16
  262. package/src/network/pool-health-machine.ts +22 -2
  263. package/src/network/rate-limit-verdict.ts +68 -0
  264. package/src/network/retry.ts +1 -1
  265. package/src/network/ssrf.ts +24 -75
  266. package/src/observability/event-log.ts +2 -2
  267. package/src/observability/event-query.ts +286 -214
  268. package/src/observability/log-sanitize.ts +1 -1
  269. package/src/observability/metrics.ts +4 -4
  270. package/src/observability/payload-capture.ts +6 -5
  271. package/src/observability/payload-store.ts +1 -1
  272. package/src/observability/runtime-metrics.ts +1 -1
  273. package/src/observability/telemetry-buffer.ts +12 -3
  274. package/src/observability/telemetry-reconciliation.ts +3 -3
  275. package/src/observability/telemetry-status.ts +1 -1
  276. package/src/observability/token-speed.ts +1 -1
  277. package/src/persistence/connection-url.ts +1 -1
  278. package/src/persistence/db-handle.ts +1 -1
  279. package/src/persistence/db-pg.ts +2 -2
  280. package/src/persistence/db-sqlite.ts +10 -5
  281. package/src/persistence/dialect.ts +5 -1
  282. package/src/persistence/readiness.ts +19 -7
  283. package/src/persistence/redis.ts +1 -1
  284. package/src/persistence/schema.ts +177 -45
  285. package/src/persistence/shared-cache.ts +2 -2
  286. package/src/persistence/sort-order.ts +1 -1
  287. package/src/persistence/stores/api-key-store.ts +109 -4
  288. package/src/persistence/stores/share-store.ts +20 -8
  289. package/src/persistence/stores/telemetry-store.ts +1 -1
  290. package/src/persistence/system-prompt-store.ts +63 -18
  291. package/src/persistence/tenant-preferences.ts +1 -1
  292. package/src/persistence/ttl-cache.ts +3 -3
  293. package/src/persistence/usage-flush.ts +7 -2
  294. package/src/protocol/canonical-model.ts +1 -1
  295. package/src/protocol/codecs.ts +12 -16
  296. package/src/protocol/dsml.ts +3 -49
  297. package/src/protocol/gateway-error.ts +6 -3
  298. package/src/protocol/image-clamp.ts +2 -2
  299. package/src/protocol/image-generation.ts +2 -2
  300. package/src/protocol/reasoning-text.ts +1 -1
  301. package/src/protocol/reasoning.ts +7 -52
  302. package/src/protocol/registry.ts +50 -15
  303. package/src/protocol/request/chat.ts +46 -19
  304. package/src/protocol/request/codex.ts +1 -1
  305. package/src/protocol/request/messages.ts +46 -14
  306. package/src/protocol/request/responses.ts +19 -10
  307. package/src/protocol/response/chat.ts +27 -12
  308. package/src/protocol/response/codex.ts +2 -2
  309. package/src/protocol/response/gemini.ts +1 -1
  310. package/src/protocol/response/messages.ts +6 -6
  311. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  312. package/src/protocol/response/responses.ts +3 -3
  313. package/src/protocol/sse-decoder.ts +64 -4
  314. package/src/protocol/stop-reason.ts +2 -2
  315. package/src/protocol/think-split.ts +347 -0
  316. package/src/protocol/translation/attachment-integrity.ts +3 -3
  317. package/src/protocol/translation/cache-controls.ts +8 -20
  318. package/src/protocol/translation/capabilities.ts +8 -8
  319. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  320. package/src/protocol/translation/dialect/inband.ts +39 -0
  321. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  322. package/src/protocol/translation/dialect/qwen.ts +114 -0
  323. package/src/protocol/translation/dialect/resolver.ts +83 -0
  324. package/src/protocol/translation/dialect/rules.ts +93 -0
  325. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  326. package/src/protocol/translation/dialect/types.ts +58 -0
  327. package/src/protocol/translation/quirks.ts +18 -4
  328. package/src/protocol/translation/thinking.ts +12 -252
  329. package/src/protocol/translation/tool-id.ts +3 -3
  330. package/src/protocol/translation/tool-repair.ts +1 -1
  331. package/src/protocol/upstream/post-json.ts +6 -1
  332. package/src/protocol/upstream/send-claude-messages.ts +3 -3
  333. package/src/providers/adapters/base-adapter.ts +1 -1
  334. package/src/providers/adapters/compatible-adapter.ts +44 -5
  335. package/src/providers/authentication/jwt-validator.ts +0 -5
  336. package/src/providers/authentication/oauth-client.ts +254 -253
  337. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  338. package/src/providers/authentication/oauth-refresh-service.ts +2 -2
  339. package/src/providers/bundled-discovery-providers.ts +6 -30
  340. package/src/providers/configured-openai-providers.ts +82 -97
  341. package/src/providers/configured-provider.ts +1 -1
  342. package/src/providers/default-registry.ts +23 -35
  343. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  344. package/src/providers/discovery/discovery-types.ts +0 -1
  345. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  346. package/src/providers/discovery/probe-phases.ts +50 -15
  347. package/src/providers/discovery/probing-service.ts +18 -20
  348. package/src/providers/integrations/agentrouter/agentrouter.ts +14 -5
  349. package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
  350. package/src/providers/integrations/anthropic.ts +4 -11
  351. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  352. package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
  353. package/src/providers/integrations/antigravity/antigravity.ts +15 -26
  354. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +2 -2
  355. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  356. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  357. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
  358. package/src/providers/integrations/buddy/buddy-identity.ts +108 -1
  359. package/src/providers/integrations/buddy/buddy-media.ts +16 -16
  360. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  361. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  362. package/src/providers/integrations/buddy/codebuddy/provider.ts +5 -16
  363. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  364. package/src/providers/integrations/buddy/workbuddy/provider.ts +6 -10
  365. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  366. package/src/providers/integrations/claude/claude-betas.ts +1 -1
  367. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  368. package/src/providers/integrations/claude/claude-credentials.ts +1 -1
  369. package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
  370. package/src/providers/integrations/claude/claude.ts +3 -10
  371. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  372. package/src/providers/integrations/cline/cline.ts +2 -2
  373. package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
  374. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  375. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  376. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  377. package/src/providers/integrations/codex/codex.ts +14 -15
  378. package/src/providers/integrations/commandcode/commandcode.ts +76 -116
  379. package/src/providers/integrations/commandcode/quota.ts +132 -48
  380. package/src/providers/integrations/cursor/cursor-http2.ts +11 -14
  381. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  382. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  383. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  384. package/src/providers/integrations/cursor/cursor.ts +53 -20
  385. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  386. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  387. package/src/providers/integrations/factory/factory.ts +13 -13
  388. package/src/providers/integrations/gemini.ts +11 -11
  389. package/src/providers/integrations/github/github-discovery.ts +55 -10
  390. package/src/providers/integrations/github/github-oauth.ts +6 -5
  391. package/src/providers/integrations/github/github.ts +47 -20
  392. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  393. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  394. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  395. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  396. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  397. package/src/providers/integrations/grok/grok.ts +88 -42
  398. package/src/providers/integrations/inferhub.ts +4 -11
  399. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  400. package/src/providers/integrations/kimi/kimi.ts +4 -11
  401. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  402. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  403. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  404. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  405. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  406. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  407. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  408. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  409. package/src/providers/integrations/kiro/kiro.ts +91 -78
  410. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  411. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  412. package/src/providers/integrations/muse/muse.ts +6 -9
  413. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  414. package/src/providers/integrations/opencode/provider.ts +17 -24
  415. package/src/providers/integrations/perplexity.ts +2 -2
  416. package/src/providers/integrations/qoder/qoder-catalog.ts +132 -17
  417. package/src/providers/integrations/qoder/qoder-checkin.ts +26 -8
  418. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +9 -8
  419. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  420. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  421. package/src/providers/integrations/qoder/qoder-realm.ts +22 -28
  422. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  423. package/src/providers/integrations/qoder/qoder-umid.ts +33 -38
  424. package/src/providers/integrations/qoder/qoder-virtual.ts +14 -2
  425. package/src/providers/integrations/qoder/qoder.ts +521 -63
  426. package/src/providers/integrations/shared/connect.ts +0 -54
  427. package/src/providers/integrations/shared/hash.ts +15 -0
  428. package/src/providers/integrations/shared/session-cache.ts +136 -0
  429. package/src/providers/integrations/tokenharbor.ts +2 -2
  430. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  431. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  432. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  433. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  434. package/src/providers/integrations/trae/trae.ts +59 -19
  435. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  436. package/src/providers/integrations/zcode/oauth.ts +1 -1
  437. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  438. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  439. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  440. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  441. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  442. package/src/providers/operations/account-error-classification.ts +131 -116
  443. package/src/providers/operations/account-health-service.ts +31 -39
  444. package/src/providers/operations/account-reset-service.ts +58 -1
  445. package/src/providers/operations/cli-platform.ts +31 -5
  446. package/src/providers/operations/client-version-resolver.ts +4 -6
  447. package/src/providers/operations/client-versions.ts +10 -7
  448. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  449. package/src/providers/operations/provider-catalog-service.ts +22 -6
  450. package/src/providers/operations/provider-credential-service.ts +35 -6
  451. package/src/providers/operations/upstream-deadline.ts +3 -3
  452. package/src/providers/provider-contracts.ts +65 -10
  453. package/src/providers/provider-credential-resolver.ts +1 -1
  454. package/src/providers/provider-metadata.ts +17 -2
  455. package/src/providers/provider-model-definition.ts +22 -1
  456. package/src/providers/provider-probe.ts +4 -2
  457. package/src/providers/provider-registry.ts +4 -4
  458. package/src/providers/provider-usage.ts +81 -16
  459. package/src/providers/quota/cache.ts +29 -0
  460. package/src/providers/quota/quota-contracts.ts +28 -10
  461. package/src/providers/quota/refresh.ts +29 -6
  462. package/src/providers/quota/snapshot.ts +94 -0
  463. package/src/providers/search/search-provider.ts +6 -8
  464. package/src/providers/search/search-providers.ts +2 -1
  465. package/src/security/abuse.ts +8 -8
  466. package/src/security/admission/reasons.ts +17 -17
  467. package/src/security/admission/service.ts +2 -2
  468. package/src/security/admission/ttl.ts +61 -61
  469. package/src/security/api-key-auth.ts +20 -8
  470. package/src/security/client-router-fingerprint.ts +4 -4
  471. package/src/security/credential-codec.ts +3 -3
  472. package/src/security/csrf.ts +2 -2
  473. package/src/security/ip-boundary.ts +16 -189
  474. package/src/security/model-access-rule.ts +1 -1
  475. package/src/security/outbound-headers.ts +18 -0
  476. package/src/shared/coerce.ts +19 -0
  477. package/src/shared/invoke-guard.ts +47 -0
  478. package/src/transport/contracts.ts +13 -4
  479. package/src/transport/dispatch/attempt-finalize.ts +5 -4
  480. package/src/transport/dispatch/attempt-loop.ts +10 -7
  481. package/src/transport/dispatch/capture-policy.ts +34 -37
  482. package/src/transport/dispatch/empty-stream.ts +28 -25
  483. package/src/transport/dispatch/generation.ts +33 -53
  484. package/src/transport/dispatch/leases.ts +8 -8
  485. package/src/transport/dispatch/native-shared.ts +127 -0
  486. package/src/transport/dispatch/proxy-request.ts +10 -8
  487. package/src/transport/dispatch/responses-compact.ts +35 -56
  488. package/src/transport/dispatch/retry-policy.ts +4 -67
  489. package/src/transport/dispatch/stream-bridge.ts +1 -1
  490. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  491. package/src/transport/dispatch/streaming/finalize.ts +11 -10
  492. package/src/transport/dispatch/streaming/stream.ts +7 -3
  493. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  494. package/src/transport/dispatch/systemone.ts +27 -55
  495. package/src/transport/dispatch/token-count.ts +49 -46
  496. package/src/transport/dispatch/websearch.ts +24 -50
  497. package/src/transport/drain-endpoint.ts +3 -3
  498. package/src/transport/failure-policy.ts +19 -6
  499. package/src/transport/guards/auth-guard.ts +6 -11
  500. package/src/transport/guards/body-policy.ts +7 -7
  501. package/src/transport/guards/error-lifecycle.ts +18 -3
  502. package/src/transport/guards/guard-errors.ts +7 -3
  503. package/src/transport/guards/headroom-guard.ts +2 -2
  504. package/src/transport/guards/pipeline.ts +7 -7
  505. package/src/transport/guards/readiness-guard.ts +16 -2
  506. package/src/transport/guards/request-context.ts +15 -20
  507. package/src/transport/request/preparer.ts +306 -27
  508. package/src/transport/request/rtk/compress-request.ts +3 -3
  509. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  510. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  511. package/src/transport/request/state.ts +5 -20
  512. package/src/transport/request/system-prompt-injection.ts +74 -11
  513. package/src/transport/request/tenant-preferences.ts +22 -0
  514. package/src/transport/routing/candidate-index.ts +2 -2
  515. package/src/transport/routing/round-robin.ts +1 -1
  516. package/src/transport/routing/route-catalog.ts +113 -19
  517. package/src/transport/routing/route-model.ts +131 -38
  518. package/src/transport/routing/router.ts +154 -39
  519. package/src/transport/shutdown-notice.ts +21 -9
  520. package/src/transport/surface/adapters.ts +3 -3
  521. package/src/transport/surface/chat/encode.ts +7 -7
  522. package/src/transport/surface/chat/parse.ts +7 -7
  523. package/src/transport/surface/completion.ts +3 -3
  524. package/src/transport/surface/content-parts.ts +1 -1
  525. package/src/transport/surface/dialects.ts +2 -2
  526. package/src/transport/surface/messages/encode.ts +1 -1
  527. package/src/transport/surface/messages/parse.ts +3 -3
  528. package/src/transport/surface/responses/adapter.ts +1 -1
  529. package/src/transport/surface/responses/errors.ts +0 -9
  530. package/src/transport/vision-handoff-cache.ts +58 -0
  531. package/src/transport/vision-handoff.ts +376 -0
  532. package/src/workers/account-identity-sweep.ts +2 -2
  533. package/src/workers/buddy-growth-tasks.ts +392 -346
  534. package/src/workers/daily-checkin.ts +102 -49
  535. package/src/workers/maintenance-lease.ts +2 -35
  536. package/src/workers/oauth-refresh-sweep.ts +8 -1
  537. package/src/workers/quota-refresh-sweep.ts +120 -82
  538. package/src/workers/scheduler.ts +67 -4
  539. package/src/workers/sweep-runner.ts +24 -5
  540. package/src/workers/zcode-gift-claim.ts +108 -0
  541. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  542. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  543. package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
  544. package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
  545. package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
  546. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js +0 -1
  547. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
  548. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js +0 -1
  549. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
  550. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +0 -1
  551. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
  552. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +0 -1
  553. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
  554. package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
  555. package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
  556. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +0 -1
  557. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
  558. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +0 -4
  559. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
  560. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  561. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  562. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +0 -1
  563. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
  564. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +0 -9
  565. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
  566. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js +0 -1
  567. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
  568. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js +0 -1
  569. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
  570. package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
  571. package/dist/dashboard/assets/SortableList-BAL5npBC.js +0 -1
  572. package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
  573. package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
  574. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js +0 -56
  575. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
  576. package/dist/dashboard/assets/api-keys-MbI7e7rt.js +0 -1
  577. package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
  578. package/dist/dashboard/assets/app-B2vyPoII.css +0 -2
  579. package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
  580. package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
  581. package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
  582. package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
  583. package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
  584. package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
  585. package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
  586. package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
  587. package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
  588. package/dist/dashboard/assets/entry-DS90gUks.js +0 -1
  589. package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
  590. package/dist/dashboard/assets/entry-j40LJotO.js +0 -4
  591. package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
  592. package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
  593. package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
  594. package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
  595. package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
  596. package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
  597. package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
  598. package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
  599. package/dist/dashboard/assets/live-KaG15HLQ.js +0 -1
  600. package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
  601. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
  602. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
  603. package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
  604. package/dist/dashboard/assets/numbers-BNDZnsUE.js +0 -2
  605. package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
  606. package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
  607. package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
  608. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  609. package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
  610. package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
  611. package/dist/dashboard/assets/settings-Dt3Eqbnq.js +0 -1
  612. package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
  613. package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
  614. package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
  615. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  616. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  617. package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
  618. package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
  619. package/src/network/response-headers.ts +0 -39
  620. package/src/protocol/response/dsml.ts +0 -38
  621. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  622. /package/dist/dashboard/assets/{app-wvwCi4Eh.js → app-Bgd3zANQ.js} +0 -0
  623. /package/dist/dashboard/assets/{app-wvwCi4Eh.js.gz → app-Bgd3zANQ.js.gz} +0 -0
  624. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  625. /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
@@ -18,8 +18,9 @@
18
18
  * - `isGatewayError()` semantics live in one shared SQL predicate.
19
19
  */
20
20
  import { and, desc, eq, gte, inArray, isNotNull, or, sql, type SQL } from "drizzle-orm";
21
+ import { PgDialect } from "drizzle-orm/pg-core";
21
22
  import type { CartethyiaDatabase } from "../persistence/db-handle";
22
- import { apiKeys, telemetryEvents } from "../persistence/schema";
23
+ import { apiKeys, telemetryEvents, telemetryUsageTotals } from "../persistence/schema";
23
24
  import type { TelemetryEventRecord } from "./event-log";
24
25
  import { gatewayErrorSql } from "./telemetry-status";
25
26
 
@@ -31,7 +32,21 @@ import { gatewayErrorSql } from "./telemetry-status";
31
32
  * totals, and the durable usage rollup cannot drift apart — an unknown status
32
33
  * must be classified identically in all three.
33
34
  */
34
- const IS_GATEWAY_ERROR_SQL = gatewayErrorSql(telemetryEvents.status, telemetryEvents.httpStatus);
35
+ export const IS_GATEWAY_ERROR_SQL = gatewayErrorSql(telemetryEvents.status, telemetryEvents.httpStatus);
36
+
37
+ /**
38
+ * The aggregate select fragments shared by every tenant-window read. The
39
+ * `errors` count uses the single shared gateway-error predicate (see
40
+ * {@link IS_GATEWAY_ERROR_SQL}) so the row-level rule and the SQL aggregate
41
+ * cannot drift; the sum fragments coalesce to 0 so an empty window reads as
42
+ * zero rather than null.
43
+ */
44
+ const AGGREGATE_SELECT = {
45
+ errors: sql<number>`sum(case when ${IS_GATEWAY_ERROR_SQL} then 1 else 0 end)`,
46
+ inputTokens: sql<number>`coalesce(sum(${telemetryEvents.inputTokens}), 0)`,
47
+ outputTokens: sql<number>`coalesce(sum(${telemetryEvents.outputTokens}), 0)`,
48
+ cachedInputTokens: sql<number>`coalesce(sum(${telemetryEvents.cachedInputTokens}), 0)`,
49
+ } as const;
35
50
 
36
51
  /** One bounded page of events, newest first. */
37
52
  export interface EventPage {
@@ -40,7 +55,7 @@ export interface EventPage {
40
55
  readonly nextCursor?: string;
41
56
  }
42
57
 
43
- export interface EventListQuery {
58
+ interface EventListQuery {
44
59
  readonly tenantId: string;
45
60
  /**
46
61
  * Lower bound. Omitting it means "the whole retained window" — the SQL table
@@ -96,7 +111,7 @@ export interface UsageSummary {
96
111
  readonly unpriced: number;
97
112
  }
98
113
 
99
- export interface BreakdownGroup {
114
+ interface BreakdownGroup {
100
115
  readonly key: string;
101
116
  readonly requests: number;
102
117
  readonly errors: number;
@@ -116,15 +131,8 @@ export interface ChartBucket {
116
131
  readonly errors: number;
117
132
  readonly inputTokens: number;
118
133
  readonly outputTokens: number;
119
- }
120
-
121
- export interface HealthTotals {
122
- readonly requests: number;
123
- readonly errors: number;
124
- readonly inputTokens: number;
125
- readonly outputTokens: number;
134
+ /** Cached-input share of the prompt, summed for the bucket. */
126
135
  readonly cachedInputTokens: number;
127
- readonly latencyAvgMs: number | null;
128
136
  }
129
137
 
130
138
  const DEFAULT_LIMIT = 50;
@@ -141,7 +149,7 @@ function numericNullable(value: unknown): number | null {
141
149
  }
142
150
 
143
151
  /** Maps one `telemetry_events` row to the console's record shape. */
144
- export function eventRecordFromRow(row: {
152
+ function eventRecordFromRow(row: {
145
153
  readonly id: string | null;
146
154
  readonly createdAt: Date;
147
155
  readonly tenantId: string;
@@ -225,11 +233,28 @@ export function eventRecordFromRow(row: {
225
233
  * renders the SQL, so it is the only answer that cannot disagree with it.
226
234
  */
227
235
  function isPostgres(db: CartethyiaDatabase): boolean {
228
- const dialect = (db as unknown as { dialect?: { constructor?: { name?: string } } }).dialect;
229
- const name = dialect?.constructor?.name;
236
+ const dialect = (db as unknown as { dialect?: unknown }).dialect;
237
+ if (dialect instanceof PgDialect) return true;
238
+ const name = (dialect as { constructor?: { name?: string } } | undefined)?.constructor?.name;
230
239
  return name === "PostgresDialect" || name === "PgDialect";
231
240
  }
232
241
 
242
+ /**
243
+ * The epoch-bucket SQL expression for one time column.
244
+ *
245
+ * Inlined (not parameterised) so Postgres sees the SELECT and GROUP BY
246
+ * expressions as identical: parameter numbers differing between the two
247
+ * clauses made Postgres treat them as unrelated expressions referencing
248
+ * `created_at`, which 42803-rejected the query. The width is a raw SQL
249
+ * literal for the same reason.
250
+ */
251
+ function bucketExpression(db: CartethyiaDatabase, column: typeof telemetryEvents.createdAt, widthSeconds: number): SQL {
252
+ const w = sql.raw(String(Math.max(1, Math.trunc(widthSeconds))));
253
+ return isPostgres(db)
254
+ ? sql<number>`(cast(extract(epoch from ${column}) as bigint) / ${w}) * ${w}`
255
+ : sql<number>`(cast(strftime('%s', ${column}) as integer) / ${w}) * ${w}`;
256
+ }
257
+
233
258
  /**
234
259
  * The tenant predicate — the scope every read shares — plus the window when
235
260
  * one is given.
@@ -400,90 +425,9 @@ export async function queryTelemetryEvents(
400
425
  };
401
426
  }
402
427
 
403
- /** One event by request id — a single indexed row, never a window scan. */
404
- export async function eventByRequestId(
405
- db: CartethyiaDatabase,
406
- tenantId: string,
407
- requestId: string,
408
- ): Promise<TelemetryEventRecord | undefined> {
409
- const [row] = await db
410
- .select()
411
- .from(telemetryEvents)
412
- .where(and(eq(telemetryEvents.tenantId, tenantId), eq(telemetryEvents.requestId, requestId)))
413
- .limit(1);
414
- if (!row) return undefined;
415
- return eventRecordFromRow(row as Parameters<typeof eventRecordFromRow>[0]);
416
- }
417
-
418
428
  /**
419
- * Totals plus latency percentiles for one tenant window.
420
- *
421
- * Percentiles are computed by the database. Postgres uses `percentile_cont`;
422
- * SQLite (no ordered-set aggregates) computes nearest-rank percentiles from
423
- * the ordered latency column via OFFSET/LIMIT. Either way, latency values are
424
- * never returned to JavaScript — the array that the old raw reader built to
425
- * sort in JS was the memory cost this replaces.
429
+ * The SQL column one breakdown dimension groups by.
426
430
  */
427
- export async function summarizeTelemetry(
428
- db: CartethyiaDatabase,
429
- tenantId: string,
430
- since: Date,
431
- ): Promise<UsageSummary> {
432
- const condition = scoped(tenantId, since);
433
- const [row] = await db
434
- .select({
435
- requests: sql<number>`count(*)`,
436
- errors: sql<number>`sum(case when ${IS_GATEWAY_ERROR_SQL} then 1 else 0 end)`,
437
- cancelled: sql<number>`sum(case when ${telemetryEvents.status} = 'cancelled' then 1 else 0 end)`,
438
- truncated: sql<number>`sum(case when ${telemetryEvents.status} = 'truncated' then 1 else 0 end)`,
439
- inputTokens: sql<number>`coalesce(sum(${telemetryEvents.inputTokens}), 0)`,
440
- outputTokens: sql<number>`coalesce(sum(${telemetryEvents.outputTokens}), 0)`,
441
- cachedInputTokens: sql<number>`coalesce(sum(${telemetryEvents.cachedInputTokens}), 0)`,
442
- reasoningTokens: sql<number>`coalesce(sum(${telemetryEvents.reasoningTokens}), 0)`,
443
- estimatedCostUsd: sql<number>`coalesce(sum(${telemetryEvents.estimatedCostUsd}), 0)`,
444
- sampled: sql<number>`count(${telemetryEvents.latencyMs})`,
445
- latencyAvg: sql<number>`avg(${telemetryEvents.latencyMs})`,
446
- unpriced: sql<number>`sum(case when ${telemetryEvents.estimatedCostUsd} is null then 1 else 0 end)`,
447
- })
448
- .from(telemetryEvents)
449
- .where(condition);
450
- if (!row) {
451
- return {
452
- requests: 0,
453
- errors: 0,
454
- cancelled: 0,
455
- truncated: 0,
456
- inputTokens: 0,
457
- outputTokens: 0,
458
- cachedInputTokens: 0,
459
- reasoningTokens: 0,
460
- estimatedCostUsd: 0,
461
- sampled: 0,
462
- latencyMs: { avg: null, p50: null, p95: null, p99: null },
463
- unpriced: 0,
464
- };
465
- }
466
-
467
- const sampled = numeric(row.sampled);
468
- const percentiles = await latencyPercentiles(db, tenantId, since, sampled);
469
- return {
470
- requests: numeric(row.requests),
471
- errors: numeric(row.errors),
472
- cancelled: numeric(row.cancelled),
473
- truncated: numeric(row.truncated),
474
- inputTokens: numeric(row.inputTokens),
475
- outputTokens: numeric(row.outputTokens),
476
- cachedInputTokens: numeric(row.cachedInputTokens),
477
- reasoningTokens: numeric(row.reasoningTokens),
478
- estimatedCostUsd: numeric(row.estimatedCostUsd),
479
- sampled,
480
- latencyMs: {
481
- avg: numericNullable(row.latencyAvg),
482
- ...percentiles,
483
- },
484
- unpriced: numeric(row.unpriced),
485
- };
486
- }
487
431
 
488
432
  type Percentiles = { p50: number | null; p95: number | null; p99: number | null };
489
433
 
@@ -516,23 +460,36 @@ async function latencyPercentiles(
516
460
  }
517
461
 
518
462
  // SQLite: nearest-rank percentile = the value at ceil(p * n) rows into the
519
- // ordered latency column, via ORDER BY + OFFSET/LIMIT in SQL.
520
- const rankAt = async (fraction: number): Promise<number | null> => {
521
- const offset = Math.max(0, Math.ceil(fraction * sampled) - 1);
522
- const [row] = await db
523
- .select({ value: telemetryEvents.latencyMs })
524
- .from(telemetryEvents)
525
- .where(and(...base))
526
- .orderBy(telemetryEvents.latencyMs)
527
- .limit(1)
528
- .offset(offset);
529
- return row?.value ?? null;
463
+ // ordered latency column. One query computes all three ranks via a window
464
+ // function, instead of three separate OFFSET/LIMIT passes over the same
465
+ // ordered column (one per percentile).
466
+ const n = Math.max(1, sampled);
467
+ const ranks = [
468
+ Math.ceil(0.5 * n),
469
+ Math.ceil(0.95 * n),
470
+ Math.ceil(0.99 * n),
471
+ ];
472
+ const [row] = await db
473
+ .select({
474
+ p50: sql<number | null>`max(case when rn = ${ranks[0]} then value end)`,
475
+ p95: sql<number | null>`max(case when rn = ${ranks[1]} then value end)`,
476
+ p99: sql<number | null>`max(case when rn = ${ranks[2]} then value end)`,
477
+ })
478
+ .from(
479
+ sql`(
480
+ select ${telemetryEvents.latencyMs} as value,
481
+ row_number() over (order by ${telemetryEvents.latencyMs}) as rn
482
+ from ${telemetryEvents}
483
+ where ${and(...base)}
484
+ )`,
485
+ );
486
+ return {
487
+ p50: numericNullable(row?.p50 ?? null),
488
+ p95: numericNullable(row?.p95 ?? null),
489
+ p99: numericNullable(row?.p99 ?? null),
530
490
  };
531
- const [p50, p95, p99] = await Promise.all([rankAt(0.5), rankAt(0.95), rankAt(0.99)]);
532
- return { p50, p95, p99 };
533
491
  }
534
492
 
535
- /** The SQL column one breakdown dimension groups by. */
536
493
  function dimensionColumn(dimension: string) {
537
494
  if (dimension === "model") return telemetryEvents.requestedModel;
538
495
  if (dimension === "provider") return telemetryEvents.providerId;
@@ -571,9 +528,7 @@ export async function breakdownTelemetry(
571
528
  .select({
572
529
  key: column,
573
530
  requests: sql<number>`count(*)`,
574
- errors: sql<number>`sum(case when ${IS_GATEWAY_ERROR_SQL} then 1 else 0 end)`,
575
- inputTokens: sql<number>`coalesce(sum(${telemetryEvents.inputTokens}), 0)`,
576
- outputTokens: sql<number>`coalesce(sum(${telemetryEvents.outputTokens}), 0)`,
531
+ ...AGGREGATE_SELECT,
577
532
  cachedInputTokens: sql<number>`coalesce(sum(${telemetryEvents.cachedInputTokens}), 0)`,
578
533
  costUsd: sql<number>`coalesce(sum(${telemetryEvents.estimatedCostUsd}), 0)`,
579
534
  avgTokensPerSec: sql<number>`coalesce(avg(${telemetryEvents.tokensPerSec}), 0)`,
@@ -610,32 +565,23 @@ export async function chartTelemetry(
610
565
  since: Date,
611
566
  bucketSeconds: number,
612
567
  ): Promise<ChartBucket[]> {
613
- const width = Math.max(1, Math.trunc(bucketSeconds));
614
- // Inlined (not parameterised) so Postgres sees the SELECT and GROUP BY
615
- // expressions as identical: parameter numbers differing between the two
616
- // clauses made Postgres treat them as unrelated expressions referencing
617
- // `created_at`, which 42803-rejected the query.
618
- const w = sql.raw(String(width));
619
- const bucketExpression = isPostgres(db)
620
- ? sql<number>`(cast(extract(epoch from ${telemetryEvents.createdAt}) as bigint) / ${w}) * ${w}`
621
- : sql<number>`(cast(strftime('%s', ${telemetryEvents.createdAt}) as integer) / ${w}) * ${w}`;
568
+ const bucketExpr = bucketExpression(db, telemetryEvents.createdAt, bucketSeconds);
622
569
  const rows = await db
623
570
  .select({
624
- at: bucketExpression,
571
+ at: bucketExpr,
625
572
  requests: sql<number>`count(*)`,
626
- errors: sql<number>`sum(case when ${IS_GATEWAY_ERROR_SQL} then 1 else 0 end)`,
627
- inputTokens: sql<number>`coalesce(sum(${telemetryEvents.inputTokens}), 0)`,
628
- outputTokens: sql<number>`coalesce(sum(${telemetryEvents.outputTokens}), 0)`,
573
+ ...AGGREGATE_SELECT,
629
574
  })
630
575
  .from(telemetryEvents)
631
576
  .where(scoped(tenantId, since))
632
- .groupBy(bucketExpression);
577
+ .groupBy(bucketExpr);
633
578
  return rows.map((row) => ({
634
579
  at: numeric(row.at) * 1000,
635
580
  requests: numeric(row.requests),
636
581
  errors: numeric(row.errors),
637
582
  inputTokens: numeric(row.inputTokens),
638
583
  outputTokens: numeric(row.outputTokens),
584
+ cachedInputTokens: numeric(row.cachedInputTokens),
639
585
  }));
640
586
  }
641
587
 
@@ -665,20 +611,15 @@ export async function providerUsageMatrix(
665
611
  providerId: string,
666
612
  since: Date,
667
613
  ): Promise<ProviderUsageDayRow[]> {
668
- // Same inlining rule as chartTelemetry: SELECT and GROUP BY must spell the
669
- // identical expression or Postgres 42803-rejects the grouped reference.
670
- const w = sql.raw(String(86400));
671
- const dayExpression = isPostgres(db)
672
- ? sql<number>`(cast(extract(epoch from ${telemetryEvents.createdAt}) as bigint) / ${w}) * ${w}`
673
- : sql<number>`(cast(strftime('%s', ${telemetryEvents.createdAt}) as integer) / ${w}) * ${w}`;
614
+ const dayExpression = bucketExpression(db, telemetryEvents.createdAt, 86400);
674
615
  const rows = await db
675
616
  .select({
676
617
  day: dayExpression,
677
618
  model: telemetryEvents.requestedModel,
678
619
  requests: sql<number>`count(*)`,
679
- inputTokens: sql<number>`coalesce(sum(${telemetryEvents.inputTokens}), 0)`,
620
+ inputTokens: AGGREGATE_SELECT.inputTokens,
680
621
  cachedInputTokens: sql<number>`coalesce(sum(${telemetryEvents.cachedInputTokens}), 0)`,
681
- outputTokens: sql<number>`coalesce(sum(${telemetryEvents.outputTokens}), 0)`,
622
+ outputTokens: AGGREGATE_SELECT.outputTokens,
682
623
  costUsd: sql<number>`coalesce(sum(${telemetryEvents.estimatedCostUsd}), 0)`,
683
624
  })
684
625
  .from(telemetryEvents)
@@ -696,43 +637,6 @@ export async function providerUsageMatrix(
696
637
  }));
697
638
  }
698
639
 
699
- /** Overview/health totals for one tenant window. */
700
- export async function healthTelemetry(
701
- db: CartethyiaDatabase,
702
- tenantId: string,
703
- since: Date,
704
- ): Promise<HealthTotals> {
705
- const [row] = await db
706
- .select({
707
- requests: sql<number>`count(*)`,
708
- errors: sql<number>`sum(case when ${IS_GATEWAY_ERROR_SQL} then 1 else 0 end)`,
709
- inputTokens: sql<number>`coalesce(sum(${telemetryEvents.inputTokens}), 0)`,
710
- outputTokens: sql<number>`coalesce(sum(${telemetryEvents.outputTokens}), 0)`,
711
- cachedInputTokens: sql<number>`coalesce(sum(${telemetryEvents.cachedInputTokens}), 0)`,
712
- latencyAvg: sql<number>`avg(${telemetryEvents.latencyMs})`,
713
- })
714
- .from(telemetryEvents)
715
- .where(scoped(tenantId, since));
716
- if (!row) {
717
- return {
718
- requests: 0,
719
- errors: 0,
720
- inputTokens: 0,
721
- outputTokens: 0,
722
- cachedInputTokens: 0,
723
- latencyAvgMs: null,
724
- };
725
- }
726
- return {
727
- requests: numeric(row.requests),
728
- errors: numeric(row.errors),
729
- inputTokens: numeric(row.inputTokens),
730
- outputTokens: numeric(row.outputTokens),
731
- cachedInputTokens: numeric(row.cachedInputTokens),
732
- latencyAvgMs: numericNullable(row.latencyAvg),
733
- };
734
- }
735
-
736
640
  /**
737
641
  * Summed input+output tokens for a set of API keys over one window — the
738
642
  * family bucket spend query.
@@ -768,54 +672,157 @@ export async function sumTelemetryTokens(
768
672
  }
769
673
 
770
674
  /**
771
- * The health card's latency and throughput figures for one window.
675
+ * All-time family counts from the durable rollup, not from a scan of the
676
+ * retained telemetry window.
772
677
  *
773
- * Same principle as {@link summarizeTelemetry}: `avg(tokens_per_sec)` and the
774
- * latency percentiles are computed by the database. The console's health card
775
- * used to pull every retained event into an array to average `tokensPerSec`
776
- * and sort latencies in JavaScript.
678
+ * The share KPI cards need lifetime numbers. `telemetry_usage_totals` is
679
+ * charged at write time and accumulates per identity, so a family's lifetime
680
+ * requests/errors/tokens are a SUM over its rows (keyed by
681
+ * `family_template_key_id`) — sub-millisecond on a small table, versus a
682
+ * full-window aggregate over `telemetry_events` that reads every retained
683
+ * event on every tick. The rollup is the same definition as the read-side
684
+ * counts, so the two cannot drift.
777
685
  */
778
- export interface HealthPerformance {
779
- readonly latencyAvgMs: number | null;
780
- readonly latencyP50Ms: number | null;
781
- readonly latencyP95Ms: number | null;
782
- readonly latencyP99Ms: number | null;
783
- readonly avgTokensPerSec: number;
686
+ interface FamilyKpiTotals {
687
+ readonly requests: number;
688
+ readonly errors: number;
689
+ readonly tokens: number;
784
690
  }
785
691
 
786
- export async function summarizeHealthPerformance(
692
+ /** Sums one share family's durable lifetime totals. */
693
+ export async function familyKpiTotals(
787
694
  db: CartethyiaDatabase,
788
695
  tenantId: string,
789
- since: Date,
790
- ): Promise<HealthPerformance> {
696
+ familyKeyId: string,
697
+ ): Promise<FamilyKpiTotals> {
791
698
  const [row] = await db
792
699
  .select({
793
- latencyAvg: sql<number>`avg(${telemetryEvents.latencyMs})`,
794
- sampled: sql<number>`count(${telemetryEvents.latencyMs})`,
795
- avgTokensPerSec: sql<number>`coalesce(avg(${telemetryEvents.tokensPerSec}), 0)`,
700
+ requests: sql<number>`coalesce(sum(${telemetryUsageTotals.requests}), 0)`,
701
+ errors: sql<number>`coalesce(sum(${telemetryUsageTotals.errors}), 0)`,
702
+ tokens: sql<number>`coalesce(sum(coalesce(${telemetryUsageTotals.inputTokens}, 0) + coalesce(${telemetryUsageTotals.outputTokens}, 0)), 0)`,
796
703
  })
797
- .from(telemetryEvents)
798
- .where(scoped(tenantId, since));
799
- const sampled = numeric(row?.sampled ?? 0);
800
- const percentiles = await latencyPercentiles(db, tenantId, since, sampled);
704
+ .from(telemetryUsageTotals)
705
+ .where(
706
+ and(
707
+ eq(telemetryUsageTotals.tenantId, tenantId),
708
+ eq(telemetryUsageTotals.familyTemplateKeyId, familyKeyId),
709
+ ),
710
+ );
801
711
  return {
802
- latencyAvgMs: numericNullable(row?.latencyAvg ?? null),
803
- latencyP50Ms: percentiles.p50,
804
- latencyP95Ms: percentiles.p95,
805
- latencyP99Ms: percentiles.p99,
806
- avgTokensPerSec: numeric(row?.avgTokensPerSec ?? 0),
712
+ requests: numeric(row?.requests ?? 0),
713
+ errors: numeric(row?.errors ?? 0),
714
+ tokens: numeric(row?.tokens ?? 0),
807
715
  };
808
716
  }
809
717
 
718
+ /** Counts one share family's requests in a bounded window — a range scan, not a full-window aggregate. */
719
+ export async function familyWindowRequests(
720
+ db: CartethyiaDatabase,
721
+ tenantId: string,
722
+ familyKeyId: string,
723
+ since: Date,
724
+ ): Promise<number> {
725
+ const [row] = await db
726
+ .select({
727
+ requests: sql<number>`count(*)`,
728
+ })
729
+ .from(telemetryEvents)
730
+ .where(
731
+ and(
732
+ eq(telemetryEvents.tenantId, tenantId),
733
+ familyKeyCondition(familyKeyId),
734
+ gte(telemetryEvents.createdAt, since),
735
+ ),
736
+ );
737
+ return numeric(row?.requests ?? 0);
738
+ }
739
+
740
+ /**
741
+ * One share family's client-IP breakdown, computed in SQL.
742
+ *
743
+ * Unlike the 24-hour page read, this covers a bounded history so an address
744
+ * that stopped before the last day still appears — marked inactive. The
745
+ * `active` flag is decided in the caller from `lastSeenAt` vs a cutoff; here
746
+ * we only aggregate. The window is bounded (`since`, default the whole
747
+ * retained window) so an unbounded scan is never the default for callers
748
+ * that only need recent addresses.
749
+ */
750
+ export async function familyIpBreakdown(
751
+ db: CartethyiaDatabase,
752
+ tenantId: string,
753
+ familyKeyId: string,
754
+ limit = 50,
755
+ since?: Date,
756
+ ): Promise<{ ip: string; requests: number; tokens: number; lastSeenAt: Date | null }[]> {
757
+ const conditions = [
758
+ eq(telemetryEvents.tenantId, tenantId),
759
+ familyKeyCondition(familyKeyId),
760
+ ...(since === undefined ? [] : [gte(telemetryEvents.createdAt, since)]),
761
+ ];
762
+ const rows = await db
763
+ .select({
764
+ ip: telemetryEvents.clientIp,
765
+ requests: sql<number>`count(*)`,
766
+ tokens: sql<number>`coalesce(sum(coalesce(${telemetryEvents.inputTokens}, 0) + coalesce(${telemetryEvents.outputTokens}, 0)), 0)`,
767
+ lastSeenAt: sql<Date>`max(${telemetryEvents.createdAt})`,
768
+ })
769
+ .from(telemetryEvents)
770
+ .where(and(...conditions))
771
+ .groupBy(telemetryEvents.clientIp)
772
+ // Highest volume first: the table answers "who has used the link most",
773
+ // not just who is active right now.
774
+ .orderBy(desc(sql`count(*)`))
775
+ .limit(limit);
776
+ return rows.flatMap((row) =>
777
+ row.ip === null ? [] : [{ ip: row.ip, requests: numeric(row.requests), tokens: numeric(row.tokens), lastSeenAt: row.lastSeenAt }],
778
+ );
779
+ }
780
+
781
+ /**
782
+ * One share family's time-series buckets, computed in SQL with the family
783
+ * membership filter.
784
+ *
785
+ * Mirrors {@link chartTelemetry} but scoped to a share family. The share page's
786
+ * 24-hour strip must not be truncated to the bounded page (500 events), or a
787
+ * busy family's chart undercounts every hour bucket.
788
+ */
789
+ export async function familyHourlyTelemetry(
790
+ db: CartethyiaDatabase,
791
+ tenantId: string,
792
+ familyKeyId: string,
793
+ since: Date,
794
+ bucketSeconds: number,
795
+ ): Promise<ChartBucket[]> {
796
+ const bucketExpr = bucketExpression(db, telemetryEvents.createdAt, bucketSeconds);
797
+ const conditions = [scoped(tenantId, since), familyKeyCondition(familyKeyId)];
798
+ const rows = await db
799
+ .select({
800
+ at: bucketExpr,
801
+ requests: sql<number>`count(*)`,
802
+ ...AGGREGATE_SELECT,
803
+ })
804
+ .from(telemetryEvents)
805
+ .where(and(...conditions))
806
+ .groupBy(bucketExpr);
807
+ return rows.map((row) => ({
808
+ at: numeric(row.at) * 1000,
809
+ requests: numeric(row.requests),
810
+ errors: numeric(row.errors),
811
+ inputTokens: numeric(row.inputTokens),
812
+ outputTokens: numeric(row.outputTokens),
813
+ cachedInputTokens: numeric(row.cachedInputTokens),
814
+ }));
815
+ }
816
+
810
817
  /**
811
818
  * The Usage summary's performance block, computed in SQL.
812
819
  *
813
820
  * Replaces `summarizeUsagePerformance` (which sorted a latency array in
814
821
  * JavaScript): `avg(resolve_ms)` and `avg(tokens_per_sec)` are SQL averages,
815
822
  * and the latency percentiles come from the same database-side percentile
816
- * path {@link summarizeTelemetry} uses.
823
+ * path {@link summarizeUsageWithPerformance} uses.
817
824
  */
818
- export interface UsagePerformanceTotals {
825
+ interface UsagePerformanceTotals {
819
826
  readonly avgResolveMs: number;
820
827
  readonly avgDurationMs: number;
821
828
  /**
@@ -832,32 +839,97 @@ export interface UsagePerformanceTotals {
832
839
  readonly avgTokensPerSec: number;
833
840
  }
834
841
 
835
- export async function summarizeUsagePerformanceSql(
842
+ /**
843
+ * Combined usage totals + performance in ONE window scan.
844
+ *
845
+ * This runs the combined SELECT once and one percentile pass, returning both
846
+ * shapes so the caller needs no second query.
847
+ */
848
+ export async function summarizeUsageWithPerformance(
836
849
  db: CartethyiaDatabase,
837
850
  tenantId: string,
838
851
  since: Date,
839
- ): Promise<UsagePerformanceTotals> {
852
+ ): Promise<{ summary: UsageSummary; performance: UsagePerformanceTotals }> {
853
+ const condition = scoped(tenantId, since);
840
854
  const [row] = await db
841
855
  .select({
856
+ requests: sql<number>`count(*)`,
857
+ errors: AGGREGATE_SELECT.errors,
858
+ cancelled: sql<number>`sum(case when ${telemetryEvents.status} = 'cancelled' then 1 else 0 end)`,
859
+ truncated: sql<number>`sum(case when ${telemetryEvents.status} = 'truncated' then 1 else 0 end)`,
860
+ inputTokens: AGGREGATE_SELECT.inputTokens,
861
+ outputTokens: AGGREGATE_SELECT.outputTokens,
862
+ cachedInputTokens: sql<number>`coalesce(sum(${telemetryEvents.cachedInputTokens}), 0)`,
863
+ reasoningTokens: sql<number>`coalesce(sum(${telemetryEvents.reasoningTokens}), 0)`,
864
+ estimatedCostUsd: sql<number>`coalesce(sum(${telemetryEvents.estimatedCostUsd}), 0)`,
842
865
  sampled: sql<number>`count(${telemetryEvents.latencyMs})`,
843
866
  latencyAvg: sql<number>`avg(${telemetryEvents.latencyMs})`,
867
+ unpriced: sql<number>`sum(case when ${telemetryEvents.estimatedCostUsd} is null then 1 else 0 end)`,
844
868
  resolveSum: sql<number>`coalesce(sum(${telemetryEvents.resolveMs}), 0)`,
845
869
  resolveCount: sql<number>`count(${telemetryEvents.resolveMs})`,
846
870
  avgTokensPerSec: sql<number>`coalesce(avg(${telemetryEvents.tokensPerSec}), 0)`,
847
871
  })
848
872
  .from(telemetryEvents)
849
- .where(scoped(tenantId, since));
850
- const sampled = numeric(row?.sampled ?? 0);
851
- const resolveCount = numeric(row?.resolveCount ?? 0);
873
+ .where(condition);
874
+
875
+ if (!row) {
876
+ const empty: UsageSummary = {
877
+ requests: 0,
878
+ errors: 0,
879
+ cancelled: 0,
880
+ truncated: 0,
881
+ inputTokens: 0,
882
+ outputTokens: 0,
883
+ cachedInputTokens: 0,
884
+ reasoningTokens: 0,
885
+ estimatedCostUsd: 0,
886
+ sampled: 0,
887
+ latencyMs: { avg: null, p50: null, p95: null, p99: null },
888
+ unpriced: 0,
889
+ };
890
+ const emptyPerformance: UsagePerformanceTotals = {
891
+ avgResolveMs: 0,
892
+ avgDurationMs: 0,
893
+ latencyP50Ms: 0,
894
+ latencyP95Ms: 0,
895
+ latencyP99Ms: 0,
896
+ latencySampleCount: 0,
897
+ resolveSampleCount: 0,
898
+ avgTokensPerSec: 0,
899
+ };
900
+ return { summary: empty, performance: emptyPerformance };
901
+ }
902
+
903
+ const sampled = numeric(row.sampled);
852
904
  const percentiles = await latencyPercentiles(db, tenantId, since, sampled);
905
+ const resolveCount = numeric(row.resolveCount);
853
906
  return {
854
- avgResolveMs: resolveCount > 0 ? numeric(row?.resolveSum ?? 0) / resolveCount : 0,
855
- avgDurationMs: numericNullable(row?.latencyAvg ?? null) ?? 0,
856
- latencyP50Ms: percentiles.p50 ?? 0,
857
- latencyP95Ms: percentiles.p95 ?? 0,
858
- latencyP99Ms: percentiles.p99 ?? 0,
859
- latencySampleCount: sampled,
860
- resolveSampleCount: resolveCount,
861
- avgTokensPerSec: numeric(row?.avgTokensPerSec ?? 0),
907
+ summary: {
908
+ requests: numeric(row.requests),
909
+ errors: numeric(row.errors),
910
+ cancelled: numeric(row.cancelled),
911
+ truncated: numeric(row.truncated),
912
+ inputTokens: numeric(row.inputTokens),
913
+ outputTokens: numeric(row.outputTokens),
914
+ cachedInputTokens: numeric(row.cachedInputTokens),
915
+ reasoningTokens: numeric(row.reasoningTokens),
916
+ estimatedCostUsd: numeric(row.estimatedCostUsd),
917
+ sampled,
918
+ latencyMs: {
919
+ avg: numericNullable(row.latencyAvg),
920
+ ...percentiles,
921
+ },
922
+ unpriced: numeric(row.unpriced),
923
+ },
924
+ performance: {
925
+ avgResolveMs: resolveCount > 0 ? numeric(row.resolveSum) / resolveCount : 0,
926
+ avgDurationMs: numericNullable(row.latencyAvg) ?? 0,
927
+ latencyP50Ms: percentiles.p50 ?? 0,
928
+ latencyP95Ms: percentiles.p95 ?? 0,
929
+ latencyP99Ms: percentiles.p99 ?? 0,
930
+ latencySampleCount: sampled,
931
+ resolveSampleCount: resolveCount,
932
+ avgTokensPerSec: numeric(row.avgTokensPerSec),
933
+ },
862
934
  };
863
935
  }
@@ -36,7 +36,7 @@ export const LOG_RING_MAX_CODE_UNITS = 8_192;
36
36
 
37
37
  /** Marker appended wherever content was dropped. */
38
38
  export const LOG_TRUNCATED_MARKER = "[truncated]";
39
- export const LOG_CIRCULAR_MARKER = "[circular]";
39
+ const LOG_CIRCULAR_MARKER = "[circular]";
40
40
 
41
41
  function isHighSurrogate(code: number): boolean {
42
42
  return code >= 0xd800 && code <= 0xdbff;