cartethyia 2.7.8 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/.env.example +3 -3
  2. package/README.md +29 -59
  3. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js +1 -0
  4. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-CXsaOAO2.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
  13. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js +1 -0
  14. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
  17. package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-CgoPc5B9.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-D9bFQern.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BwNC_-Cx.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-CctTga2C.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-febLU4r1.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-M6fvR5Wz.js} +1 -1
  40. package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-BFOlvrJC.js +1 -0
  44. package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
  45. package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
  46. package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-CidQVoNo.js → assert-CqTLw5e7.js} +1 -1
  48. package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-C83EqLB0.js} +1 -1
  50. package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
  51. package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
  52. package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
  53. package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-D1xn7pIe.js} +1 -1
  54. package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
  55. package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
  56. package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
  57. package/dist/dashboard/assets/brand/favicon.webp +0 -0
  58. package/dist/dashboard/assets/brand/login_register.webp +0 -0
  59. package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
  60. package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
  61. package/dist/dashboard/assets/brand/og_image.webp +0 -0
  62. package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
  63. package/dist/dashboard/assets/{card-_HwAEqFF.js → card-DU3Lq5Lm.js} +1 -1
  64. package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
  65. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js +1 -0
  66. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
  67. package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CEL5Sg_J.js} +1 -1
  68. package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
  69. package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-BjFR5UHy.js} +1 -1
  70. package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
  71. package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-HvBW0AjI.js} +1 -1
  72. package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
  74. package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
  75. package/dist/dashboard/assets/{entry-COaC-irN.js → entry-CECVELyy.js} +1 -1
  76. package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
  77. package/dist/dashboard/assets/entry-DYYofQ7W.js +1 -0
  78. package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
  79. package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-DLW4i8zK.js} +1 -1
  80. package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
  81. package/dist/dashboard/assets/{index-CgKmFngv.js → index-1-fCqe64.js} +2 -2
  82. package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
  83. package/dist/dashboard/assets/{inline-B2BirKry.js → inline-DGvU2oLK.js} +1 -1
  84. package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
  85. package/dist/dashboard/assets/{input-D-qV9dKY.js → input-iNnO1X2K.js} +1 -1
  86. package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
  87. package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-0q8UGypb.js} +1 -1
  88. package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
  89. package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-C0w2bQjJ.js} +1 -1
  90. package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
  91. package/dist/dashboard/assets/{live-DGizF06-.js → live-M5aJ_xCa.js} +1 -1
  92. package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
  93. package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
  94. package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
  95. package/dist/dashboard/assets/{network-C9UE-bOr.js → network-BKeQGT3K.js} +1 -1
  96. package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
  97. package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-CivAk3da.js} +1 -1
  98. package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
  99. package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-CpzvI5ep.js} +1 -1
  100. package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
  101. package/dist/dashboard/assets/providers/devin.svg +9 -0
  102. package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
  103. package/dist/dashboard/assets/providers/windsurf.svg +10 -0
  104. package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
  105. package/dist/dashboard/assets/providers/zro.svg +3 -0
  106. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  107. package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
  108. package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
  109. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  110. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  111. package/dist/dashboard/assets/routing-BFQX2VBc.js +1 -0
  112. package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
  113. package/dist/dashboard/assets/{select-BCOcv8WK.js → select-CXbck0jG.js} +2 -2
  114. package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
  115. package/dist/dashboard/assets/settings-C_XvMHh9.js +1 -0
  116. package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
  117. package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DggRiCxE.js} +1 -1
  118. package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
  119. package/dist/dashboard/assets/{state-DXbGTx7N.js → state-ClFzfbYp.js} +1 -1
  120. package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
  121. package/dist/dashboard/assets/story/25817331.webp +0 -0
  122. package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
  123. package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
  124. package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
  125. package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
  126. package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
  127. package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
  128. package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
  129. package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-CTIFxKqS.js} +1 -1
  130. package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
  131. package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BClG3yWj.js} +1 -1
  132. package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
  133. package/dist/dashboard/console-backgrounds.webp +0 -0
  134. package/dist/dashboard/console-backgrounds_initial.webp +0 -0
  135. package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
  136. package/dist/dashboard/index.html +3 -3
  137. package/dist/dashboard/index.html.gz +0 -0
  138. package/dist/dashboard/precompressed-assets.json +59 -56
  139. package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
  140. package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  141. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  142. package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
  143. package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  144. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  145. package/package.json +1 -1
  146. package/src/boot/app.ts +10 -13
  147. package/src/boot/dependencies.ts +12 -4
  148. package/src/boot/flush.ts +20 -0
  149. package/src/boot/lifecycle.ts +7 -5
  150. package/src/config/cooldowns.ts +1 -1
  151. package/src/config/routing.ts +26 -0
  152. package/src/config/spec-metadata.ts +72 -0
  153. package/src/config/spec.ts +20 -6
  154. package/src/console/api-keys/contracts.ts +12 -1
  155. package/src/console/api-keys/routes.ts +50 -31
  156. package/src/console/auth/account-reset.ts +7 -9
  157. package/src/console/backup/contracts.ts +4 -0
  158. package/src/console/backup/export-endpoint.ts +7 -5
  159. package/src/console/cli-tools/service.ts +6 -12
  160. package/src/console/console-router-guards.ts +23 -16
  161. package/src/console/console-router.ts +7 -0
  162. package/src/console/dashboard-assets.ts +5 -5
  163. package/src/console/domain-registration.ts +27 -2
  164. package/src/console/inferhub/normalize.ts +24 -28
  165. package/src/console/inferhub/types.ts +3 -0
  166. package/src/console/ip-bans/routes.ts +93 -0
  167. package/src/console/observability/store.ts +0 -53
  168. package/src/console/providers/catalog/catalog-projections.ts +5 -2
  169. package/src/console/providers/catalog/contracts.ts +123 -0
  170. package/src/console/providers/catalog/discover-constants.ts +6 -0
  171. package/src/console/providers/catalog/model-operations.ts +122 -10
  172. package/src/console/providers/catalog/provider-operations.ts +116 -8
  173. package/src/console/providers/catalog/public-model-store.ts +0 -0
  174. package/src/console/providers/catalog/routes.ts +65 -4
  175. package/src/console/providers/catalog/store-helpers.ts +30 -1
  176. package/src/console/providers/catalog/store.ts +249 -4
  177. package/src/console/quota/account-quota-global.ts +97 -19
  178. package/src/console/quota/account-quota-shared.ts +49 -0
  179. package/src/console/quota/account-quota-tenant.ts +6 -18
  180. package/src/console/quota/native-identity.ts +2 -0
  181. package/src/console/quota/quota-view.ts +19 -3
  182. package/src/console/routing/pools/operations.ts +31 -46
  183. package/src/console/settings/contracts.ts +18 -0
  184. package/src/console/settings/store.ts +32 -0
  185. package/src/console/shared/errors.ts +33 -6
  186. package/src/console/system-prompt/contracts.ts +0 -1
  187. package/src/main.ts +3 -3
  188. package/src/network/pool/byte-accounting.ts +3 -2
  189. package/src/network/pool/selector.ts +182 -39
  190. package/src/network/pool-cooldown-store.ts +89 -14
  191. package/src/network/pool-health.ts +49 -61
  192. package/src/network/rate-limit-verdict.ts +1 -1
  193. package/src/network/ssrf.ts +6 -6
  194. package/src/observability/event-log.ts +7 -361
  195. package/src/observability/event-query.ts +18 -4
  196. package/src/observability/performance-metrics.ts +9 -0
  197. package/src/observability/runtime-metrics.ts +14 -9
  198. package/src/observability/telemetry-buffer.ts +15 -25
  199. package/src/observability/telemetry-reconciliation.ts +9 -37
  200. package/src/observability/telemetry-row.ts +125 -0
  201. package/src/persistence/db-mode.ts +4 -1
  202. package/src/persistence/dialect.ts +30 -0
  203. package/src/persistence/migrate.ts +6 -3
  204. package/src/persistence/schema.ts +28 -2
  205. package/src/persistence/ttl-cache.ts +20 -2
  206. package/src/protocol/codecs.ts +1 -1
  207. package/src/protocol/dsml.ts +35 -87
  208. package/src/protocol/gateway-error.ts +1 -0
  209. package/src/protocol/markup-stream-parser.ts +195 -0
  210. package/src/protocol/reasoning-text.ts +19 -0
  211. package/src/protocol/reasoning.ts +1 -1
  212. package/src/protocol/request/chat.ts +14 -14
  213. package/src/protocol/request/gemini.ts +8 -2
  214. package/src/protocol/request/messages.ts +4 -5
  215. package/src/protocol/response/chat.ts +12 -20
  216. package/src/protocol/response/gemini.ts +6 -1
  217. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  218. package/src/protocol/response/responses.ts +18 -2
  219. package/src/protocol/sse-decoder.ts +25 -8
  220. package/src/protocol/stop-reason.ts +23 -1
  221. package/src/protocol/stream-error-frames.ts +13 -3
  222. package/src/protocol/think-split.ts +112 -0
  223. package/src/protocol/translation/dialect/qwen.ts +26 -58
  224. package/src/protocol/translation/quirks.ts +1 -1
  225. package/src/protocol/translation/tool-repair.ts +2 -2
  226. package/src/protocol/upstream/send-claude-messages.ts +15 -4
  227. package/src/protocol/upstream-parsing.ts +1 -1
  228. package/src/providers/adapters/compatible-adapter.ts +78 -17
  229. package/src/providers/authentication/oauth-client.ts +1 -1
  230. package/src/providers/authentication/oauth-device-flow.ts +98 -8
  231. package/src/providers/authentication/oauth-refresh-service.ts +153 -59
  232. package/src/providers/configured-provider.ts +148 -0
  233. package/src/providers/default-registry.ts +117 -27
  234. package/src/providers/discovery/discovery-types.ts +12 -0
  235. package/src/providers/discovery/probe-phases.ts +74 -33
  236. package/src/providers/discovery/probing-service.ts +293 -165
  237. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  238. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  239. package/src/providers/integrations/antigravity/antigravity-protocol.ts +121 -2
  240. package/src/providers/integrations/antigravity/antigravity.ts +13 -2
  241. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
  242. package/src/providers/integrations/buddy/buddy-chat-shared.ts +191 -35
  243. package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
  244. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
  245. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
  246. package/src/providers/integrations/buddy/buddy-identity.ts +89 -6
  247. package/src/providers/integrations/buddy/buddy-media.ts +4 -5
  248. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
  249. package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
  250. package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
  251. package/src/providers/integrations/buddy/capabilities.ts +6 -43
  252. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
  253. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +23 -1
  254. package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
  255. package/src/providers/integrations/buddy/codebuddy/shared.ts +38 -27
  256. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  257. package/src/providers/integrations/cline/cline.ts +2 -2
  258. package/src/providers/integrations/commandcode/quota.ts +22 -28
  259. package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
  260. package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
  261. package/src/providers/integrations/cursor/cursor-exec.ts +745 -0
  262. package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
  263. package/src/providers/integrations/cursor/cursor-request.ts +35 -20
  264. package/src/providers/integrations/cursor/cursor-shared.ts +26 -5
  265. package/src/providers/integrations/cursor/cursor.ts +139 -70
  266. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  267. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  268. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
  269. package/src/providers/integrations/devin/devin-auth.ts +353 -0
  270. package/src/providers/integrations/devin/devin-catalog.ts +207 -0
  271. package/src/providers/integrations/devin/devin-http.ts +91 -0
  272. package/src/providers/integrations/devin/devin-quota.ts +214 -0
  273. package/src/providers/integrations/devin/devin-shared.ts +111 -0
  274. package/src/providers/integrations/devin/devin.ts +539 -0
  275. package/src/providers/integrations/devin/protocol/devin-proto.ts +558 -0
  276. package/src/providers/integrations/devin/protocol/framing.ts +124 -0
  277. package/src/providers/integrations/factory/factory-oauth.ts +17 -44
  278. package/src/providers/integrations/factory/factory.ts +2 -2
  279. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  280. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  281. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  282. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  283. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  284. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  285. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  286. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  287. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  288. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  289. package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
  290. package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
  291. package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
  292. package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
  293. package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
  294. package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
  295. package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
  296. package/src/providers/integrations/freepi/freepi.ts +292 -0
  297. package/src/providers/integrations/gemini.ts +2 -2
  298. package/src/providers/integrations/grok/grok-oauth.ts +48 -45
  299. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  300. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  301. package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
  302. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +13 -21
  303. package/src/providers/integrations/hermes/index.ts +2 -0
  304. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  305. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  306. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  307. package/src/providers/integrations/kiro/kiro.ts +15 -19
  308. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  309. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  310. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  311. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  312. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  313. package/src/providers/integrations/perch/perch-catalog.ts +105 -0
  314. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  315. package/src/providers/integrations/perch/perch-oauth.ts +170 -0
  316. package/src/providers/integrations/perch/perch-protocol.ts +208 -0
  317. package/src/providers/integrations/perch/perch-quota.ts +123 -0
  318. package/src/providers/integrations/perch/perch-realm.ts +82 -0
  319. package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
  320. package/src/providers/integrations/perch/perch.ts +305 -0
  321. package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
  322. package/src/providers/integrations/qoder/chat-proto.ts +328 -328
  323. package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
  324. package/src/providers/integrations/qoder/qoder-adapter.ts +90 -40
  325. package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
  326. package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
  327. package/src/providers/integrations/qoder/qoder-catalog.ts +26 -6
  328. package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
  329. package/src/providers/integrations/qoder/qoder-checkin.ts +6 -6
  330. package/src/providers/integrations/qoder/qoder-grpc.ts +210 -7
  331. package/src/providers/integrations/qoder/qoder-ide.ts +5 -108
  332. package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
  333. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
  334. package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
  335. package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
  336. package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
  337. package/src/providers/integrations/qoder/qoder.ts +3 -7
  338. package/src/providers/integrations/trae/trae-catalog.ts +9 -14
  339. package/src/providers/integrations/trae/trae-headers.ts +36 -11
  340. package/src/providers/integrations/trae/trae-oauth.ts +14 -17
  341. package/src/providers/integrations/trae/trae-protocol.ts +12 -11
  342. package/src/providers/integrations/trae/trae-quota.ts +96 -36
  343. package/src/providers/integrations/trae/trae-realm.ts +74 -15
  344. package/src/providers/integrations/trae/trae.ts +99 -26
  345. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  346. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  347. package/src/providers/integrations/zro/zro.ts +204 -0
  348. package/src/providers/operations/account-error-classification.ts +235 -278
  349. package/src/providers/operations/account-health-service.ts +273 -26
  350. package/src/providers/operations/account-refresh-status.ts +269 -0
  351. package/src/providers/operations/account-tier.ts +66 -0
  352. package/src/providers/operations/client-version-resolver.ts +1 -1
  353. package/src/providers/operations/client-versions.ts +42 -150
  354. package/src/providers/operations/cooldown-rules.ts +56 -0
  355. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  356. package/src/providers/operations/provider-catalog-seeder.ts +4 -0
  357. package/src/providers/operations/provider-catalog-service.ts +1 -1
  358. package/src/providers/operations/provider-credential-service.ts +102 -32
  359. package/src/providers/operations/provider-rules/github.ts +48 -0
  360. package/src/providers/operations/provider-rules/grok.ts +33 -0
  361. package/src/providers/operations/provider-rules/inception.ts +49 -0
  362. package/src/providers/operations/provider-rules/index.ts +32 -0
  363. package/src/providers/operations/provider-rules/perch.ts +37 -0
  364. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  365. package/src/providers/operations/provider-rules/trae.ts +81 -0
  366. package/src/providers/provider-contracts.ts +33 -2
  367. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  368. package/src/providers/provider-metadata.ts +99 -14
  369. package/src/providers/provider-model-definition.ts +36 -0
  370. package/src/providers/provider-probe.ts +15 -1
  371. package/src/providers/provider-registry.ts +42 -33
  372. package/src/providers/quota/quota-limit-windows.ts +16 -5
  373. package/src/providers/quota/quota-support.ts +1 -1
  374. package/src/providers/quota/quota-window-parser.ts +90 -18
  375. package/src/providers/quota/refresh.ts +96 -18
  376. package/src/security/abuse.ts +71 -0
  377. package/src/security/api-key-auth-cache.ts +1 -1
  378. package/src/security/api-key-auth.ts +6 -2
  379. package/src/transport/dispatch/attempt-finalize.ts +14 -13
  380. package/src/transport/dispatch/attempt-loop.ts +71 -17
  381. package/src/transport/dispatch/generation.ts +2 -8
  382. package/src/transport/dispatch/native-shared.ts +25 -0
  383. package/src/transport/dispatch/proxy-request.ts +9 -46
  384. package/src/transport/dispatch/responses-compact.ts +2 -8
  385. package/src/transport/dispatch/streaming/finalize.ts +9 -6
  386. package/src/transport/dispatch/streaming/stream.ts +9 -36
  387. package/src/transport/dispatch/systemone.ts +2 -8
  388. package/src/transport/dispatch/token-count.ts +2 -6
  389. package/src/transport/dispatch/upstream.ts +67 -1
  390. package/src/transport/drain-endpoint.ts +31 -4
  391. package/src/transport/failure-policy.ts +129 -37
  392. package/src/transport/guards/body-policy.ts +7 -1
  393. package/src/transport/guards/error-lifecycle.ts +13 -1
  394. package/src/transport/guards/request-context.ts +22 -14
  395. package/src/transport/request/preparer.ts +81 -71
  396. package/src/transport/routing/bounded-lru.ts +14 -0
  397. package/src/transport/routing/drain-reserve.ts +129 -0
  398. package/src/transport/routing/plan-gate.ts +7 -1
  399. package/src/transport/routing/round-robin.ts +60 -0
  400. package/src/transport/routing/route-catalog.ts +123 -111
  401. package/src/transport/routing/route-model.ts +51 -9
  402. package/src/transport/routing/router.ts +418 -33
  403. package/src/transport/surface/adapters.ts +11 -2
  404. package/src/transport/surface/chat/adapter.ts +2 -2
  405. package/src/transport/surface/chat/encode.ts +47 -4
  406. package/src/transport/surface/chat/parse.ts +2 -12
  407. package/src/transport/surface/completion.ts +44 -33
  408. package/src/transport/surface/content-parts.ts +21 -0
  409. package/src/transport/surface/messages/adapter.ts +9 -2
  410. package/src/transport/surface/messages/encode.ts +11 -3
  411. package/src/transport/surface/messages/parse.ts +2 -1
  412. package/src/transport/surface/messages/stream.ts +33 -17
  413. package/src/transport/surface/responses/encode.ts +34 -6
  414. package/src/transport/surface/responses/parse.ts +3 -11
  415. package/src/workers/daily-checkin.ts +1 -1
  416. package/src/workers/quota-refresh-sweep.ts +1 -1
  417. package/src/workers/scheduler.ts +22 -4
  418. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
  419. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
  420. package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
  421. package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
  422. package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
  423. package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
  424. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
  425. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
  426. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
  427. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
  428. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
  429. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
  430. package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
  431. package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
  432. package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
  433. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
  434. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
  435. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
  436. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
  437. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
  438. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
  439. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
  440. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
  441. package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
  442. package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js +0 -1
  443. package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
  444. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
  445. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
  446. package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
  447. package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
  448. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
  449. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
  450. package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
  451. package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
  452. package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
  453. package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
  454. package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
  455. package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
  456. package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
  457. package/dist/dashboard/assets/brand/bansos_page.png +0 -0
  458. package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
  459. package/dist/dashboard/assets/brand/favicon.jpg +0 -0
  460. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  461. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  462. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  463. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  464. package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
  465. package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
  466. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
  467. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
  468. package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
  469. package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
  470. package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
  471. package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
  472. package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
  473. package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
  474. package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
  475. package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
  476. package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
  477. package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
  478. package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
  479. package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
  480. package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
  481. package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
  482. package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
  483. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
  484. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  485. package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
  486. package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
  487. package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
  488. package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
  489. package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
  490. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  491. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  492. package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
  493. package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
  494. package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
  495. package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
  496. package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
  497. package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
  498. package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
  499. package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
  500. package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
  501. package/dist/dashboard/console-backgrounds.jpg +0 -0
  502. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  503. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  504. package/src/providers/bundled-discovery-providers.ts +0 -98
  505. package/src/providers/configured-openai-providers.ts +0 -120
  506. package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
  507. package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
  508. package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
  509. package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
  510. package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
  511. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
  512. package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
  513. package/src/providers/operations/model-discovery-cache.ts +0 -34
  514. package/src/providers/operations/probe-fetch.ts +0 -15
  515. package/src/providers/operations/provider-version-cache.ts +0 -30
  516. package/src/providers/provider-capabilities.ts +0 -33
  517. package/src/providers/provider-credential-envelope.ts +0 -43
  518. /package/dist/dashboard/assets/{app-CTxoqyYB.js → app-DIXLjojg.js} +0 -0
  519. /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-DIXLjojg.js.gz} +0 -0
@@ -11,6 +11,27 @@ import { GatewayError } from "../protocol/gateway-error";
11
11
  import { fastPathname } from "../transport/pathname";
12
12
  import { parseCookieValue, SESSION_COOKIE_NAME, isCsrfValid } from "../security/csrf";
13
13
 
14
+ /**
15
+ * Console auth routes are excluded from CSRF and mutation-rate limiting: they
16
+ * run before a session exists or are the very requests that mint the session,
17
+ * so they must not require a CSRF token or count against the session's budget.
18
+ */
19
+ const AUTH_ROUTE_SUFFIXES = ["/auth/login", "/auth/setup", "/auth/setup/restore", "/auth/session"] as const;
20
+
21
+ /**
22
+ * Whether `request` is a protected console mutation: a `/console/api/*` route
23
+ * in an unsafe method, excluding the auth-route suffixes. Shared by the CSRF
24
+ * and mutation-limiter guards so a change to the protected set lands in both.
25
+ */
26
+ function isProtectedConsoleMutation(request: Request): boolean {
27
+ const path = fastPathname(request.url);
28
+ return (
29
+ path.startsWith("/console/api/") &&
30
+ ["POST", "PUT", "PATCH", "DELETE"].includes(request.method) &&
31
+ !AUTH_ROUTE_SUFFIXES.some((suffix) => path.endsWith(suffix))
32
+ );
33
+ }
34
+
14
35
  /**
15
36
  * Stateless double-submit CSRF guard for unsafe `/console/api/*` mutations.
16
37
  * Compares the `x-csrf-token` header against the readable `csrf_token`
@@ -19,16 +40,9 @@ import { parseCookieValue, SESSION_COOKIE_NAME, isCsrfValid } from "../security/
19
40
  * store whose outage could masquerade as an auth failure.
20
41
  */
21
42
  export function createConsoleCsrfMiddleware(): Elysia {
22
- const excluded = ["/auth/login", "/auth/setup", "/auth/setup/restore", "/auth/session"];
23
43
  return new Elysia()
24
44
  .beforeHandle(async ({ request }) => {
25
- const path = fastPathname(request.url);
26
- if (
27
- !path.startsWith("/console/api/") ||
28
- !["POST", "PUT", "PATCH", "DELETE"].includes(request.method) ||
29
- excluded.some((suffix) => path.endsWith(suffix))
30
- )
31
- return;
45
+ if (!isProtectedConsoleMutation(request)) return;
32
46
  const token = parseCookieValue(request, SESSION_COOKIE_NAME);
33
47
  // No session cookie means no ambient authority to forge: logout and
34
48
  // other mutations without a session fail authentication downstream.
@@ -75,16 +89,9 @@ function pruneConsoleMutationHits(now: number): void {
75
89
  }
76
90
 
77
91
  export function createConsoleMutationLimiterMiddleware(): Elysia {
78
- const excluded = ["/auth/login", "/auth/setup", "/auth/setup/restore", "/auth/session"];
79
92
  return new Elysia()
80
93
  .beforeHandle(async ({ request, set }) => {
81
- const path = fastPathname(request.url);
82
- if (
83
- !path.startsWith("/console/api/") ||
84
- !["POST", "PUT", "PATCH", "DELETE"].includes(request.method) ||
85
- excluded.some((suffix) => path.endsWith(suffix))
86
- )
87
- return;
94
+ if (!isProtectedConsoleMutation(request)) return;
88
95
  const key = consoleMutationKey(request);
89
96
  // No session cookie means no ambient authority to abuse: the request
90
97
  // fails authentication downstream. Only tracked sessions are throttled.
@@ -35,12 +35,18 @@ import type { ProviderRegistry } from "../providers/provider-registry";
35
35
  import type { BundledProviderCatalog } from "../providers/operations/provider-catalog-service";
36
36
  import type { ValidatedNetworkBindingFactory } from "../network/pool/resolver";
37
37
  import type { TrustedProxyBoundary } from "../config/network";
38
+ import type { IpAbuseStore } from "../security/abuse";
38
39
  import { gzipResponse } from "./response-compression";
39
40
  import { beginConsoleActivity } from "../shared/invoke-guard";
40
41
 
41
42
  export interface ConsoleApiCompositionDeps {
42
43
  readonly db: CartethyiaDatabase;
43
44
  readonly accessResolver: ConsoleAccessResolver;
45
+ /**
46
+ * The IP-abuse store backing manual IP bans and the live ban list. Absent
47
+ * (reduced compositions) means the IP-ban routes are not offered.
48
+ */
49
+ readonly ipAbuseStore?: IpAbuseStore;
44
50
  /**
45
51
  * Drops the cached route snapshot after a console write changes the catalog.
46
52
  * Structurally satisfied by `RouteSnapshotService`; declared here so the
@@ -222,6 +228,7 @@ export function createConsoleRouter(deps: ConsoleApiCompositionDeps): AnyElysia
222
228
  telemetryBuffer,
223
229
  ...(deps.stateStore ? { stateStore: deps.stateStore } : {}),
224
230
  ...(deps.shareStore ? { shareStore: deps.shareStore } : {}),
231
+ ...(deps.ipAbuseStore ? { ipAbuseStore: deps.ipAbuseStore } : {}),
225
232
  providerRegistry: deps.providerRegistry,
226
233
  bundledModelCatalog: deps.bundledModelCatalog,
227
234
  networkBindingFactory: deps.networkBindingFactory,
@@ -62,13 +62,13 @@ export const SHARE_SOCIAL_META: SocialMeta = {
62
62
  title: "Cartethyia — Bansos Token",
63
63
  description:
64
64
  "Come and save your tokens 💖 Shared Cartethyia API key — Bansos Token with live quota, usage and model allowlist.",
65
- image: "/assets/brand/og_bansos.jpg",
66
- imageType: "image/jpeg",
65
+ image: "/assets/brand/og_bansos.webp",
66
+ imageType: "image/webp",
67
67
  // Read from the file's own header, not carried over from a previous image:
68
68
  // og:image:width/height are what a crawler uses to allocate the card before
69
69
  // the bytes arrive, so a copied size makes every preview crop.
70
- imageWidth: 1760,
71
- imageHeight: 576,
70
+ imageWidth: 1200,
71
+ imageHeight: 393,
72
72
  imageAlt: "Cartethyia Bansos Token — Come and save your tokens",
73
73
  };
74
74
 
@@ -405,7 +405,7 @@ function getMimeType(filePath: string): string {
405
405
  * and the modern Vite spelling `app-BCnqn3N3.js` — plus `_`, and then required
406
406
  * to *look generated*: the segment must contain a digit or mix upper and lower
407
407
  * case. The second half is what keeps a plain word out of the set, since
408
- * `console-backgrounds.jpg` and `openai-light.svg` both match the separator
408
+ * `console-backgrounds.webp` and `openai-light.svg` both match the separator
409
409
  * alone and are emphatically not immutable.
410
410
  *
411
411
  * Getting this wrong in either direction costs real money: a hashed asset
@@ -36,7 +36,8 @@ import { createLiveRoutes } from "./observability/live";
36
36
  import { createLogRoutes } from "./observability/logs";
37
37
  import { createAuditRoutes } from "./audit/routes";
38
38
  import { DrizzleAuditReadStore } from "./audit/store";
39
- import { createPerformanceRoutes } from "./observability/performance";
39
+ import { createIpBanRoutes } from "./ip-bans/routes";
40
+ import type { IpAbuseStore } from "../security/abuse";
40
41
  import { createUpdateRoutes } from "./updates/routes";
41
42
  import { createRemoteSupportRoutes } from "./remote-support/routes";
42
43
  import { createRemoteSupportService } from "./remote-support/service";
@@ -55,6 +56,7 @@ import {
55
56
  createAccountExportCredentialsResolver,
56
57
  createAccountSecretResolver,
57
58
  } from "../providers/operations/provider-credential-service";
59
+ import { refreshAccountStatus } from "../providers/operations/account-refresh-status";
58
60
  import { syncByokProvider } from "../providers/operations/provider-catalog-service";
59
61
  import { DrizzleApiKeyStore } from "../persistence/stores/api-key-store";
60
62
  import {
@@ -82,6 +84,11 @@ import type { ConsoleAccessResolver } from "./auth/access";
82
84
  export interface ConsoleDomainContext {
83
85
  readonly db: CartethyiaDatabase;
84
86
  readonly accessResolver: ConsoleAccessResolver;
87
+ /**
88
+ * The IP-abuse store backing manual IP bans and the live ban list. Absent
89
+ * (reduced compositions) means the IP-ban routes are not offered.
90
+ */
91
+ readonly ipAbuseStore?: IpAbuseStore;
85
92
  readonly providerRegistry: ProviderRegistry;
86
93
  readonly bundledModelCatalog: BundledProviderCatalog;
87
94
  readonly networkBindingFactory: ValidatedNetworkBindingFactory;
@@ -159,6 +166,17 @@ export function registerConsoleDomains(
159
166
  resolveRefresher: (id) => ctx.providerRegistry.resolveRefresher(id),
160
167
  refreshService: ctx.oauthRefreshService,
161
168
  });
169
+ /** Manual OAuth token refresh for the catalog route (area 04). */
170
+ const refreshAccountStatusForConsole = (accountId: string, opts?: { readonly origin?: "ui" | "sweep" | "dispatch" }) =>
171
+ refreshAccountStatus(
172
+ {
173
+ db: ctx.db,
174
+ refreshService: ctx.oauthRefreshService,
175
+ resolveRefresher: (id) => ctx.providerRegistry.resolveRefresher(id),
176
+ },
177
+ accountId,
178
+ opts,
179
+ );
162
180
  const providerCatalogStore = new DrizzleProviderCatalogStore(ctx.db, {
163
181
  telemetryBuffer: ctx.telemetryBuffer,
164
182
  bundledModelCatalog: ctx.bundledModelCatalog,
@@ -238,6 +256,7 @@ export function registerConsoleDomains(
238
256
  };
239
257
  },
240
258
  snapshotInvalidator: ctx.routeSnapshotService,
259
+ redis: ctx.redis,
241
260
  readAccountInflight: async (providerId, tenantId) =>
242
261
  ctx.readRoutingAccountInflight ? ctx.readRoutingAccountInflight(providerId, tenantId) : [],
243
262
  });
@@ -258,7 +277,6 @@ export function registerConsoleDomains(
258
277
  ...(ctx.stateStore ? { stateStore: ctx.stateStore } : {}),
259
278
  }));
260
279
  console.use(createLogRoutes({ accessResolver: ctx.accessResolver, auditSink: ctx.auditRecorder }));
261
- console.use(createPerformanceRoutes({ accessResolver: ctx.accessResolver }));
262
280
  console.use(createUpdateRoutes({
263
281
  accessResolver: ctx.accessResolver,
264
282
  observabilityStore,
@@ -271,6 +289,12 @@ export function registerConsoleDomains(
271
289
  service: createRemoteSupportService({ port: resolvePort() }),
272
290
  }));
273
291
  console.use(createAuditRoutes({ store: auditReadStore, accessResolver: ctx.accessResolver }));
292
+ if (ctx.ipAbuseStore) {
293
+ console.use(createIpBanRoutes({
294
+ accessResolver: ctx.accessResolver,
295
+ store: ctx.ipAbuseStore,
296
+ }));
297
+ }
274
298
  // InferHub market data. On demand only: a route fetches when its snapshot is
275
299
  // past its freshness window, so a page nobody opens causes no upstream calls.
276
300
  const inferhubKeys = createInferhubKeyStore(ctx.db);
@@ -287,6 +311,7 @@ export function registerConsoleDomains(
287
311
  redis: ctx.redis,
288
312
  snapshotInvalidator: ctx.routeSnapshotService,
289
313
  resolveExportCredentials,
314
+ refreshAccountStatus: refreshAccountStatusForConsole,
290
315
  // Without this, a custom provider only becomes dispatchable after a
291
316
  // restart: boot registers BYOK rows once, and the catalog routes are the
292
317
  syncByokProvider: (providerId) =>
@@ -35,19 +35,12 @@ const FAMILY_STATES: readonly FamilyState[] = [
35
35
  "low_traffic",
36
36
  ];
37
37
 
38
- /** Reads a finite number, or null. Never coerces a missing metric to zero. */
39
- function num(value: unknown): number | null {
40
- if (typeof value === "number" && Number.isFinite(value)) return value;
41
- // Catalog and budgets carry decimal strings; market carries numbers. Both
42
- // are read here so one helper covers the wire's own inconsistency.
43
- if (typeof value === "string" && value.trim().length > 0) {
44
- const parsed = Number(value);
45
- if (Number.isFinite(parsed)) return parsed;
46
- }
47
- return null;
48
- }
49
-
50
- /** Reads a finite number, or null. Never coerces a missing metric to zero. */
38
+ /**
39
+ * Reads a finite number, or null. Never coerces a missing metric to zero.
40
+ *
41
+ * Catalog and budgets carry decimal strings; market carries numbers. Both
42
+ * are read here so one helper covers the wire's own inconsistency.
43
+ */
51
44
  export function numOrNull(value: unknown): number | null {
52
45
  if (typeof value === "number" && Number.isFinite(value)) return value;
53
46
  if (typeof value === "string" && value.trim().length > 0) {
@@ -122,8 +115,8 @@ function pricePointsToTiers(
122
115
  const seen = new Map<number, Tier>();
123
116
  for (const point of points) {
124
117
  if (!Array.isArray(point) || point.length < 2) continue;
125
- const price = num(point[0]);
126
- const avail = num(point[1]);
118
+ const price = numOrNull(point[0]);
119
+ const avail = numOrNull(point[1]);
127
120
  if (price === null || avail === null) continue;
128
121
  const count = Math.max(0, Math.trunc(avail));
129
122
  const key = Math.round(price * 10 ** PRICE_PRECISION) / 10 ** PRICE_PRECISION;
@@ -208,8 +201,8 @@ export function normalizeCatalogModel(
208
201
  const prefix = str(upstream.prefix) ?? "";
209
202
  if (modelSlug === null || prefix.length === 0) return null;
210
203
  const id = `${prefix}/${modelSlug}`;
211
- const officialIn = num(raw.officialIn);
212
- const officialOut = num(raw.officialOut);
204
+ const officialIn = numOrNull(raw.officialIn);
205
+ const officialOut = numOrNull(raw.officialOut);
213
206
  const tiersIn = pricePointsToTiers(raw.pricePointsIn, officialIn);
214
207
  const tiersOut = pricePointsToTiers(raw.pricePointsOut, officialOut);
215
208
  // Live providers is the capacity behind the cheapest tier, not a row count:
@@ -234,10 +227,13 @@ export function normalizeCatalogModel(
234
227
  minAskOut: tiersOut[0]?.price ?? null,
235
228
  maxAskIn: tiersIn.at(-1)?.price ?? null,
236
229
  maxAskOut: tiersOut.at(-1)?.price ?? null,
230
+ // Computed by the route layer before sending; starts null in internal models.
231
+ capacityPriceIn: null,
232
+ capacityPriceOut: null,
237
233
  providersLive,
238
234
  // Not carried by the catalog; merged from `/api/market` by the store.
239
235
  lastRate: null,
240
- cacheHitRate: num(raw.cacheHitRate),
236
+ cacheHitRate: numOrNull(raw.cacheHitRate),
241
237
  health: {
242
238
  successRatePct: null,
243
239
  avgTtftMs: null,
@@ -245,8 +241,8 @@ export function normalizeCatalogModel(
245
241
  familyState: null,
246
242
  },
247
243
  capabilities: {
248
- contextWindow: num(raw.contextWindow),
249
- maxOutputTokens: num(raw.maxOutputTokens),
244
+ contextWindow: numOrNull(raw.contextWindow),
245
+ maxOutputTokens: numOrNull(raw.maxOutputTokens),
250
246
  modalities: stringArray(raw.modalities),
251
247
  outputModality:
252
248
  outputModality === "image" || outputModality === "video" ? outputModality : "text",
@@ -274,13 +270,13 @@ export function mergeMarketPrices(
274
270
  if (entry === undefined) return model;
275
271
  return {
276
272
  ...model,
277
- lastRate: num(entry.lastRate),
273
+ lastRate: numOrNull(entry.lastRate),
278
274
  // Market is the cheaper source for the extremes, but the catalog's tier
279
275
  // grouping is what gives them capacity, so only the nulls are filled.
280
- minAskIn: model.minAskIn ?? num(entry.minAskIn),
281
- minAskOut: model.minAskOut ?? num(entry.minAskOut),
282
- maxAskIn: model.maxAskIn ?? num(entry.maxAskIn),
283
- maxAskOut: model.maxAskOut ?? num(entry.maxAskOut),
276
+ minAskIn: model.minAskIn ?? numOrNull(entry.minAskIn),
277
+ minAskOut: model.minAskOut ?? numOrNull(entry.minAskOut),
278
+ maxAskIn: model.maxAskIn ?? numOrNull(entry.maxAskIn),
279
+ maxAskOut: model.maxAskOut ?? numOrNull(entry.maxAskOut),
284
280
  };
285
281
  });
286
282
  }
@@ -306,9 +302,9 @@ export function mergeModelHealth(
306
302
  ...model,
307
303
  health: {
308
304
  ...model.health,
309
- successRatePct: num(entry.successRatePct),
310
- avgTtftMs: num(entry.avgTtftMs),
311
- avgTps: num(entry.avgTps),
305
+ successRatePct: numOrNull(entry.successRatePct),
306
+ avgTtftMs: numOrNull(entry.avgTtftMs),
307
+ avgTps: numOrNull(entry.avgTps),
312
308
  },
313
309
  };
314
310
  });
@@ -61,6 +61,9 @@ export interface ModelMarket {
61
61
  readonly minAskOut: number | null;
62
62
  readonly maxAskIn: number | null;
63
63
  readonly maxAskOut: number | null;
64
+ /** Capacity-weighted price computed by the route layer; null when no tiers. */
65
+ readonly capacityPriceIn: number | null;
66
+ readonly capacityPriceOut: number | null;
64
67
  /** Zero means no live provider — listed, but never routed to. */
65
68
  readonly providersLive: number;
66
69
  readonly lastRate: number | null;
@@ -0,0 +1,93 @@
1
+ // IP-ban domain: manual IP bans visible in the Banned Users modal.
2
+ //
3
+ // The gateway's abuse store keeps automatic bans (escalation after a burst).
4
+ // This domain exposes the same store to the operator so an address can be
5
+ // banned BY HAND and listed/unbanned from the console. IP bans are instance
6
+ // state, not tenant data, so every route is guarded by `platform:admin` like
7
+ // the other instance-level operations.
8
+
9
+ import { Elysia, t, type AnyElysia } from "elysia";
10
+ import { errorResponse, requireGlobalAdmin } from "../shared/errors";
11
+ import type { IpAbuseStore } from "../../security/abuse";
12
+ import type { ConsoleAccessResolver } from "../auth/access";
13
+ import { BAN_DURATION_CHOICES } from "../api-keys/ban-constants";
14
+
15
+ /** One active IP ban, as the console returns it. */
16
+ export interface IpBanResponse {
17
+ /** The banned address. */
18
+ readonly ip: string;
19
+ /** When the ban lifts, as ISO timestamp; null = permanent. */
20
+ readonly bannedUntil: string | null;
21
+ /** True when the ban was set by hand rather than by abuse escalation. */
22
+ readonly manual: boolean;
23
+ }
24
+
25
+ export interface IpBanRoutesConfig {
26
+ readonly accessResolver: ConsoleAccessResolver;
27
+ readonly store: IpAbuseStore;
28
+ }
29
+
30
+ /** Parses a duration choice into minutes; null = permanent; undefined = invalid. */
31
+ function parseBanDuration(value: unknown): number | null | undefined {
32
+ if (value === null || value === undefined) return value === null ? null : undefined;
33
+ const minutes = Number(value);
34
+ if (!Number.isFinite(minutes) || minutes <= 0) return undefined;
35
+ return Math.round(minutes);
36
+ }
37
+
38
+ export function createIpBanRoutes(config: IpBanRoutesConfig): AnyElysia {
39
+ const { accessResolver, store } = config;
40
+ return new Elysia()
41
+ .get("/ip-bans", async ({ request, set }) => {
42
+ try {
43
+ requireGlobalAdmin(accessResolver(request));
44
+ const bans = await store.listBans();
45
+ return bans.map((ban) => ({
46
+ ip: ban.identity,
47
+ bannedUntil: ban.bannedUntil === null ? null : new Date(ban.bannedUntil).toISOString(),
48
+ manual: true,
49
+ }));
50
+ } catch (e) {
51
+ return errorResponse(e, set, "Could not list IP bans");
52
+ }
53
+ })
54
+ .post(
55
+ "/ip-bans",
56
+ { body: t.Object({ ip: t.String(), minutes: t.Optional(t.Nullable(t.Number())) }) },
57
+ async ({ request, body, set }) => {
58
+ try {
59
+ requireGlobalAdmin(accessResolver(request));
60
+ const ip = body.ip.trim();
61
+ if (ip.length === 0)
62
+ return errorResponse(new Error("ip is required"), set, "Invalid IP ban request");
63
+ // Absent is permanent (the default), exactly like an explicit null;
64
+ // parseBanDuration maps those to null and rejects anything else.
65
+ const minutes = body.minutes === undefined ? null : parseBanDuration(body.minutes);
66
+ if (minutes === undefined)
67
+ return errorResponse(new Error("minutes must be a positive integer or null"), set, "Invalid IP ban request");
68
+ await store.ban(ip, minutes === null ? 0 : minutes * 60_000);
69
+ return {
70
+ ip,
71
+ bannedUntil: minutes === null ? null : new Date(Date.now() + minutes * 60_000).toISOString(),
72
+ manual: true,
73
+ };
74
+ } catch (e) {
75
+ return errorResponse(e, set, "Could not ban IP");
76
+ }
77
+ },
78
+ )
79
+ .delete("/ip-bans/:ip", async ({ request, params, set }) => {
80
+ try {
81
+ requireGlobalAdmin(accessResolver(request));
82
+ const ip = typeof params?.["ip"] === "string" ? decodeURIComponent(params["ip"]) : "";
83
+ if (ip.length === 0)
84
+ return errorResponse(new Error("ip is required"), set, "Invalid unban request");
85
+ await store.unban(ip);
86
+ return { success: true, ip };
87
+ } catch (e) {
88
+ return errorResponse(e, set, "Could not unban IP");
89
+ }
90
+ });
91
+ }
92
+
93
+ export { BAN_DURATION_CHOICES };
@@ -196,59 +196,6 @@ const TOP_N = 50;
196
196
  */
197
197
  const USAGE_READ_THROUGH_TTL_SECONDS = 30;
198
198
 
199
- /**
200
- * Nearest-rank percentile over a sorted array — kept for the JS-side
201
- * `summarizeUsagePerformance` (still used by callers that already hold a
202
- * bounded event list). The usage endpoints read percentiles from SQL via
203
- * `summarizeUsagePerformanceSql`, which is what the dashboard polls.
204
- */
205
- function percentile(sorted: readonly number[], fraction: number): number {
206
- if (sorted.length === 0) return 0;
207
- const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(fraction * sorted.length) - 1));
208
- return Math.round(sorted[index] ?? 0);
209
- }
210
-
211
- interface UsagePerformanceSummary {
212
- avgResolveMs: number;
213
- avgDurationMs: number;
214
- latencyP50Ms: number;
215
- latencyP95Ms: number;
216
- latencyP99Ms: number;
217
- /** Events each series was computed from; see `UsageSummaryTotals`. */
218
- latencySampleCount: number;
219
- resolveSampleCount: number;
220
- }
221
-
222
- /** Summarizes routing delay and request latency for an already-filtered period. */
223
- export function summarizeUsagePerformance(
224
- events: readonly Pick<TelemetryEventRecord, "latencyMs" | "resolveMs">[],
225
- ): UsagePerformanceSummary {
226
- const latencies: number[] = [];
227
- let latencySum = 0;
228
- let resolveSum = 0;
229
- let resolveCount = 0;
230
- for (const event of events) {
231
- if (event.latencyMs !== null) {
232
- latencies.push(event.latencyMs);
233
- latencySum += event.latencyMs;
234
- }
235
- if (event.resolveMs !== null) {
236
- resolveSum += event.resolveMs;
237
- resolveCount += 1;
238
- }
239
- }
240
- latencies.sort((a, b) => a - b);
241
- return {
242
- avgResolveMs: resolveCount > 0 ? resolveSum / resolveCount : 0,
243
- avgDurationMs: latencies.length > 0 ? latencySum / latencies.length : 0,
244
- latencyP50Ms: percentile(latencies, 0.5),
245
- latencyP95Ms: percentile(latencies, 0.95),
246
- latencyP99Ms: percentile(latencies, 0.99),
247
- latencySampleCount: latencies.length,
248
- resolveSampleCount: resolveCount,
249
- };
250
- }
251
-
252
199
  /**
253
200
  * The console dimension as the SQL reader's grouping key.
254
201
  *
@@ -11,7 +11,7 @@
11
11
  import { isBundledProviderId } from "../../../providers/provider-contracts";
12
12
  import { BUILTIN_DEFAULT_ENDPOINTS } from "../../../protocol/codecs";
13
13
 
14
- import { providerDisplayName, providerServiceKinds } from "../../../providers/provider-metadata";
14
+ import { providerDisplayName, providerServiceKinds, customProviderServiceKinds } from "../../../providers/provider-metadata";
15
15
  import type { CompatibilityProfile } from "../../../providers/provider-metadata";
16
16
  import { supportedWireFamiliesForProvider } from "../../../providers/discovery/probe-wire";
17
17
  import { providers } from "../../../persistence/schema";
@@ -74,6 +74,7 @@ export function mapProviderRow(
74
74
  row.wireFamilyDefault,
75
75
  row.compatibilityProfile,
76
76
  );
77
+ const compatibilityProfile = row.compatibilityProfile as CompatibilityProfile | null;
77
78
  return {
78
79
  providerId: row.id,
79
80
  displayName: providerDisplayName(row.id),
@@ -82,7 +83,9 @@ export function mapProviderRow(
82
83
  isBuiltIn: isBundledProviderId(row.id),
83
84
  configured: false,
84
85
  requiresAccount: row.requiresAccount,
85
- serviceKinds: providerServiceKinds(row.id),
86
+ serviceKinds: isBundledProviderId(row.id)
87
+ ? providerServiceKinds(row.id)
88
+ : customProviderServiceKinds(compatibilityProfile),
86
89
  // No model advertises search; see the note in provider-metadata.ts.
87
90
  supportsWebSearch: false,
88
91
  supportsModelDiscovery: