cartethyia 2.5.3 → 2.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/.env.example +13 -4
  2. package/README.md +2 -2
  3. package/dist/dashboard/assets/{BannedPage-hfCzi-Ux.js → BannedPage-CADDq4IU.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-DEDyKBlC.js → CliToolsPage-DuO1Z7dq.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-bsLuZmYH.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-_UWhwziJ.js → CustomizationPage-BR5HHvbi.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-DDVIfZGW.js → LoginPage-V5faV9V2.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
  19. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
  20. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
  25. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js +1 -0
  26. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
  27. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
  28. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-D6pmQ86L.js → SetupPage-UB690SE2.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-DWvKiuSl.js → SortableList-BLujdX7N.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-CmkUrgdj.js → StudioPage-D5C2raAc.js} +3 -3
  38. package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
  39. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
  40. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
  41. package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
  42. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  43. package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
  44. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  45. package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-C9FD_JwJ.js} +1 -1
  46. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  47. package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-CR72dasT.js} +1 -1
  48. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  49. package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-CI9_eSfO.js} +1 -1
  50. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  51. package/dist/dashboard/assets/{card--AtFedAq.js → card-DS7CQEsO.js} +1 -1
  52. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  53. package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-CNqzrfdb.js} +1 -1
  54. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  55. package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-611pSsaX.js} +1 -1
  56. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  57. package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
  58. package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
  59. package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
  60. package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
  61. package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
  62. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  63. package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
  64. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-B9Xvtnfj.js +1 -0
  66. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
  68. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
  70. package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
  71. package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-sltk1qUi.js} +1 -1
  72. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  73. package/dist/dashboard/assets/{index-BydyKaDz.js → index-CmxZr14F.js} +2 -2
  74. package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
  75. package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-BhY2sSnO.js} +1 -1
  76. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  77. package/dist/dashboard/assets/{input-BG41et9G.js → input-V1umFiBk.js} +1 -1
  78. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  79. package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
  80. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  81. package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-1zzZYM0B.js} +1 -1
  82. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  83. package/dist/dashboard/assets/{live-BJGYRBnR.js → live-BivGfuUs.js} +1 -1
  84. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  85. package/dist/dashboard/assets/model-bans-CmffjLuG.js +1 -0
  86. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  87. package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-46P0oWMe.js} +1 -1
  88. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  89. package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
  90. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  91. package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-BFhGpwR1.js} +1 -1
  92. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  93. package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-CAb4F37R.js} +1 -1
  94. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  95. package/dist/dashboard/assets/{select-BO6h8yNv.js → select-BYR_5K2c.js} +2 -2
  96. package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
  97. package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-DNX9sydi.js} +1 -1
  98. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  99. package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-BHTfXdz_.js} +1 -1
  100. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  101. package/dist/dashboard/assets/{state-DYdQmdAl.js → state-mPDRlQkl.js} +1 -1
  102. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  103. package/dist/dashboard/assets/switch-DOmJxg7H.js +1 -0
  104. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  105. package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
  106. package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
  107. package/dist/dashboard/assets/tokens-COzario9.js +1 -0
  108. package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
  109. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
  110. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
  111. package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-Dem0lXQj.js} +1 -1
  112. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  113. package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
  114. package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
  115. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  116. package/dist/dashboard/index.html +1 -1
  117. package/dist/dashboard/index.html.gz +0 -0
  118. package/dist/dashboard/precompressed-assets.json +57 -57
  119. package/migrations/README.md +52 -52
  120. package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
  121. package/package.json +96 -95
  122. package/scripts/cli/admin.ts +125 -0
  123. package/scripts/cli/autostart.ts +237 -0
  124. package/scripts/cli/{ui/background.ts → background.ts} +1 -1
  125. package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -248
  126. package/scripts/cli/doctor.ts +37 -0
  127. package/scripts/cli/{support/env.ts → env.ts} +99 -99
  128. package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
  129. package/scripts/cli/instance.ts +224 -0
  130. package/scripts/cli/main.ts +88 -18
  131. package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
  132. package/scripts/cli/port.ts +100 -0
  133. package/scripts/cli/{support/services.ts → services.ts} +88 -88
  134. package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
  135. package/scripts/cli/{command/start.ts → start.ts} +10 -5
  136. package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
  137. package/scripts/cli/tray/index.ts +62 -12
  138. package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
  139. package/scripts/cli/update.ts +40 -0
  140. package/scripts/cli/version.ts +67 -0
  141. package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
  142. package/src/boot/app.ts +11 -7
  143. package/src/boot/dependencies.ts +50 -19
  144. package/src/boot/timeout.ts +2 -13
  145. package/src/config/cooldowns.ts +186 -138
  146. package/src/config/network.ts +8 -14
  147. package/src/config/providers.ts +7 -10
  148. package/src/config/runtime.ts +76 -76
  149. package/src/config/security.ts +15 -0
  150. package/src/config/spec.ts +52 -7
  151. package/src/config/telemetry.ts +26 -0
  152. package/src/config/timeouts.ts +0 -6
  153. package/src/console/admin/model-abuse/contracts.ts +28 -6
  154. package/src/console/admin/model-abuse/routes.ts +56 -3
  155. package/src/console/api-keys/bucket-spend.ts +15 -4
  156. package/src/console/api-keys/contracts.ts +410 -414
  157. package/src/console/api-keys/routes.ts +881 -880
  158. package/src/console/auth/credentials.ts +3 -3
  159. package/src/console/auth/setup-service.ts +2 -2
  160. package/src/console/backup/contracts.ts +15 -2
  161. package/src/console/backup/export-endpoint.ts +6 -2
  162. package/src/console/backup/nine-router.ts +2 -5
  163. package/src/console/backup/service.ts +4 -0
  164. package/src/console/backup/store.ts +192 -29
  165. package/src/console/backup/validate.ts +3 -45
  166. package/src/console/cli-tools/fs-ops.ts +487 -487
  167. package/src/console/console-router-guards.ts +1 -1
  168. package/src/console/console-router.ts +8 -0
  169. package/src/console/data-management/contracts.ts +100 -0
  170. package/src/console/data-management/operations.ts +234 -0
  171. package/src/console/data-management/routes.ts +103 -0
  172. package/src/console/domain-registration.ts +45 -1
  173. package/src/console/observability/contracts.ts +524 -485
  174. package/src/console/observability/live.ts +22 -0
  175. package/src/console/observability/store.ts +1138 -1018
  176. package/src/console/observability/usage-dimensions.ts +4 -4
  177. package/src/console/providers/catalog/catalog-projections.ts +6 -7
  178. package/src/console/providers/catalog/contracts.ts +6 -0
  179. package/src/console/providers/catalog/model-operations.ts +5 -2
  180. package/src/console/providers/catalog/provider-operations.ts +24 -4
  181. package/src/console/providers/catalog/public-model-store.ts +8 -11
  182. package/src/console/providers/catalog/routes.ts +7 -0
  183. package/src/console/providers/catalog/store.ts +1553 -1532
  184. package/src/console/providers/oauth/routes.ts +8 -12
  185. package/src/console/quota/account-quota-shared.ts +52 -39
  186. package/src/console/quota/account-quota-tenant.ts +6 -6
  187. package/src/console/quota/quota-view.ts +1 -1
  188. package/src/console/quota/refresh.ts +441 -429
  189. package/src/console/remote-support/contracts.ts +61 -0
  190. package/src/console/remote-support/routes.ts +65 -0
  191. package/src/console/remote-support/service.ts +207 -0
  192. package/src/console/response-compression.ts +0 -3
  193. package/src/console/routing/pools/import-parse.ts +206 -0
  194. package/src/console/routing/pools/operations.ts +543 -501
  195. package/src/console/routing/pools/probe-result.ts +3 -2
  196. package/src/console/routing/pools/probe-verdict.ts +132 -132
  197. package/src/console/routing/pools/routes.ts +181 -164
  198. package/src/console/routing/pools/store.ts +8 -8
  199. package/src/console/settings/contracts.ts +95 -2
  200. package/src/console/settings/store.ts +81 -3
  201. package/src/console/shared/cursor.ts +8 -8
  202. package/src/console/shared/errors.ts +1 -3
  203. package/src/console/updates/routes.ts +16 -0
  204. package/src/main.ts +10 -0
  205. package/src/network/http2-bidi.ts +2 -4
  206. package/src/network/pool/agent.ts +478 -478
  207. package/src/network/pool/selector.ts +5 -4
  208. package/src/network/retry.ts +1 -2
  209. package/src/network/ssrf.ts +5 -4
  210. package/src/observability/event-compaction.ts +317 -0
  211. package/src/observability/event-day-index.ts +440 -0
  212. package/src/observability/event-log.ts +155 -6
  213. package/src/observability/logger.ts +6 -6
  214. package/src/observability/payload-capture.ts +158 -33
  215. package/src/observability/payload-store.ts +82 -61
  216. package/src/observability/redaction.ts +5 -6
  217. package/src/observability/request-notify.ts +43 -0
  218. package/src/observability/telemetry-status.ts +8 -9
  219. package/src/observability/token-speed.ts +5 -4
  220. package/src/persistence/db-mode.ts +7 -7
  221. package/src/persistence/health-event-flush.ts +36 -0
  222. package/src/persistence/readiness.ts +1 -1
  223. package/src/persistence/schema.ts +75 -29
  224. package/src/persistence/shared-cache.ts +1 -2
  225. package/src/persistence/stores/api-key-store.ts +18 -6
  226. package/src/persistence/stores/share-store.ts +4 -3
  227. package/src/persistence/stores/telemetry-store.ts +33 -11
  228. package/src/protocol/canonical-model.ts +11 -3
  229. package/src/protocol/codecs.ts +139 -6
  230. package/src/protocol/gateway-error.ts +6 -4
  231. package/src/protocol/image-clamp.ts +19 -1
  232. package/src/protocol/registry.ts +8 -0
  233. package/src/protocol/request/chat.ts +21 -1
  234. package/src/protocol/request/codex.ts +10 -1
  235. package/src/protocol/request/gemini.ts +9 -2
  236. package/src/protocol/request/messages.ts +18 -7
  237. package/src/protocol/request/responses.ts +9 -1
  238. package/src/protocol/response/chat.ts +34 -4
  239. package/src/protocol/response/codex.ts +7 -4
  240. package/src/protocol/response/dsml.ts +140 -28
  241. package/src/protocol/response/responses.ts +11 -5
  242. package/src/protocol/stream-error-frames.ts +4 -5
  243. package/src/providers/adapters/compatible-adapter.ts +3 -0
  244. package/src/providers/authentication/jwt-validator.ts +9 -10
  245. package/src/providers/authentication/oauth-refresh-service.ts +9 -11
  246. package/src/providers/bundled-discovery-providers.ts +122 -119
  247. package/src/providers/configured-openai-providers.ts +4 -4
  248. package/src/providers/default-registry.ts +5 -4
  249. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  250. package/src/providers/discovery/catalog/catalog-format.ts +3 -6
  251. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  252. package/src/providers/discovery/openai-model-discovery.ts +5 -0
  253. package/src/providers/discovery/openrouter-free-roster.ts +15 -17
  254. package/src/providers/discovery/probe-phases.ts +4 -4
  255. package/src/providers/discovery/probe-wire.ts +6 -13
  256. package/src/providers/discovery/probing-service.ts +1117 -1045
  257. package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
  258. package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
  259. package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
  260. package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
  261. package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
  262. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
  263. package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
  264. package/src/providers/integrations/buddy/buddy-media.ts +2 -2
  265. package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
  266. package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
  267. package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
  268. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
  269. package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
  270. package/src/providers/integrations/claude/claude.ts +608 -608
  271. package/src/providers/integrations/cline/cline-quota.ts +27 -20
  272. package/src/providers/integrations/cline/cline.ts +537 -539
  273. package/src/providers/integrations/codex/codex-headers.ts +25 -39
  274. package/src/providers/integrations/codex/codex-oauth.ts +1 -1
  275. package/src/providers/integrations/codex/codex.ts +2 -29
  276. package/src/providers/integrations/commandcode/commandcode.ts +674 -675
  277. package/src/providers/integrations/commandcode/discovery.ts +117 -116
  278. package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
  279. package/src/providers/integrations/cursor/cursor-quota.ts +309 -0
  280. package/src/providers/integrations/cursor/cursor-request.ts +616 -471
  281. package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
  282. package/src/providers/integrations/cursor/cursor.ts +1333 -960
  283. package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
  284. package/src/providers/integrations/factory/factory-models.ts +3 -5
  285. package/src/providers/integrations/factory/factory.ts +8 -1
  286. package/src/providers/integrations/gemini.ts +6 -1
  287. package/src/providers/integrations/grok/grok-install-id.ts +2 -3
  288. package/src/providers/integrations/grok/grok-quota.ts +279 -177
  289. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
  290. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
  291. package/src/providers/integrations/grok/grok.ts +6 -7
  292. package/src/providers/integrations/hermes.ts +14 -18
  293. package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
  294. package/src/providers/integrations/kiro/kiro.ts +1 -1
  295. package/src/providers/integrations/mistral.ts +6 -6
  296. package/src/providers/integrations/opencode/fingerprint.ts +3 -3
  297. package/src/providers/integrations/opencode/provider.ts +19 -20
  298. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
  299. package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
  300. package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
  301. package/src/providers/integrations/qoder/qoder.ts +884 -865
  302. package/src/providers/integrations/shared/connect.ts +6 -6
  303. package/src/providers/integrations/trae/trae-catalog.ts +1 -2
  304. package/src/providers/integrations/trae/trae-quota.ts +384 -0
  305. package/src/providers/integrations/trae/trae.ts +2 -2
  306. package/src/providers/integrations/zcode/oauth.ts +6 -1
  307. package/src/providers/operations/account-health-service.ts +75 -27
  308. package/src/providers/operations/account-reset-service.ts +1 -1
  309. package/src/providers/operations/client-versions.ts +6 -6
  310. package/src/providers/operations/custom-cli-headers.ts +3 -4
  311. package/src/providers/operations/provider-catalog-seeder.ts +95 -95
  312. package/src/providers/operations/provider-catalog-service.ts +3 -4
  313. package/src/providers/provider-contracts.ts +7 -0
  314. package/src/providers/provider-metadata.ts +10 -8
  315. package/src/providers/provider-registry.ts +6 -7
  316. package/src/providers/provider-usage.ts +1 -1
  317. package/src/providers/quota/quota-support.ts +8 -0
  318. package/src/security/abuse.ts +15 -10
  319. package/src/security/access-control.ts +144 -146
  320. package/src/security/admission/contracts.ts +11 -0
  321. package/src/security/admission/in-memory-store.ts +4 -3
  322. package/src/security/admission/lease-sweep.ts +6 -6
  323. package/src/security/admission/reasons.ts +5 -1
  324. package/src/security/admission/redis-store.ts +211 -210
  325. package/src/security/admission/service.ts +4 -0
  326. package/src/security/api-key-auth.ts +27 -17
  327. package/src/security/ip-boundary.ts +155 -15
  328. package/src/security/model-abuse.ts +366 -94
  329. package/src/security/outbound-headers.ts +3 -4
  330. package/src/shared/sleep.ts +4 -0
  331. package/src/transport/dispatch/attempt-finalize.ts +47 -5
  332. package/src/transport/dispatch/attempt-loop.ts +531 -530
  333. package/src/transport/dispatch/generation.ts +6 -0
  334. package/src/transport/dispatch/proxy-request.ts +24 -1
  335. package/src/transport/dispatch/ratelimit-capture.ts +37 -0
  336. package/src/transport/dispatch/stream-bridge.ts +1 -3
  337. package/src/transport/dispatch/streaming/finalize.ts +1 -1
  338. package/src/transport/dispatch/streaming/stream.ts +19 -6
  339. package/src/transport/dispatch/upstream.ts +9 -0
  340. package/src/transport/failure-policy.ts +162 -8
  341. package/src/transport/guards/auth-guard.ts +7 -4
  342. package/src/transport/guards/concurrency-guard.ts +0 -5
  343. package/src/transport/guards/error-lifecycle.ts +13 -6
  344. package/src/transport/guards/pipeline.ts +15 -3
  345. package/src/transport/guards/request-context.ts +8 -2
  346. package/src/transport/request/inflight.ts +5 -6
  347. package/src/transport/request/preparer.ts +9 -8
  348. package/src/transport/request/state.ts +21 -8
  349. package/src/transport/request/tenant-preferences.ts +60 -0
  350. package/src/transport/routing/admission.ts +4 -0
  351. package/src/transport/routing/route-catalog.ts +65 -10
  352. package/src/transport/routing/route-model.ts +46 -14
  353. package/src/transport/routing/router.ts +21 -14
  354. package/src/transport/surface/completion.ts +2 -3
  355. package/src/transport/surface/dialects.ts +5 -6
  356. package/src/transport/surface/responses/encode.ts +38 -0
  357. package/src/transport/surface/responses/parse.ts +4 -0
  358. package/src/transport/translation/capabilities.ts +2 -3
  359. package/src/transport/translation/quirks.ts +3 -3
  360. package/src/workers/buddy-growth-tasks.ts +5 -10
  361. package/src/workers/daily-checkin.ts +17 -23
  362. package/src/workers/quota-refresh-sweep.ts +26 -26
  363. package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
  364. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +0 -1
  365. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
  366. package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
  367. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +0 -1
  368. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
  369. package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
  370. package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
  371. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +0 -1
  372. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
  373. package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
  374. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +0 -1
  375. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
  376. package/dist/dashboard/assets/OverviewPage-B53YYMng.js +0 -1
  377. package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
  378. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +0 -3
  379. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
  380. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +0 -1
  381. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
  382. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +0 -7
  383. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
  384. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +0 -1
  385. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
  386. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +0 -1
  387. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
  388. package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
  389. package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
  390. package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
  391. package/dist/dashboard/assets/UsagePage-B47uOydr.js +0 -56
  392. package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
  393. package/dist/dashboard/assets/api-keys-uNywYlfv.js +0 -1
  394. package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
  395. package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
  396. package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
  397. package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
  398. package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
  399. package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
  400. package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
  401. package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
  402. package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
  403. package/dist/dashboard/assets/dialog-BtpsJU98.js +0 -1
  404. package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
  405. package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
  406. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  407. package/dist/dashboard/assets/drawer-WwwtzihF.js +0 -1
  408. package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
  409. package/dist/dashboard/assets/entry-CkryrIea.js +0 -4
  410. package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
  411. package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
  412. package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
  413. package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
  414. package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
  415. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  416. package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
  417. package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
  418. package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
  419. package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
  420. package/dist/dashboard/assets/layout-C8mer_Ak.js +0 -1
  421. package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
  422. package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
  423. package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
  424. package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
  425. package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
  426. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  427. package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
  428. package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
  429. package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
  430. package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
  431. package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
  432. package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
  433. package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
  434. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  435. package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
  436. package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
  437. package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
  438. package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
  439. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
  440. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  441. package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
  442. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  443. package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
  444. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  445. package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
  446. package/scripts/cli/command/admin.ts +0 -65
  447. package/scripts/cli/command/doctor.ts +0 -257
  448. package/scripts/cli/command/update.ts +0 -23
  449. package/scripts/cli/gateway/drain-token.ts +0 -78
  450. package/scripts/cli/gateway/instance-lock.ts +0 -105
  451. package/scripts/cli/gateway/stop.ts +0 -51
  452. package/scripts/cli/support/bun.ts +0 -27
  453. package/scripts/cli/support/version.ts +0 -37
  454. /package/dist/dashboard/assets/{app-CafWtVXA.js → app-BIt446yE.js} +0 -0
  455. /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-BIt446yE.js.gz} +0 -0
  456. /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Provider-specific knobs config.ts formerly owned: Kimi OAuth/quota hosts,
3
- * the GitHub Enterprise domain for the Copilot device login, and the OAuth
4
- * callback URL builders built on the public origin.
2
+ * Provider-specific knobs: Kimi OAuth/quota hosts, the GitHub Enterprise domain
3
+ * for the Copilot device login, and the OAuth callback URL builders built on the
4
+ * public origin.
5
5
  */
6
6
 
7
- import { CONFIG_SPEC, readInt, readList, readRequired, readText } from "./spec";
7
+ import { CONFIG_SPEC, readInt, readRequired, readText } from "./spec";
8
8
 
9
9
  /** Reads and normalizes the public OAuth origin. */
10
10
  export function requirePublicOrigin(): string {
@@ -64,14 +64,11 @@ export function resolveOpenCodeUnavailableFree():
64
64
  const raw = process.env.CARTETHYIA_OPENCODE_UNAVAILABLE_FREE?.trim();
65
65
  if (raw === undefined || raw === "") return undefined;
66
66
  if (raw.toLowerCase() === "none") return "none";
67
- const parsed = readList(
68
- "CARTETHYIA_OPENCODE_UNAVAILABLE_FREE",
69
- CONFIG_SPEC.CARTETHYIA_OPENCODE_UNAVAILABLE_FREE,
70
- );
71
- // Separators only: readList drops the empties and yields nothing. Treating
67
+ // Separators only: dropping empties and yielding nothing. Treating
72
68
  // that as "trust everything" would un-block every refused model on a typo,
73
69
  // so it falls back to the bundled list instead.
74
- return parsed === undefined || parsed.length === 0 ? undefined : parsed;
70
+ const entries = raw.split(",").map((v) => v.trim()).filter(Boolean);
71
+ return entries.length === 0 ? undefined : entries;
75
72
  }
76
73
 
77
74
  /**
@@ -1,76 +1,76 @@
1
- /**
2
- * HTTP listener, dashboard runtime, and graceful-shutdown drain resolvers.
3
- */
4
-
5
- import { CONFIG_SPEC, readInt, readOptionalText, readText } from "./spec";
6
- import { resolvePackagePath } from "./package-paths";
7
-
8
- /** Resolves the HTTP listener port from the process environment. */
9
- export function resolvePort(): number {
10
- return readInt("PORT", CONFIG_SPEC.PORT);
11
- }
12
-
13
- /** Resolves the dashboard static asset directory. */
14
- export function resolveDashboardDist(): string {
15
- if (!process.env.DASHBOARD_DIST?.trim()) return resolvePackagePath("dist", "dashboard");
16
- return readText("DASHBOARD_DIST", CONFIG_SPEC.DASHBOARD_DIST);
17
- }
18
-
19
- /**
20
- * Resolves the configured default gateway API key (`CARTETHYIA_API_KEY`), if
21
- * one is set. Consumers that need the raw secret — first-boot seeding and the
22
- * console's legacy-key recovery — read it here instead of probing
23
- * `process.env` directly.
24
- */
25
- export function resolveDefaultGatewayApiKey(): string | undefined {
26
- return readOptionalText("CARTETHYIA_API_KEY", CONFIG_SPEC.CARTETHYIA_API_KEY);
27
- }
28
-
29
- /** Resolves the request-body ceiling shared by the Bun listener and ingress middleware. */
30
- export function resolveMaxBodyBytes(): number {
31
- return readInt("CARTETHYIA_SERVER_MAX_BODY_BYTES", CONFIG_SPEC.CARTETHYIA_SERVER_MAX_BODY_BYTES);
32
- }
33
-
34
- /** Resolves the listener idle-socket timeout (seconds). */
35
- export function resolveIdleTimeout(): number {
36
- return readInt("CARTETHYIA_SERVER_IDLE_TIMEOUT", CONFIG_SPEC.CARTETHYIA_SERVER_IDLE_TIMEOUT);
37
- }
38
-
39
- /** Resolves the graceful-shutdown drain window: time for in-flight requests to finish before abort. */
40
- export function resolveShutdownDrainWindowMs(): number {
41
- return readInt(
42
- "CARTETHYIA_SHUTDOWN_DRAIN_WINDOW_MS",
43
- CONFIG_SPEC.CARTETHYIA_SHUTDOWN_DRAIN_WINDOW_MS,
44
- );
45
- }
46
-
47
- /** Resolves the operator drain token, or `undefined` when the drain route is disabled. */
48
- export function resolveDrainToken(): string | undefined {
49
- const raw = process.env.CARTETHYIA_DRAIN_TOKEN?.trim();
50
- return raw !== undefined && raw.length > 0 ? raw : undefined;
51
- }
52
-
53
- /**
54
- * Whether Elysia precompiles route handlers at startup (AOT).
55
- *
56
- * Precompilation takes the eager validator branch, which calls TypeBox's
57
- * `SchemaCompile(schema)` and then reads `.buildResult.external` off the
58
- * result. In a source-run process that field is absent — the lazy
59
- * `require("typebox/schema")` resolves to a namespace that does not carry it —
60
- * and the process dies at the first schema-bearing route with
61
- * `undefined is not an object (evaluating 'this.tb.buildResult.external')`.
62
- * `scripts/binary/aot.ts` avoids this by rewriting the call site and bundling
63
- * the namespaces statically, so only the built artifact can actually take the
64
- * eager path.
65
- *
66
- * So: precompilation is on for production *builds*, and off for a source run
67
- * even when `NODE_ENV=production`. A deployment that runs
68
- * `bun run src/main.ts` must not be pushed onto the eager branch.
69
- *
70
- * `CARTETHYIA_SOURCE_RUN` marks a source run explicitly. It is set by
71
- * `scripts/deploy/*.sh`, which are the only supported way to run from source.
72
- */
73
- export function resolveElysiaPrecompile(): boolean {
74
- if (process.env.CARTETHYIA_SOURCE_RUN === "1") return false;
75
- return process.env.NODE_ENV === "production";
76
- }
1
+ /**
2
+ * HTTP listener, dashboard runtime, and graceful-shutdown drain resolvers.
3
+ */
4
+
5
+ import { CONFIG_SPEC, readInt, readOptionalText } from "./spec";
6
+ import { resolvePackagePath } from "./package-paths";
7
+
8
+ /** Resolves the HTTP listener port from the process environment. */
9
+ export function resolvePort(): number {
10
+ return readInt("PORT", CONFIG_SPEC.PORT);
11
+ }
12
+
13
+ /** Resolves the dashboard static asset directory. */
14
+ export function resolveDashboardDist(): string {
15
+ const raw = process.env.DASHBOARD_DIST?.trim();
16
+ if (!raw) return resolvePackagePath("dist", "dashboard");
17
+ return raw;
18
+ }
19
+
20
+ /**
21
+ * Resolves the configured default gateway API key (`CARTETHYIA_API_KEY`), if
22
+ * one is set. Consumers that need the raw secret — first-boot seeding and the
23
+ * console's key recovery from existing installations — read it here instead of
24
+ * probing `process.env` directly.
25
+ */
26
+ export function resolveDefaultGatewayApiKey(): string | undefined {
27
+ return readOptionalText("CARTETHYIA_API_KEY", CONFIG_SPEC.CARTETHYIA_API_KEY);
28
+ }
29
+
30
+ /** Resolves the request-body ceiling shared by the Bun listener and ingress middleware. */
31
+ export function resolveMaxBodyBytes(): number {
32
+ return readInt("CARTETHYIA_SERVER_MAX_BODY_BYTES", CONFIG_SPEC.CARTETHYIA_SERVER_MAX_BODY_BYTES);
33
+ }
34
+
35
+ /** Resolves the listener idle-socket timeout (seconds). */
36
+ export function resolveIdleTimeout(): number {
37
+ return readInt("CARTETHYIA_SERVER_IDLE_TIMEOUT", CONFIG_SPEC.CARTETHYIA_SERVER_IDLE_TIMEOUT);
38
+ }
39
+
40
+ /** Resolves the graceful-shutdown drain window: time for in-flight requests to finish before abort. */
41
+ export function resolveShutdownDrainWindowMs(): number {
42
+ return readInt(
43
+ "CARTETHYIA_SHUTDOWN_DRAIN_WINDOW_MS",
44
+ CONFIG_SPEC.CARTETHYIA_SHUTDOWN_DRAIN_WINDOW_MS,
45
+ );
46
+ }
47
+
48
+ /** Resolves the operator drain token, or `undefined` when the drain route is disabled. */
49
+ export function resolveDrainToken(): string | undefined {
50
+ return readOptionalText("CARTETHYIA_DRAIN_TOKEN", CONFIG_SPEC.CARTETHYIA_DRAIN_TOKEN);
51
+ }
52
+
53
+ /**
54
+ * Whether Elysia precompiles route handlers at startup (AOT).
55
+ *
56
+ * Precompilation takes the eager validator branch, which calls TypeBox's
57
+ * `SchemaCompile(schema)` and then reads `.buildResult.external` off the
58
+ * result. In a source-run process that field is absent — the lazy
59
+ * `require("typebox/schema")` resolves to a namespace that does not carry it —
60
+ * and the process dies at the first schema-bearing route with
61
+ * `undefined is not an object (evaluating 'this.tb.buildResult.external')`.
62
+ * `scripts/build/aot.ts` avoids this by rewriting the call site and bundling
63
+ * the namespaces statically, so only the built artifact can actually take the
64
+ * eager path.
65
+ *
66
+ * So: precompilation is on for production *builds*, and off for a source run
67
+ * even when `NODE_ENV=production`. A deployment that runs
68
+ * `bun run src/main.ts` must not be pushed onto the eager branch.
69
+ *
70
+ * `CARTETHYIA_SOURCE_RUN` marks a source run explicitly. It is set by
71
+ * `scripts/deploy/*.sh`, which are the only supported way to run from source.
72
+ */
73
+ export function resolveElysiaPrecompile(): boolean {
74
+ if (process.env.CARTETHYIA_SOURCE_RUN === "1") return false;
75
+ return process.env.NODE_ENV === "production";
76
+ }
@@ -37,6 +37,21 @@ export function resolveModelBanTtlMs(): number {
37
37
  return readInt("CARTETHYIA_MODEL_BAN_TTL_MS", CONFIG_SPEC.CARTETHYIA_MODEL_BAN_TTL_MS);
38
38
  }
39
39
 
40
+ /**
41
+ * The longest model-abuse ban the console will record, however long a request
42
+ * asks for.
43
+ *
44
+ * A separate knob from the ban TTL because the two are different decisions:
45
+ * the TTL is what a graduated ban gets, this is the ceiling on what an
46
+ * operator may ask for by hand. Raising one was never meant to raise the other.
47
+ */
48
+ export function resolveModelBanMaxTtlMs(): number {
49
+ return readInt(
50
+ "CARTETHYIA_MODEL_BAN_MAX_TTL_MS",
51
+ CONFIG_SPEC.CARTETHYIA_MODEL_BAN_MAX_TTL_MS,
52
+ );
53
+ }
54
+
40
55
  /** Reads the application encryption key without caching it. */
41
56
  export function requireEncryptionKeyEnv(): string {
42
57
  return readRequired("CARTETHYIA_ENCRYPTION_KEY", CONFIG_SPEC.CARTETHYIA_ENCRYPTION_KEY);
@@ -148,7 +148,40 @@ export const CONFIG_SPEC = {
148
148
  */
149
149
  CARTETHYIA_DISABLED_TASKS: { kind: "list" },
150
150
 
151
+ /**
152
+ * How long a day's telemetry is kept, in days.
153
+ *
154
+ * The retention unit is the whole day: a day's event file and its index are
155
+ * deleted together, and the current day is never touched. Past this age an
156
+ * individual request no longer exists anywhere, so its detail is gone —
157
+ * totals, breakdowns and spend are unaffected, because the index keeps
158
+ * running sums rather than the rows.
159
+ *
160
+ * This is the knob that decides how long the request list, per-request
161
+ * filters, and the detail drawer stay complete.
162
+ */
151
163
  CARTETHYIA_TELEMETRY_RETENTION_DAYS: { kind: "int", default: 30, min: 3, max: 365 },
164
+ /**
165
+ * Age at which a request's detail is described as no longer stored.
166
+ *
167
+ * Retention itself is `CARTETHYIA_TELEMETRY_RETENTION_DAYS`; this only words
168
+ * the operator-facing message shown when a request that old is asked for.
169
+ */
170
+ CARTETHYIA_TELEMETRY_COMPACT_AFTER_DAYS: { kind: "int", default: 7, min: 1, max: 365 },
171
+ /**
172
+ * How often each day's index is brought up to date, in minutes.
173
+ *
174
+ * Bringing an index up to date means folding the bytes appended since the
175
+ * last pass — not re-reading the day. Reads fold before they answer too, so
176
+ * raising this does not make the numbers stale; it only leaves the on-disk
177
+ * index further behind, and the next read pays for the remainder.
178
+ */
179
+ CARTETHYIA_TELEMETRY_COMPACT_INTERVAL_MINUTES: {
180
+ kind: "int",
181
+ default: 60,
182
+ min: 5,
183
+ max: 1440,
184
+ },
152
185
  CARTETHYIA_TELEMETRY_PAYLOAD_MAX_BYTES: {
153
186
  kind: "int",
154
187
  default: 16 * 1024 * 1024,
@@ -191,10 +224,10 @@ export const CONFIG_SPEC = {
191
224
  /**
192
225
  * An unmapped upstream failure cools the account down for 15 minutes.
193
226
  *
194
- * It used to be 60s, which put the account straight back into rotation and
195
- * re-dialled an upstream that had just answered something this gateway does
196
- * not understand — so the same unknown error was retried every minute, and
197
- * an account that was actually broken contributed a steady stream of failed
227
+ * A shorter window puts the account straight back into rotation and re-dials
228
+ * an upstream that has just answered something this gateway does not
229
+ * understand — so the same unknown error is retried every minute, and an
230
+ * account that is actually broken contributes a steady stream of failed
198
231
  * attempts instead of one. Fifteen minutes is long enough for a transient
199
232
  * provider condition to clear on its own and short enough that no operator
200
233
  * action is needed to recover it. It is still a cooldown with a deadline,
@@ -224,6 +257,18 @@ export const CONFIG_SPEC = {
224
257
  CARTETHYIA_MODEL_STRIKE_THRESHOLD: { kind: "int", default: 10, min: 1, max: 100 },
225
258
  CARTETHYIA_MODEL_STRIKE_WINDOW_MS: { kind: "int", default: 1_800_000, min: 1_000, max: 86_400_000 },
226
259
  CARTETHYIA_MODEL_BAN_TTL_MS: { kind: "int", default: 21_600_000, min: 1_000, max: 604_800_000 },
260
+ /**
261
+ * The longest ban the console will record, however long a request asks for.
262
+ * The "Permanent" choice is a very long TTL rather than an unbounded one,
263
+ * and this is what keeps a typo in a duration from parking a caller for a
264
+ * decade.
265
+ */
266
+ CARTETHYIA_MODEL_BAN_MAX_TTL_MS: {
267
+ kind: "int",
268
+ default: 604_800_000,
269
+ min: 1_000,
270
+ max: 604_800_000,
271
+ },
227
272
  CARTETHYIA_ENCRYPTION_KEY: {
228
273
  kind: "required",
229
274
  error:
@@ -240,9 +285,9 @@ export const CONFIG_SPEC = {
240
285
  /**
241
286
  * Loopback port the browser-OAuth callback listener binds.
242
287
  *
243
- * Configurable because the default is a fixed number and a busy port used to
244
- * strand the operator: the login could not start and there was no way to move
245
- * it. Codex's authorization server still expects 59653, so changing this
288
+ * Configurable because the default is a fixed number: a busy port would strand
289
+ * the operator, since the login could not start and there would be no way to
290
+ * move it. Codex's authorization server still expects 59653, so changing this
246
291
  * only helps the providers whose redirect URI is not allowlisted upstream.
247
292
  */
248
293
  CARTETHYIA_OAUTH_CALLBACK_PORT: { kind: "int", default: 59_653, min: 1_024, max: 65_535 },
@@ -19,3 +19,29 @@ export function resolveTelemetryPayloadMaxBytes(): number {
19
19
  CONFIG_SPEC.CARTETHYIA_TELEMETRY_PAYLOAD_MAX_BYTES,
20
20
  );
21
21
  }
22
+
23
+ /**
24
+ * Age at which hourly telemetry files are compacted into a per-day summary.
25
+ *
26
+ * Clamped below the retention window: compacting at or past the delete age
27
+ * would fold a file and throw it away in the same pass, spending the read for
28
+ * totals that were about to be discarded.
29
+ */
30
+ export function resolveTelemetryCompactAfterDays(): number {
31
+ const compacted = readInt(
32
+ "CARTETHYIA_TELEMETRY_COMPACT_AFTER_DAYS",
33
+ CONFIG_SPEC.CARTETHYIA_TELEMETRY_COMPACT_AFTER_DAYS,
34
+ );
35
+ return Math.min(compacted, resolveTelemetryRetentionDays());
36
+ }
37
+
38
+ /** Minutes between compaction passes. */
39
+ export function resolveTelemetryCompactIntervalMs(): number {
40
+ const minutes = readInt(
41
+ "CARTETHYIA_TELEMETRY_COMPACT_INTERVAL_MINUTES",
42
+ CONFIG_SPEC.CARTETHYIA_TELEMETRY_COMPACT_INTERVAL_MINUTES,
43
+ );
44
+ return minutes * 60_000;
45
+ }
46
+
47
+
@@ -51,12 +51,6 @@ let cachedUpstreamTimeoutMs: number | undefined;
51
51
  let cachedStreamStallTimeoutMs: number | undefined;
52
52
  let cachedInflightTtlSeconds: number | undefined;
53
53
 
54
- /** Clears the memoized timeout cache; tests that mutate env mid-process only. */
55
- export function resetTimeoutCacheForTests(): void {
56
- cachedUpstreamTimeoutMs = undefined;
57
- cachedStreamStallTimeoutMs = undefined;
58
- cachedInflightTtlSeconds = undefined;
59
- }
60
54
 
61
55
  /**
62
56
  * Crash-recovery TTL, in seconds, for a distributed inflight slot.
@@ -1,16 +1,38 @@
1
- // Console surface for model-abuse bans: list active bans and lift one.
1
+ // Console surface for model-abuse bans: list active bans, ban one, lift one.
2
2
  //
3
- // The strike layer (security/model-abuse.ts) bans a client address after
4
- // repeated invalid-model requests. A ban lapses on its own after its TTL, and
5
- // this is the escape hatch for lifting one early — a false positive (a shared
6
- // NAT, a client that genuinely mistyped) must be fixable without SQL. It is a
3
+ // The strike layer (security/model-abuse.ts) bans a caller after repeated
4
+ // invalid-model requests. A ban lapses on its own after its TTL, and this is
5
+ // the escape hatch for lifting one early — a false positive (a shared NAT, a
6
+ // client that genuinely mistyped) must be fixable without SQL. It is a
7
7
  // platform-admin surface: a ban is a security decision and the identity values
8
8
  // are cross-tenant.
9
9
  import type { AccessDecision } from "../../../security/access-control";
10
10
  import type { ModelStrikeService } from "../../../security/model-abuse";
11
11
 
12
+ /**
13
+ * The ban durations the console offers, in whole minutes. `null` is the
14
+ * "Permanent" choice.
15
+ *
16
+ * Expressed in minutes rather than milliseconds because that is what an
17
+ * operator picks and what the label says — a millisecond constant here drifts
18
+ * from the copy the moment either changes.
19
+ */
20
+ export const BAN_DURATION_CHOICES = [
21
+ { minutes: 60, label: "1 hour" },
22
+ { minutes: 6 * 60, label: "6 hours" },
23
+ { minutes: 12 * 60, label: "12 hours" },
24
+ { minutes: 24 * 60, label: "1 day" },
25
+ { minutes: 7 * 24 * 60, label: "7 days" },
26
+ { minutes: null, label: "Permanent" },
27
+ ] as const;
28
+
29
+ /** The duration a bare "ban this user" click uses: the shortest one on offer. */
30
+ export const DEFAULT_BAN_MINUTES = 60;
31
+
32
+ /** Whole minutes the console accepts, or `null` for permanent. */
33
+ export type BanDurationMinutes = (typeof BAN_DURATION_CHOICES)[number]["minutes"];
34
+
12
35
  export interface ModelAbuseRoutesConfig {
13
36
  readonly strikes: Pick<ModelStrikeService, "listBans" | "unban" | "ban">;
14
37
  readonly accessResolver: (request: Request) => AccessDecision | undefined;
15
38
  }
16
-
@@ -1,16 +1,64 @@
1
1
  import { Elysia, t } from "elysia";
2
- import type { ModelAbuseBan } from "../../../security/model-abuse";
2
+ import { PERMANENT_BAN_TTL_MS, type ModelAbuseBan } from "../../../security/model-abuse";
3
3
  import { ConsoleDomainError, errorResponse, requireGlobalAdmin } from "../../shared/errors";
4
- import type { ModelAbuseRoutesConfig } from "./contracts";
4
+ import {
5
+ BAN_DURATION_CHOICES,
6
+ DEFAULT_BAN_MINUTES,
7
+ type ModelAbuseRoutesConfig,
8
+ } from "./contracts";
5
9
 
6
10
  const unbanBody = t.Object({
11
+ /** The opaque member the listing reports, so a scoped ban lifts exactly it. */
7
12
  identity: t.String({ minLength: 1 }),
8
13
  });
9
14
 
10
15
  const banBody = t.Object({
11
16
  ip: t.String({ minLength: 1, maxLength: 45 }),
17
+ /**
18
+ * The key this ban is scoped to. Omitted or null is the blunt instrument:
19
+ * every key used from this address. That is a real choice an operator makes
20
+ * when the address is the problem, not a fallback for a missing value — so it
21
+ * is explicit in the body rather than inferred from an empty string.
22
+ */
23
+ apiKeyId: t.Optional(t.Union([t.String({ minLength: 1 }), t.Null()])),
24
+ /** Whole minutes, or null for permanent. Default one hour. */
25
+ minutes: t.Optional(t.Union([t.Number({ minimum: 1 }), t.Null()])),
26
+ /**
27
+ * Bans the address outright, whatever `apiKeyId` says. Kept as its own flag
28
+ * so "no key supplied" cannot quietly widen into "every key" — the two read
29
+ * the same in a body that only had the one field.
30
+ */
31
+ addressOnly: t.Optional(t.Boolean()),
12
32
  });
13
33
 
34
+ /** The durations the route accepts, as whole minutes. `null` is permanent. */
35
+ const ALLOWED_MINUTES = new Set<number | null>(
36
+ BAN_DURATION_CHOICES.map((choice) => choice.minutes),
37
+ );
38
+
39
+ /**
40
+ * How long a requested ban lasts, in milliseconds.
41
+ *
42
+ * `null` means permanent, which is a very long TTL rather than an unbounded
43
+ * ban: both stores lapse a ban by comparing an expiry to now, so a genuinely
44
+ * infinite ban would need a second mechanism and a second way to get it wrong.
45
+ *
46
+ * Anything not on the offered list is rejected rather than clamped. Clamping
47
+ * would answer a request for 90 days with a week and report success, and an
48
+ * operator who asked for the longer ban would believe they got it.
49
+ */
50
+ function banTtlMs(minutes: number | null | undefined): number {
51
+ const chosen = minutes === undefined ? DEFAULT_BAN_MINUTES : minutes;
52
+ if (!ALLOWED_MINUTES.has(chosen)) {
53
+ throw new ConsoleDomainError(
54
+ "invalid_request",
55
+ 400,
56
+ "Unsupported ban duration. Choose one of the offered durations.",
57
+ );
58
+ }
59
+ return chosen === null ? PERMANENT_BAN_TTL_MS : chosen * 60_000;
60
+ }
61
+
14
62
  export function createModelAbuseRoutes(config: ModelAbuseRoutesConfig): Elysia {
15
63
  return new Elysia()
16
64
  .get("/model-bans", async ({ request, set }) => {
@@ -25,7 +73,12 @@ export function createModelAbuseRoutes(config: ModelAbuseRoutesConfig): Elysia {
25
73
  .post("/model-bans", { body: banBody }, async ({ request, body, set }) => {
26
74
  try {
27
75
  requireGlobalAdmin(config.accessResolver(request));
28
- const ban = await config.strikes.ban(body.ip);
76
+ const ban = await config.strikes.ban({
77
+ ip: body.ip,
78
+ ...(body.apiKeyId === undefined ? {} : { apiKeyId: body.apiKeyId }),
79
+ ttlMs: banTtlMs(body.minutes),
80
+ ...(body.addressOnly === undefined ? {} : { addressOnly: body.addressOnly }),
81
+ });
29
82
  return { ban };
30
83
  } catch (e) {
31
84
  return errorResponse(e, set, "Model ban failed");
@@ -2,7 +2,7 @@ import { eq } from "drizzle-orm";
2
2
 
3
3
  import type { CartethyiaDatabase } from "../../persistence/db-handle";
4
4
  import { apiKeys } from "../../persistence/schema";
5
- import { queryTelemetryEvents } from "../../observability/event-log";
5
+ import { queryIndexedBuckets } from "../../observability/event-day-index";
6
6
  import { utcDayStart, utcMonthStart } from "../shared/day";
7
7
 
8
8
  /**
@@ -33,9 +33,20 @@ export async function familyBucketSpend(
33
33
  .where(eq(apiKeys.parentKeyId, keyId));
34
34
  const keyIds = [keyId, ...children.map((child) => child.id)];
35
35
  const spentSince = async (start: Date): Promise<number> => {
36
- const events = await queryTelemetryEvents({ apiKeyIds: keyIds, since: start });
37
- return events
38
- .reduce((sum, event) => sum + (event.inputTokens ?? 0) + (event.outputTokens ?? 0), 0);
36
+ // Folded first, so the index covers every event written so far including the
37
+ // current day's — then read from the index alone.
38
+ //
39
+ // Deliberately not index *plus* events: they hold the same requests, and
40
+ // adding them doubled every bucket. Folding first is what makes the index
41
+ // sufficient on its own; the watermark is the point up to which it is
42
+ // complete.
43
+ const allowed = new Set(keyIds);
44
+ const indexed = await queryIndexedBuckets({ since: start, foldFirst: true }).catch(
45
+ () => [],
46
+ );
47
+ return indexed
48
+ .filter((bucket) => bucket.apiKeyId !== null && allowed.has(bucket.apiKeyId))
49
+ .reduce((sum, bucket) => sum + bucket.inputTokens + bucket.outputTokens, 0);
39
50
  };
40
51
  const [daily, monthly] = await Promise.all([
41
52
  spentSince(utcDayStart(now)),