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
@@ -51,8 +51,9 @@ export function classifyPoolConnectError(
51
51
  error: unknown,
52
52
  latencyMs: number,
53
53
  ): HealthCheckResult | undefined {
54
- // The agent throws a typed refusal carrying the status, so this no longer
55
- // re-parses the message text (which a wording change would silently break).
54
+ // The agent throws a typed refusal carrying the status, so this reads the
55
+ // status rather than re-parsing message text (which a wording change would
56
+ // silently break).
56
57
  if (!(error instanceof ProxyConnectRefusalError)) return undefined;
57
58
  if (error.statusCode !== 402 && error.statusCode !== 407) return undefined;
58
59
  return classifyPoolProbeResponse(poolId, { ok: false, status: error.statusCode }, latencyMs);
@@ -1,132 +1,132 @@
1
- /**
2
- * What one provider-probe answer means about the egress.
3
- *
4
- * Extracted from the store so the console, the CLI probe
5
- * (`scripts/operations/probe-egress.ts`) and the tests cannot drift into three
6
- * readings of the same response. The rule that matters is not obvious:
7
- *
8
- * **The provider's IP gate runs before it validates the body.** So a `4xx`
9
- * complaint about the payload proves the address was accepted — it is the
10
- * probe's request that is wrong, not the proxy. Mapping that to `unreachable`
11
- * ("the probe could not complete") blamed the proxy for our own defect and hid
12
- * working egresses behind a verdict that said the pool was unusable.
13
- *
14
- * Only the free-tier refusal retires an address. Every other answer either says
15
- * the address works (`allowed`, `throttled`) or says nothing about it
16
- * (`unreachable`), and treating "says nothing" as "refused" would drop a usable
17
- * pool for good.
18
- */
19
-
20
- export type ProbeVerdict = "allowed" | "blocked" | "throttled" | "unreachable";
21
-
22
- export interface ProbeAnswer {
23
- readonly status: ProbeVerdict;
24
- /** Operator-facing reason; absent when the answer needs none. */
25
- readonly reason?: string;
26
- }
27
-
28
- /** The upstream's own wording for "this address is retired". */
29
- const FREE_TIER_REFUSAL = /freetier|free tier can only be used/i;
30
-
31
- /**
32
- * Classifies one upstream response.
33
- *
34
- * `407` is separated from the generic 4xx branch on purpose: it comes from the
35
- * proxy front door, never from the provider, so it is a credential problem on
36
- * the tunnel and says nothing about whether the address is refused.
37
- */
38
- export function classifyProviderProbeAnswer(httpStatus: number, body: string): ProbeAnswer {
39
- if (httpStatus === 403 && FREE_TIER_REFUSAL.test(body)) {
40
- return { status: "blocked", reason: "Upstream refused this egress address" };
41
- }
42
- if (httpStatus === 407) {
43
- return { status: "unreachable", reason: "Proxy requires authentication" };
44
- }
45
- if (httpStatus === 429) {
46
- return { status: "throttled", reason: "Address accepted; rate limit spent" };
47
- }
48
- if (httpStatus >= 200 && httpStatus < 300) {
49
- return { status: "allowed" };
50
- }
51
- if (httpStatus >= 500) {
52
- return { status: "unreachable", reason: `Upstream answered ${httpStatus}` };
53
- }
54
- // Any other 4xx: the request reached the upstream's own validation, which
55
- // only happens once the address is accepted.
56
- return {
57
- status: "allowed",
58
- reason: `Reachable; upstream answered ${httpStatus} for the probe body`,
59
- };
60
- }
61
-
62
- /**
63
- * Error codes that mean "this attempt never reached the upstream", not "the
64
- * upstream refused this address".
65
- *
66
- * Every one of them is worth a second attempt, and the reason is the same in
67
- * each case: the failure is a property of one socket, not of the egress. A
68
- * proxy that has just been dialled for the first time has to resolve, open a
69
- * tunnel, and complete a TLS handshake, and any of those steps can be refused
70
- * or reset by a middlebox that accepts the very next attempt — which is exactly
71
- * the "fails once, works on retry" report. Retiring the pool on the first one
72
- * blames the address for a cold-start race.
73
- */
74
- const RETRYABLE_TRANSPORT_CODES = new Set([
75
- "ECONNREFUSED",
76
- "ECONNRESET",
77
- "ECONNABORTED",
78
- "EHOSTUNREACH",
79
- "ENETUNREACH",
80
- "ENETRESET",
81
- "EPIPE",
82
- "ETIMEDOUT",
83
- "EAI_AGAIN",
84
- "UND_ERR_SOCKET",
85
- "UND_ERR_CONNECT_TIMEOUT",
86
- "ECONNECT_TIMEOUT",
87
- ]);
88
- /** How many `cause` links to follow; wraps are shallow in practice. */
89
- const PROBE_ERROR_CAUSE_DEPTH = 5;
90
-
91
- /**
92
- * Whether one probe error should be retried before it becomes a verdict.
93
- *
94
- * Deliberately narrow: an abort is *our* deadline firing, so retrying it would
95
- * spend the whole probe budget on a slow egress, and an unknown error is left
96
- * alone rather than guessed at. Only transport codes that describe a single
97
- * dead connection qualify.
98
- */
99
- export function isRetryableProbeError(error: unknown): boolean {
100
-
101
- // Walk the `cause` chain: a transport failure is routinely wrapped — the
102
- // proxy layer raises `ProxyConnectHandshakeError` whose *message* carries the
103
- // real code, with nothing on `code`. Checking only the top error would miss
104
- // exactly the failure this exists for.
105
- let current: unknown = error;
106
- for (let depth = 0; depth < PROBE_ERROR_CAUSE_DEPTH && current !== undefined && current !== null; depth += 1) {
107
- if (!(current instanceof Error)) break;
108
- if (current.name === "AbortError" || current.name === "TimeoutError") return false;
109
- // `code` is not on the base `Error`; narrow before reading it rather than
110
- // asserting a shape the runtime may not have.
111
- const code: string | undefined =
112
- "code" in current && typeof current.code === "string" ? current.code : undefined;
113
- if (code !== undefined && RETRYABLE_TRANSPORT_CODES.has(code.toUpperCase())) return true;
114
- const message = String(current.message ?? "");
115
- // The wrapped code is often only in the message, so match it there too.
116
- for (const candidate of RETRYABLE_TRANSPORT_CODES) {
117
- if (message.includes(candidate)) return true;
118
- }
119
- if (
120
- message.includes("fetch failed") ||
121
- message.includes("socket connection was closed unexpectedly") ||
122
- message.includes("other side closed") ||
123
- message.startsWith("terminated") ||
124
- /connection (?:was|is) closed/i.test(message)
125
- ) {
126
- return true;
127
- }
128
- current = current.cause;
129
- }
130
- return false;
131
- }
132
-
1
+ /**
2
+ * What one provider-probe answer means about the egress.
3
+ *
4
+ * Extracted from the store so the console, the CLI probe
5
+ * (`scripts/tools/probe-egress.ts`) and the tests cannot drift into three
6
+ * readings of the same response. The rule that matters is not obvious:
7
+ *
8
+ * **The provider's IP gate runs before it validates the body.** So a `4xx`
9
+ * complaint about the payload proves the address was accepted — it is the
10
+ * probe's request that is wrong, not the proxy. Mapping that to `unreachable`
11
+ * ("the probe could not complete") blamed the proxy for our own defect and hid
12
+ * working egresses behind a verdict that said the pool was unusable.
13
+ *
14
+ * Only the free-tier refusal retires an address. Every other answer either says
15
+ * the address works (`allowed`, `throttled`) or says nothing about it
16
+ * (`unreachable`), and treating "says nothing" as "refused" would drop a usable
17
+ * pool for good.
18
+ */
19
+
20
+ export type ProbeVerdict = "allowed" | "blocked" | "throttled" | "unreachable";
21
+
22
+ export interface ProbeAnswer {
23
+ readonly status: ProbeVerdict;
24
+ /** Operator-facing reason; absent when the answer needs none. */
25
+ readonly reason?: string;
26
+ }
27
+
28
+ /** The upstream's own wording for "this address is retired". */
29
+ const FREE_TIER_REFUSAL = /freetier|free tier can only be used/i;
30
+
31
+ /**
32
+ * Classifies one upstream response.
33
+ *
34
+ * `407` is separated from the generic 4xx branch on purpose: it comes from the
35
+ * proxy front door, never from the provider, so it is a credential problem on
36
+ * the tunnel and says nothing about whether the address is refused.
37
+ */
38
+ export function classifyProviderProbeAnswer(httpStatus: number, body: string): ProbeAnswer {
39
+ if (httpStatus === 403 && FREE_TIER_REFUSAL.test(body)) {
40
+ return { status: "blocked", reason: "Upstream refused this egress address" };
41
+ }
42
+ if (httpStatus === 407) {
43
+ return { status: "unreachable", reason: "Proxy requires authentication" };
44
+ }
45
+ if (httpStatus === 429) {
46
+ return { status: "throttled", reason: "Address accepted; rate limit spent" };
47
+ }
48
+ if (httpStatus >= 200 && httpStatus < 300) {
49
+ return { status: "allowed" };
50
+ }
51
+ if (httpStatus >= 500) {
52
+ return { status: "unreachable", reason: `Upstream answered ${httpStatus}` };
53
+ }
54
+ // Any other 4xx: the request reached the upstream's own validation, which
55
+ // only happens once the address is accepted.
56
+ return {
57
+ status: "allowed",
58
+ reason: `Reachable; upstream answered ${httpStatus} for the probe body`,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Error codes that mean "this attempt never reached the upstream", not "the
64
+ * upstream refused this address".
65
+ *
66
+ * Every one of them is worth a second attempt, and the reason is the same in
67
+ * each case: the failure is a property of one socket, not of the egress. A
68
+ * proxy that has just been dialled for the first time has to resolve, open a
69
+ * tunnel, and complete a TLS handshake, and any of those steps can be refused
70
+ * or reset by a middlebox that accepts the very next attempt — which is exactly
71
+ * the "fails once, works on retry" report. Retiring the pool on the first one
72
+ * blames the address for a cold-start race.
73
+ */
74
+ const RETRYABLE_TRANSPORT_CODES = new Set([
75
+ "ECONNREFUSED",
76
+ "ECONNRESET",
77
+ "ECONNABORTED",
78
+ "EHOSTUNREACH",
79
+ "ENETUNREACH",
80
+ "ENETRESET",
81
+ "EPIPE",
82
+ "ETIMEDOUT",
83
+ "EAI_AGAIN",
84
+ "UND_ERR_SOCKET",
85
+ "UND_ERR_CONNECT_TIMEOUT",
86
+ "ECONNECT_TIMEOUT",
87
+ ]);
88
+ /** How many `cause` links to follow; wraps are shallow in practice. */
89
+ const PROBE_ERROR_CAUSE_DEPTH = 5;
90
+
91
+ /**
92
+ * Whether one probe error should be retried before it becomes a verdict.
93
+ *
94
+ * Deliberately narrow: an abort is *our* deadline firing, so retrying it would
95
+ * spend the whole probe budget on a slow egress, and an unknown error is left
96
+ * alone rather than guessed at. Only transport codes that describe a single
97
+ * dead connection qualify.
98
+ */
99
+ export function isRetryableProbeError(error: unknown): boolean {
100
+
101
+ // Walk the `cause` chain: a transport failure is routinely wrapped — the
102
+ // proxy layer raises `ProxyConnectHandshakeError` whose *message* carries the
103
+ // real code, with nothing on `code`. Checking only the top error would miss
104
+ // exactly the failure this exists for.
105
+ let current: unknown = error;
106
+ for (let depth = 0; depth < PROBE_ERROR_CAUSE_DEPTH && current !== undefined && current !== null; depth += 1) {
107
+ if (!(current instanceof Error)) break;
108
+ if (current.name === "AbortError" || current.name === "TimeoutError") return false;
109
+ // `code` is not on the base `Error`; narrow before reading it rather than
110
+ // asserting a shape the runtime may not have.
111
+ const code: string | undefined =
112
+ "code" in current && typeof current.code === "string" ? current.code : undefined;
113
+ if (code !== undefined && RETRYABLE_TRANSPORT_CODES.has(code.toUpperCase())) return true;
114
+ const message = String(current.message ?? "");
115
+ // The wrapped code is often only in the message, so match it there too.
116
+ for (const candidate of RETRYABLE_TRANSPORT_CODES) {
117
+ if (message.includes(candidate)) return true;
118
+ }
119
+ if (
120
+ message.includes("fetch failed") ||
121
+ message.includes("socket connection was closed unexpectedly") ||
122
+ message.includes("other side closed") ||
123
+ message.startsWith("terminated") ||
124
+ /connection (?:was|is) closed/i.test(message)
125
+ ) {
126
+ return true;
127
+ }
128
+ current = current.cause;
129
+ }
130
+ return false;
131
+ }
132
+
@@ -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
+ }