cartethyia 2.5.3 → 2.5.4

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 (262) hide show
  1. package/dist/dashboard/assets/{BannedPage-hfCzi-Ux.js → BannedPage-C2orBsI2.js} +1 -1
  2. package/dist/dashboard/assets/BannedPage-C2orBsI2.js.gz +0 -0
  3. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +1 -0
  4. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolsPage-DEDyKBlC.js → CliToolsPage-BV6pxVQo.js} +1 -1
  6. package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
  7. package/dist/dashboard/assets/{CombosPage-CfuYiQ8Z.js → CombosPage-Bsan4679.js} +1 -1
  8. package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
  9. package/dist/dashboard/assets/{ConsoleLogPage-bsLuZmYH.js → ConsoleLogPage-CW-wPmJ6.js} +1 -1
  10. package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
  11. package/dist/dashboard/assets/{CustomizationPage-_UWhwziJ.js → CustomizationPage-0p8uX7vB.js} +1 -1
  12. package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
  13. package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js +1 -0
  14. package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
  15. package/dist/dashboard/assets/{LoginPage-DDVIfZGW.js → LoginPage-BvzCx8Kr.js} +1 -1
  16. package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
  17. package/dist/dashboard/assets/{ModelPicker-CWkTUtpX.js → ModelPicker-BY1aKEkP.js} +1 -1
  18. package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
  19. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +1 -0
  20. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
  21. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +3 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
  23. package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js +1 -0
  24. package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
  25. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +7 -0
  26. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
  27. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +1 -0
  28. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
  29. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +1 -0
  30. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
  31. package/dist/dashboard/assets/{SetupPage-D6pmQ86L.js → SetupPage-DDbMQsoo.js} +1 -1
  32. package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
  33. package/dist/dashboard/assets/{SortableList-DWvKiuSl.js → SortableList-CB3Z7VTf.js} +1 -1
  34. package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
  35. package/dist/dashboard/assets/{StudioPage-CmkUrgdj.js → StudioPage-D95UCPHI.js} +3 -3
  36. package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
  37. package/dist/dashboard/assets/{UsagePage-B47uOydr.js → UsagePage-Cnae9SWA.js} +13 -13
  38. package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
  39. package/dist/dashboard/assets/{api-keys-uNywYlfv.js → api-keys-VY68Mhd1.js} +1 -1
  40. package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
  41. package/dist/dashboard/assets/app-COlK95oi.css +2 -0
  42. package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
  43. package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-Chi7pCnt.js} +1 -1
  44. package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
  45. package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-D4pQapKj.js} +1 -1
  46. package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
  47. package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-DH6cg-_B.js} +1 -1
  48. package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
  49. package/dist/dashboard/assets/{card--AtFedAq.js → card-BKqlUOe4.js} +1 -1
  50. package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
  51. package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-fTgljTGA.js} +1 -1
  52. package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
  53. package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-D5Ys_sM0.js} +1 -1
  54. package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.js.gz +0 -0
  55. package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
  56. package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
  57. package/dist/dashboard/assets/{dialog-BtpsJU98.js → dialog-BYj-8WuK.js} +1 -1
  58. package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
  59. package/dist/dashboard/assets/{drawer-WwwtzihF.js → drawer-CmXIZ6H1.js} +1 -1
  60. package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
  61. package/dist/dashboard/assets/entry-CBg5zeu8.js +1 -0
  62. package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
  63. package/dist/dashboard/assets/entry-CQBr6U9c.js +1 -0
  64. package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-CgdSAzEV.js +4 -0
  66. package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
  67. package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-BlptL8Uf.js} +1 -1
  68. package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
  69. package/dist/dashboard/assets/{index-BydyKaDz.js → index-js6TJvdr.js} +2 -2
  70. package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
  71. package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-DUWDSmDV.js} +1 -1
  72. package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
  73. package/dist/dashboard/assets/{input-BG41et9G.js → input-LVjgZq4f.js} +1 -1
  74. package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
  75. package/dist/dashboard/assets/{layout-C8mer_Ak.js → layout-CPfxpsfy.js} +1 -1
  76. package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
  77. package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-DupvLfDx.js} +1 -1
  78. package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
  79. package/dist/dashboard/assets/{live-BJGYRBnR.js → live-C4pEJUqI.js} +1 -1
  80. package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
  81. package/dist/dashboard/assets/model-bans-BRiuufkB.js +1 -0
  82. package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
  83. package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-B4ROwk8L.js} +1 -1
  84. package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
  85. package/dist/dashboard/assets/numbers-YNcHvRY7.js +2 -0
  86. package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
  87. package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-gmjzC3RS.js} +1 -1
  88. package/dist/dashboard/assets/{query-keys-D7wX2vM5.js.gz → query-keys-gmjzC3RS.js.gz} +0 -0
  89. package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-C7tBT5o-.js} +1 -1
  90. package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
  91. package/dist/dashboard/assets/{select-BO6h8yNv.js → select-WUCXorXw.js} +2 -2
  92. package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
  93. package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-C_zKl1GS.js} +1 -1
  94. package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
  95. package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-_3O0F8Xn.js} +1 -1
  96. package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
  97. package/dist/dashboard/assets/{state-DYdQmdAl.js → state-BgTxHcJS.js} +1 -1
  98. package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
  99. package/dist/dashboard/assets/switch-DEjTtOWf.js +1 -0
  100. package/dist/dashboard/assets/switch-DEjTtOWf.js.gz +0 -0
  101. package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
  102. package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
  103. package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-BWSC0UAS.js} +1 -1
  104. package/dist/dashboard/assets/useMutation-BWSC0UAS.js.gz +0 -0
  105. package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
  106. package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
  107. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  108. package/dist/dashboard/index.html +1 -1
  109. package/dist/dashboard/index.html.gz +0 -0
  110. package/dist/dashboard/precompressed-assets.json +54 -54
  111. package/package.json +1 -1
  112. package/scripts/cli/autostart.ts +237 -0
  113. package/scripts/cli/command/console-menu.ts +4 -8
  114. package/scripts/cli/command/update.ts +27 -10
  115. package/scripts/cli/main.ts +28 -1
  116. package/src/boot/app.ts +693 -693
  117. package/src/boot/dependencies.ts +646 -616
  118. package/src/config/spec.ts +55 -0
  119. package/src/config/telemetry.ts +46 -0
  120. package/src/console/api-keys/bucket-spend.ts +15 -4
  121. package/src/console/backup/contracts.ts +15 -2
  122. package/src/console/console-router.ts +8 -0
  123. package/src/console/data-management/contracts.ts +93 -0
  124. package/src/console/data-management/operations.ts +229 -0
  125. package/src/console/data-management/routes.ts +104 -0
  126. package/src/console/domain-registration.ts +39 -0
  127. package/src/console/observability/contracts.ts +524 -485
  128. package/src/console/observability/store.ts +1115 -1018
  129. package/src/console/providers/catalog/contracts.ts +6 -0
  130. package/src/console/providers/catalog/provider-operations.ts +20 -0
  131. package/src/console/providers/catalog/routes.ts +7 -0
  132. package/src/console/providers/catalog/store.ts +22 -1
  133. package/src/console/quota/account-quota-shared.ts +52 -39
  134. package/src/console/quota/refresh.ts +441 -429
  135. package/src/console/routing/pools/import-parse.ts +206 -0
  136. package/src/console/routing/pools/operations.ts +543 -501
  137. package/src/console/routing/pools/routes.ts +181 -164
  138. package/src/console/settings/contracts.ts +37 -0
  139. package/src/console/settings/store.ts +51 -0
  140. package/src/console/updates/routes.ts +16 -0
  141. package/src/main.ts +10 -0
  142. package/src/observability/event-compaction.ts +315 -0
  143. package/src/observability/event-day-index.ts +442 -0
  144. package/src/observability/event-log.ts +152 -3
  145. package/src/observability/payload-capture.ts +8 -1
  146. package/src/observability/payload-store.ts +440 -376
  147. package/src/persistence/health-event-flush.ts +36 -0
  148. package/src/persistence/schema.ts +1402 -1360
  149. package/src/persistence/stores/telemetry-store.ts +237 -214
  150. package/src/protocol/codecs.ts +1 -2
  151. package/src/protocol/image-clamp.ts +19 -1
  152. package/src/protocol/registry.ts +8 -0
  153. package/src/protocol/request/gemini.ts +9 -2
  154. package/src/protocol/request/messages.ts +14 -2
  155. package/src/providers/adapters/compatible-adapter.ts +3 -0
  156. package/src/providers/bundled-discovery-providers.ts +122 -119
  157. package/src/providers/configured-openai-providers.ts +136 -134
  158. package/src/providers/default-registry.ts +5 -4
  159. package/src/providers/discovery/openai-model-discovery.ts +5 -0
  160. package/src/providers/discovery/openrouter-free-roster.ts +15 -17
  161. package/src/providers/integrations/cline/cline-quota.ts +132 -125
  162. package/src/providers/integrations/codex/codex-headers.ts +25 -39
  163. package/src/providers/integrations/codex/codex.ts +2 -29
  164. package/src/providers/integrations/cursor/cursor-quota.ts +310 -0
  165. package/src/providers/integrations/cursor/cursor-request.ts +616 -471
  166. package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
  167. package/src/providers/integrations/cursor/cursor.ts +1485 -960
  168. package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
  169. package/src/providers/integrations/factory/factory.ts +8 -1
  170. package/src/providers/integrations/gemini.ts +6 -1
  171. package/src/providers/integrations/grok/grok-quota.ts +279 -177
  172. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
  173. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
  174. package/src/providers/integrations/opencode/provider.ts +12 -13
  175. package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
  176. package/src/providers/integrations/trae/trae-quota.ts +384 -0
  177. package/src/providers/operations/account-health-service.ts +1523 -1480
  178. package/src/providers/provider-contracts.ts +7 -0
  179. package/src/providers/provider-metadata.ts +6 -0
  180. package/src/providers/quota/quota-support.ts +8 -0
  181. package/src/security/api-key-auth.ts +504 -494
  182. package/src/transport/dispatch/generation.ts +6 -0
  183. package/src/transport/dispatch/proxy-request.ts +24 -1
  184. package/src/transport/dispatch/ratelimit-capture.ts +37 -0
  185. package/src/transport/dispatch/streaming/stream.ts +14 -0
  186. package/src/transport/dispatch/upstream.ts +9 -0
  187. package/src/transport/request/preparer.ts +7 -6
  188. package/src/transport/request/state.ts +9 -0
  189. package/src/transport/request/tenant-preferences.ts +60 -0
  190. package/src/transport/routing/route-catalog.ts +58 -10
  191. package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
  192. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +0 -1
  193. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
  194. package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
  195. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
  196. package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
  197. package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
  198. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +0 -1
  199. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
  200. package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
  201. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
  202. package/dist/dashboard/assets/OverviewPage-B53YYMng.js +0 -1
  203. package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
  204. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +0 -3
  205. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
  206. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +0 -1
  207. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
  208. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +0 -7
  209. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
  210. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +0 -1
  211. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
  212. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +0 -1
  213. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
  214. package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
  215. package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
  216. package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
  217. package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
  218. package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
  219. package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
  220. package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
  221. package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
  222. package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
  223. package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
  224. package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
  225. package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
  226. package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
  227. package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
  228. package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
  229. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  230. package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
  231. package/dist/dashboard/assets/entry-CkryrIea.js +0 -4
  232. package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
  233. package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
  234. package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
  235. package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
  236. package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
  237. package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
  238. package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
  239. package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
  240. package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
  241. package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
  242. package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
  243. package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
  244. package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
  245. package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
  246. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  247. package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
  248. package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
  249. package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
  250. package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
  251. package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
  252. package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
  253. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  254. package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
  255. package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
  256. package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
  257. package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
  258. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
  259. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  260. package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
  261. /package/dist/dashboard/assets/{app-CafWtVXA.js → app-DCQZRmHY.js} +0 -0
  262. /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-DCQZRmHY.js.gz} +0 -0
@@ -1,164 +1,181 @@
1
- // Network-pool control-plane HTTP routes. The only module that touches Elysia.
2
- import { Elysia, t } from "elysia";
3
- import { consoleErrorHandler } from "../../shared/errors";
4
- import { literalUnion } from "../../shared/elysia-schema";
5
- import { RELAY_TARGETS, type RelayDeployRequest } from "./relay-deploy";
6
- import { RELAY_REGIONS } from "./relay-regions";
7
- import { SPEED_TEST_DEFAULT_BYTES } from "./speed-test-sizes";
8
- import {
9
- MAX_BATCH_PROBE_TARGETS,
10
- POOL_KINDS,
11
- POOL_ROUTING_STRATEGIES,
12
- POOL_STATUSES,
13
- type CreateNetworkPoolRequest,
14
- type NetworkPoolConfig,
15
- type PoolStrategySetting,
16
- } from "./contracts";
17
- import { createNetworkPoolOperations } from "./operations";
18
-
19
- const poolKindSchema = literalUnion(POOL_KINDS);
20
- const createPoolBody = t.Object({
21
- kind: poolKindSchema,
22
- label: t.Optional(t.String()),
23
- endpoint: t.String(),
24
- credential: t.Optional(t.String()),
25
- maxInflight: t.Optional(t.Number()),
26
- weight: t.Optional(t.Number()),
27
- status: t.Optional(literalUnion(POOL_STATUSES)),
28
- config: t.Optional(t.Record(t.String(), t.Unknown())),
29
- /** Egress allowance in bytes; `null` clears it back to unmetered. */
30
- quotaBytes: t.Optional(t.Union([t.Number({ minimum: 1 }), t.Null()])),
31
- /** Operator-set family/tag naming which family this node belongs to. */
32
- family: t.Optional(t.Union([t.String(), t.Null()])),
33
- });
34
- const deployRelayBody = t.Object({
35
- target: literalUnion(RELAY_TARGETS),
36
- token: t.String({ minLength: 1 }),
37
- accountId: t.Optional(t.String()),
38
- projectName: t.Optional(t.String()),
39
- /** Vercel function region; ignored for targets that have no region control. */
40
- region: t.Optional(literalUnion(RELAY_REGIONS)),
41
- });
42
- const testBatchBody = t.Object({
43
- targets: t.Array(createPoolBody, { minItems: 1, maxItems: MAX_BATCH_PROBE_TARGETS }),
44
- });
45
- const updatePoolBody = t.Partial(createPoolBody);
46
- export function createNetworkPoolRoutes(config: NetworkPoolConfig): Elysia {
47
- const factory = createNetworkPoolOperations(config);
48
- return new Elysia({ prefix: "/network/pools" })
49
- .error(consoleErrorHandler("Network pool operation failed"))
50
- .get("/", async ({ request }) => {
51
- return await factory.listPools(config.accessResolver(request));
52
- })
53
- .get("/:poolId", async ({ request, params }) => {
54
- return await factory.getPoolDetail(config.accessResolver(request), params.poolId);
55
- })
56
- .post("/", { body: createPoolBody }, async ({ request, body, set }) => {
57
- set.status = 201;
58
- return await factory.createPool(
59
- config.accessResolver(request),
60
- body as CreateNetworkPoolRequest,
61
- );
62
- })
63
- .patch("/:poolId", { body: updatePoolBody }, async ({ request, params, body }) => {
64
- return await factory.updatePool(
65
- config.accessResolver(request),
66
- params.poolId,
67
- body as Partial<CreateNetworkPoolRequest>,
68
- );
69
- })
70
- .delete("/:poolId", async ({ request, params }) => {
71
- return await factory.deletePool(config.accessResolver(request), params.poolId);
72
- })
73
- .get("/:poolId/health-events", async ({ request, params }) => {
74
- return await factory.listPoolHealthEvents(config.accessResolver(request), params.poolId);
75
- })
76
- .post("/:poolId/recover", async ({ request, params }) => {
77
- return await factory.recoverPool(config.accessResolver(request), params.poolId);
78
- })
79
- .post(
80
- "/:poolId/provider-probe",
81
- { body: t.Object({ providerId: t.String() }) },
82
- async ({ request, params, body }) => {
83
- return await factory.providerProbe(
84
- config.accessResolver(request),
85
- params.poolId,
86
- body.providerId,
87
- );
88
- },
89
- )
90
- .post("/:poolId/health-check", async ({ request, params }) => {
91
- return await factory.healthCheck(config.accessResolver(request), params.poolId);
92
- })
93
- .post(
94
- "/:poolId/speed-test",
95
- { body: t.Optional(t.Object({ bytes: t.Optional(t.Number()) })) },
96
- async ({ request, params, body }) => {
97
- return await factory.speedTest(
98
- config.accessResolver(request),
99
- params.poolId,
100
- body?.bytes ?? SPEED_TEST_DEFAULT_BYTES,
101
- );
102
- },
103
- )
104
- .post("/test", { body: createPoolBody }, async ({ request, body }) => {
105
- return await factory.probeAdHocPool(
106
- config.accessResolver(request),
107
- body as CreateNetworkPoolRequest,
108
- );
109
- })
110
- .post("/test-batch", { body: testBatchBody }, async ({ request, body }) => {
111
- return await factory.probeAdHocPoolBatch(
112
- config.accessResolver(request),
113
- body.targets as CreateNetworkPoolRequest[],
114
- );
115
- })
116
- .post(
117
- "/test-batch/provider",
118
- {
119
- body: t.Object({
120
- providerId: t.String(),
121
- targets: t.Array(createPoolBody, { minItems: 1, maxItems: MAX_BATCH_PROBE_TARGETS }),
122
- }),
123
- },
124
- async ({ request, body }) => {
125
- return await factory.probeAdHocProviderBatch(
126
- config.accessResolver(request),
127
- body.targets as CreateNetworkPoolRequest[],
128
- body.providerId,
129
- );
130
- },
131
- )
132
- .post(
133
- "/:poolId/cooldowns/clear",
134
- { body: t.Optional(t.Object({ providerId: t.Optional(t.String()) })) },
135
- async ({ request, params, body }) => {
136
- return await factory.clearPoolCooldown(
137
- config.accessResolver(request),
138
- params.poolId,
139
- body?.providerId,
140
- );
141
- },
142
- )
143
- .post("/relay/deploy", { body: deployRelayBody }, async ({ request, body }) => {
144
- return await factory.deployRelay(config.accessResolver(request), body as RelayDeployRequest);
145
- })
146
- .get("/strategy", async ({ request }) => {
147
- return await factory.getStrategy(config.accessResolver(request));
148
- })
149
- .patch(
150
- "/strategy",
151
- {
152
- body: t.Object({
153
- strategy: t.Optional(literalUnion(POOL_ROUTING_STRATEGIES)),
154
- rotateCount: t.Optional(t.Number()),
155
- }),
156
- },
157
- async ({ request, body }) => {
158
- return await factory.updateStrategy(
159
- config.accessResolver(request),
160
- body as Partial<PoolStrategySetting>,
161
- );
162
- },
163
- ) as unknown as Elysia;
164
- }
1
+ // Network-pool control-plane HTTP routes. The only module that touches Elysia.
2
+ import { Elysia, t } from "elysia";
3
+ import { consoleErrorHandler } from "../../shared/errors";
4
+ import { literalUnion } from "../../shared/elysia-schema";
5
+ import { RELAY_TARGETS, type RelayDeployRequest } from "./relay-deploy";
6
+ import { RELAY_REGIONS } from "./relay-regions";
7
+ import { SPEED_TEST_DEFAULT_BYTES } from "./speed-test-sizes";
8
+ import {
9
+ MAX_BATCH_PROBE_TARGETS,
10
+ POOL_KINDS,
11
+ POOL_ROUTING_STRATEGIES,
12
+ POOL_STATUSES,
13
+ type CreateNetworkPoolRequest,
14
+ type NetworkPoolConfig,
15
+ type PoolStrategySetting,
16
+ } from "./contracts";
17
+ import { createNetworkPoolOperations } from "./operations";
18
+ import { parseProxyFile } from "./import-parse";
19
+
20
+ const poolKindSchema = literalUnion(POOL_KINDS);
21
+ const createPoolBody = t.Object({
22
+ kind: poolKindSchema,
23
+ label: t.Optional(t.String()),
24
+ endpoint: t.String(),
25
+ credential: t.Optional(t.String()),
26
+ maxInflight: t.Optional(t.Number()),
27
+ weight: t.Optional(t.Number()),
28
+ status: t.Optional(literalUnion(POOL_STATUSES)),
29
+ config: t.Optional(t.Record(t.String(), t.Unknown())),
30
+ /** Egress allowance in bytes; `null` clears it back to unmetered. */
31
+ quotaBytes: t.Optional(t.Union([t.Number({ minimum: 1 }), t.Null()])),
32
+ /** Operator-set family/tag naming which family this node belongs to. */
33
+ family: t.Optional(t.Union([t.String(), t.Null()])),
34
+ });
35
+ const deployRelayBody = t.Object({
36
+ target: literalUnion(RELAY_TARGETS),
37
+ token: t.String({ minLength: 1 }),
38
+ accountId: t.Optional(t.String()),
39
+ projectName: t.Optional(t.String()),
40
+ /** Vercel function region; ignored for targets that have no region control. */
41
+ region: t.Optional(literalUnion(RELAY_REGIONS)),
42
+ });
43
+ const testBatchBody = t.Object({
44
+ targets: t.Array(createPoolBody, { minItems: 1, maxItems: MAX_BATCH_PROBE_TARGETS }),
45
+ });
46
+ const updatePoolBody = t.Partial(createPoolBody);
47
+ /**
48
+ * A pasted proxy list. `content` is the file's text; the shapes accepted are
49
+ * documented on {@link parseProxyFile}. `maxInflight` and `weight` apply to
50
+ * every imported row, because a file carries no per-row tuning.
51
+ */
52
+ const importPoolsBody = t.Object({
53
+ content: t.String({ minLength: 1 }),
54
+ maxInflight: t.Optional(t.Number()),
55
+ weight: t.Optional(t.Number()),
56
+ family: t.Optional(t.Union([t.String(), t.Null()])),
57
+ labelPrefix: t.Optional(t.String()),
58
+ });
59
+ export function createNetworkPoolRoutes(config: NetworkPoolConfig): Elysia {
60
+ const factory = createNetworkPoolOperations(config);
61
+ return new Elysia({ prefix: "/network/pools" })
62
+ .error(consoleErrorHandler("Network pool operation failed"))
63
+ .get("/", async ({ request }) => {
64
+ return await factory.listPools(config.accessResolver(request));
65
+ })
66
+ .get("/:poolId", async ({ request, params }) => {
67
+ return await factory.getPoolDetail(config.accessResolver(request), params.poolId);
68
+ })
69
+ .post("/", { body: createPoolBody }, async ({ request, body, set }) => {
70
+ set.status = 201;
71
+ return await factory.createPool(
72
+ config.accessResolver(request),
73
+ body as CreateNetworkPoolRequest,
74
+ );
75
+ })
76
+ .post("/import", { body: importPoolsBody }, async ({ request, body, set }) => {
77
+ set.status = 200;
78
+ return await factory.importPools(config.accessResolver(request), body);
79
+ })
80
+ .patch("/:poolId", { body: updatePoolBody }, async ({ request, params, body }) => {
81
+ return await factory.updatePool(
82
+ config.accessResolver(request),
83
+ params.poolId,
84
+ body as Partial<CreateNetworkPoolRequest>,
85
+ );
86
+ })
87
+ .delete("/:poolId", async ({ request, params }) => {
88
+ return await factory.deletePool(config.accessResolver(request), params.poolId);
89
+ })
90
+ .get("/:poolId/health-events", async ({ request, params }) => {
91
+ return await factory.listPoolHealthEvents(config.accessResolver(request), params.poolId);
92
+ })
93
+ .post("/:poolId/recover", async ({ request, params }) => {
94
+ return await factory.recoverPool(config.accessResolver(request), params.poolId);
95
+ })
96
+ .post(
97
+ "/:poolId/provider-probe",
98
+ { body: t.Object({ providerId: t.String() }) },
99
+ async ({ request, params, body }) => {
100
+ return await factory.providerProbe(
101
+ config.accessResolver(request),
102
+ params.poolId,
103
+ body.providerId,
104
+ );
105
+ },
106
+ )
107
+ .post("/:poolId/health-check", async ({ request, params }) => {
108
+ return await factory.healthCheck(config.accessResolver(request), params.poolId);
109
+ })
110
+ .post(
111
+ "/:poolId/speed-test",
112
+ { body: t.Optional(t.Object({ bytes: t.Optional(t.Number()) })) },
113
+ async ({ request, params, body }) => {
114
+ return await factory.speedTest(
115
+ config.accessResolver(request),
116
+ params.poolId,
117
+ body?.bytes ?? SPEED_TEST_DEFAULT_BYTES,
118
+ );
119
+ },
120
+ )
121
+ .post("/test", { body: createPoolBody }, async ({ request, body }) => {
122
+ return await factory.probeAdHocPool(
123
+ config.accessResolver(request),
124
+ body as CreateNetworkPoolRequest,
125
+ );
126
+ })
127
+ .post("/test-batch", { body: testBatchBody }, async ({ request, body }) => {
128
+ return await factory.probeAdHocPoolBatch(
129
+ config.accessResolver(request),
130
+ body.targets as CreateNetworkPoolRequest[],
131
+ );
132
+ })
133
+ .post(
134
+ "/test-batch/provider",
135
+ {
136
+ body: t.Object({
137
+ providerId: t.String(),
138
+ targets: t.Array(createPoolBody, { minItems: 1, maxItems: MAX_BATCH_PROBE_TARGETS }),
139
+ }),
140
+ },
141
+ async ({ request, body }) => {
142
+ return await factory.probeAdHocProviderBatch(
143
+ config.accessResolver(request),
144
+ body.targets as CreateNetworkPoolRequest[],
145
+ body.providerId,
146
+ );
147
+ },
148
+ )
149
+ .post(
150
+ "/:poolId/cooldowns/clear",
151
+ { body: t.Optional(t.Object({ providerId: t.Optional(t.String()) })) },
152
+ async ({ request, params, body }) => {
153
+ return await factory.clearPoolCooldown(
154
+ config.accessResolver(request),
155
+ params.poolId,
156
+ body?.providerId,
157
+ );
158
+ },
159
+ )
160
+ .post("/relay/deploy", { body: deployRelayBody }, async ({ request, body }) => {
161
+ return await factory.deployRelay(config.accessResolver(request), body as RelayDeployRequest);
162
+ })
163
+ .get("/strategy", async ({ request }) => {
164
+ return await factory.getStrategy(config.accessResolver(request));
165
+ })
166
+ .patch(
167
+ "/strategy",
168
+ {
169
+ body: t.Object({
170
+ strategy: t.Optional(literalUnion(POOL_ROUTING_STRATEGIES)),
171
+ rotateCount: t.Optional(t.Number()),
172
+ }),
173
+ },
174
+ async ({ request, body }) => {
175
+ return await factory.updateStrategy(
176
+ config.accessResolver(request),
177
+ body as Partial<PoolStrategySetting>,
178
+ );
179
+ },
180
+ ) as unknown as Elysia;
181
+ }
@@ -26,6 +26,19 @@ export interface RuntimeSettingsResponse {
26
26
  readonly ponyTailEnabled: boolean;
27
27
  /** PonyTail directive intensity; `full` by default. */
28
28
  readonly ponyTailLevel: PonyTailLevel;
29
+ /**
30
+ * Ignores an account-wide cooldown when building route candidates. Off by
31
+ * default: dialing a cooled-down account reproduces the refusal that parked
32
+ * it, and can exhaust the candidate list. Overrides nothing else — the
33
+ * cooldown is still recorded and still recovered by the sweep.
34
+ */
35
+ readonly forceDisableCooldown: boolean;
36
+ /** Per-tenant upstream request timeout in seconds; null means deployment default. */
37
+ readonly upstreamTimeoutSeconds: number | null;
38
+ /** Per-tenant telemetry compaction age in days; null means deployment default. */
39
+ readonly telemetryWindowMinutes: number | null;
40
+ /** Per-tenant inline attachment ceiling in bytes; null means deployment default. */
41
+ readonly imageClampMaxBytes: number | null;
29
42
  readonly updatedAt: string;
30
43
  }
31
44
 
@@ -150,4 +163,28 @@ export const runtimeUpdateBody = t.Object({
150
163
  rtkPruneLevel: t.Optional(literalUnion(RTK_LEVELS)),
151
164
  ponyTailEnabled: t.Optional(t.Boolean()),
152
165
  ponyTailLevel: t.Optional(literalUnion(PONYTAIL_LEVELS)),
166
+ /*
167
+ * Operational overrides. Every field a caller may patch must appear here:
168
+ * this schema is what the route parses the body against, and a key it does
169
+ * not name is dropped before the store sees it. These four were added to the
170
+ * request type and the store but never to this object, so a save returned
171
+ * 200 and changed nothing — the mutation's toast fired while the stored
172
+ * value stayed put.
173
+ *
174
+ * `t.Null()` is what "Deployment default" sends, and it must survive:
175
+ * clearing an override is a real edit, not an absent one.
176
+ *
177
+ * The bounds mirror the store's own normalizers, so an out-of-range value is
178
+ * rejected here rather than silently clamped on the way in.
179
+ */
180
+ forceDisableCooldown: t.Optional(t.Boolean()),
181
+ upstreamTimeoutSeconds: t.Optional(
182
+ t.Union([t.Null(), t.Number({ minimum: 5, maximum: 600 })]),
183
+ ),
184
+ telemetryWindowMinutes: t.Optional(
185
+ t.Union([t.Null(), t.Number({ minimum: 1, maximum: 1440 })]),
186
+ ),
187
+ imageClampMaxBytes: t.Optional(
188
+ t.Union([t.Null(), t.Number({ minimum: 64 * 1024, maximum: 100 * 1024 * 1024 })]),
189
+ ),
153
190
  });
@@ -68,6 +68,43 @@ function normalizeTelemetryPayloadDepth(value: unknown): TelemetryPayloadDepth {
68
68
  return "minimum";
69
69
  }
70
70
 
71
+ /**
72
+ * Bounds a per-tenant numeric override, or `null` for "use the default".
73
+ *
74
+ * Clamped rather than rejected: these are operational knobs an operator tunes
75
+ * live, and a PATCH that throws on an out-of-range value leaves them stuck at
76
+ * whatever was stored. Clamping keeps the value inside the range the request
77
+ * path can actually honour — the same range the deployment env var accepts.
78
+ *
79
+ * `null` (and every non-finite value) means "no override", which is distinct
80
+ * from "override with zero".
81
+ */
82
+ function normalizeBoundedNumber(value: unknown, min: number, max: number): number | null {
83
+ if (value === null || value === undefined) return null;
84
+ const parsed = typeof value === "number" ? value : Number(value);
85
+ if (!Number.isFinite(parsed)) return null;
86
+ // A value at or below zero is not a setting, it is a typo: clamping it to
87
+ // the floor would silently give the request a 5-second budget when the
88
+ // operator meant "no override". Non-positive therefore reads as absent.
89
+ if (parsed <= 0) return null;
90
+ return Math.min(max, Math.max(min, Math.floor(parsed)));
91
+ }
92
+
93
+ /** Upstream timeout in seconds: 5 s floor, 600 s ceiling, matching the env var. */
94
+ function normalizeUpstreamTimeoutSeconds(value: unknown): number | null {
95
+ return normalizeBoundedNumber(value, 5, 600);
96
+ }
97
+
98
+ /** Source window in minutes: 1 floor, 1440 (one day) ceiling, matching the env var. */
99
+ function normalizeTelemetryWindowMinutes(value: unknown): number | null {
100
+ return normalizeBoundedNumber(value, 1, 1440);
101
+ }
102
+
103
+ /** Inline attachment ceiling in bytes: 64 KB floor, 100 MB ceiling. */
104
+ function normalizeImageClampMaxBytes(value: unknown): number | null {
105
+ return normalizeBoundedNumber(value, 64 * 1024, 100 * 1024 * 1024);
106
+ }
107
+
71
108
  function mapRuntimeSettingsRow(
72
109
  row: typeof consoleSettings.$inferSelect | undefined,
73
110
  redisBackend: RedisBackend,
@@ -95,6 +132,10 @@ function mapRuntimeSettingsRow(
95
132
  // requires the flag — never injected it.
96
133
  ponyTailEnabled: prefs.ponyTailEnabled === true,
97
134
  ponyTailLevel: normalizePonyTailLevel(prefs.ponyTailLevel),
135
+ forceDisableCooldown: prefs.forceDisableCooldown === true,
136
+ upstreamTimeoutSeconds: normalizeUpstreamTimeoutSeconds(prefs.upstreamTimeoutSeconds),
137
+ telemetryWindowMinutes: normalizeTelemetryWindowMinutes(prefs.telemetryWindowMinutes),
138
+ imageClampMaxBytes: normalizeImageClampMaxBytes(prefs.imageClampMaxBytes),
98
139
  updatedAt,
99
140
  };
100
141
  }
@@ -133,6 +174,16 @@ export class DrizzleRuntimeSettingsStore implements RuntimeSettingsStore {
133
174
  if (patch.rtkPruneLevel !== undefined) patchPrefs.rtkPruneLevel = patch.rtkPruneLevel;
134
175
  if (patch.ponyTailEnabled !== undefined) patchPrefs.ponyTailEnabled = patch.ponyTailEnabled;
135
176
  if (patch.ponyTailLevel !== undefined) patchPrefs.ponyTailLevel = patch.ponyTailLevel;
177
+ if (patch.forceDisableCooldown !== undefined)
178
+ patchPrefs.forceDisableCooldown = patch.forceDisableCooldown === true;
179
+ if (patch.upstreamTimeoutSeconds !== undefined)
180
+ patchPrefs.upstreamTimeoutSeconds = normalizeUpstreamTimeoutSeconds(patch.upstreamTimeoutSeconds);
181
+ if (patch.telemetryWindowMinutes !== undefined)
182
+ patchPrefs.telemetryWindowMinutes = normalizeTelemetryWindowMinutes(
183
+ patch.telemetryWindowMinutes,
184
+ );
185
+ if (patch.imageClampMaxBytes !== undefined)
186
+ patchPrefs.imageClampMaxBytes = normalizeImageClampMaxBytes(patch.imageClampMaxBytes);
136
187
 
137
188
  const rows = await this.db
138
189
  .insert(consoleSettings)
@@ -1,4 +1,5 @@
1
1
  import { Elysia } from "elysia";
2
+ import { spawn } from "node:child_process";
2
3
  import { consoleErrorHandler, requireTenantScope } from "../shared/errors";
3
4
  import { consoleSseResponse, createConsoleSseStream } from "../observability/sse";
4
5
  import type { ObservabilityStore } from "../observability/contracts";
@@ -86,6 +87,21 @@ export function createUpdateRoutes(config: {
86
87
  return new Elysia()
87
88
  .error(consoleErrorHandler("Update check failed"))
88
89
  .get("/updates/release", async () => await fetchRelease())
90
+ .post("/updates/apply", ({ request }) => {
91
+ requireTenantScope(config.accessResolver(request), "dashboard:write");
92
+ const isWin = process.platform === "win32";
93
+ const shell = isWin ? "cmd.exe" : "/bin/sh";
94
+ const install = "npm install -g cartethyia@latest --prefer-online";
95
+ const restart = isWin
96
+ ? "cartethyia start"
97
+ : "systemctl restart cartethyia 2>/dev/null || cartethyia start";
98
+ const script = `${install} && ${restart}`;
99
+ const args = isWin ? ["/c", script] : ["-c", script];
100
+ const child = spawn(shell, args, { detached: true, stdio: "ignore" });
101
+ child.unref();
102
+ setTimeout(() => process.exit(0), 1500);
103
+ return { started: true };
104
+ })
89
105
  .get("/notifications/stream", ({ request }) => {
90
106
  const tenantId = requireTenantScope(
91
107
  config.accessResolver(request),
package/src/main.ts CHANGED
@@ -68,6 +68,16 @@ const app = boot
68
68
  admissionService: boot.deps.admissionService,
69
69
  modelStrikes: boot.deps.modelStrikes,
70
70
  readRoutingAccountInflight: boot.deps.readRoutingAccountInflight,
71
+ // The same three writers the shutdown coordinator drains, so a manual
72
+ // flush from the console does exactly what a graceful stop would do
73
+ // for the queues, minus the stop.
74
+ flushBuffers: {
75
+ flush: async () => {
76
+ await boot.deps.telemetryBuffer.flush();
77
+ await boot.deps.usageFlush.flush();
78
+ await boot.deps.healthEventFlush.flush();
79
+ },
80
+ },
71
81
  },
72
82
  })
73
83
  : createGatewayShell();