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
@@ -13,7 +13,14 @@ import {
13
13
  /** Shape returned for quota data on dashboard account responses. */
14
14
  interface QuotaRefreshView {
15
15
  readonly source: string;
16
- readonly status: "ready" | "error" | "refreshing";
16
+ /**
17
+ * `ready` = a usable quota reading exists (successful fetch, cached or
18
+ * snapshot). `error` = the collector returned an error with no usable
19
+ * reading. `refreshing` = a background fill is in flight. `unknown` = no
20
+ * quota evidence at all (no collector surface, or an empty successful read
21
+ * with no timestamp) — the canonical wire representation for unknown quota.
22
+ */
23
+ readonly status: "ready" | "error" | "refreshing" | "unknown";
17
24
  readonly plan: string | null;
18
25
  readonly windows: ProviderQuotaResult["windows"];
19
26
  readonly fetchedAt: string | null;
@@ -94,7 +101,7 @@ export function toQuotaView(
94
101
  lastAttemptAt?: string | null;
95
102
  lastSuccessAt?: string | null;
96
103
  refreshError?: string | null;
97
- status?: "ready" | "error" | "refreshing";
104
+ status?: "ready" | "error" | "refreshing" | "unknown";
98
105
  } = {},
99
106
  ): QuotaRefreshView {
100
107
  const fetchedAt = timestamps.fetchedAt ?? account.lastSuccessAt?.toISOString() ?? null;
@@ -112,9 +119,18 @@ export function toQuotaView(
112
119
  // that the last attempt failed; only `quota.error` — no usable reading at
113
120
  // all — makes the card an error.
114
121
  const error = quota.error ?? null;
122
+ // Unknown quota: a successful read that carries no windows AND no timestamp
123
+ // is not evidence of anything — the canonical "no quota evidence" state.
124
+ // (A provider with a real zero-balance still reports windows/timestamps.)
125
+ const derivedStatus =
126
+ error !== null
127
+ ? ("error" as const)
128
+ : quota.windows.length === 0 && fetchedAt === null
129
+ ? ("unknown" as const)
130
+ : ("ready" as const);
115
131
  return {
116
132
  source: quota.source,
117
- status: timestamps.status ?? (error !== null ? "error" : "ready"),
133
+ status: timestamps.status ?? derivedStatus,
118
134
  plan: quota.plan ?? null,
119
135
  windows: quota.windows,
120
136
  fetchedAt,
@@ -45,35 +45,40 @@ function isBlockedEntry(entry: { readonly blocked?: boolean }): boolean {
45
45
  }
46
46
 
47
47
  export function createNetworkPoolOperations(config: NetworkPoolConfig) {
48
+ /**
49
+ * The read-only enrichment projection shared by the pool list and the pool
50
+ * detail page: live inflight (falling back to the stored row counter when no
51
+ * selector is configured), the derived available/utilization figures, and the
52
+ * active provider cooldowns (omitted entirely when there are none).
53
+ */
54
+ async function enrichPoolResponse(res: NetworkPoolResponse, poolId: string): Promise<NetworkPoolResponse> {
55
+ const cooldowns = await config.poolSelector?.getPoolCooldowns(poolId);
56
+ const inflight = config.poolSelector
57
+ ? await config.poolSelector.getInflightAuthoritative(poolId)
58
+ : res.inflight;
59
+ return {
60
+ ...res,
61
+ inflight,
62
+ available: Math.max(0, res.maxInflight - inflight),
63
+ utilization: res.maxInflight > 0 ? Math.min(1, inflight / res.maxInflight) : 1,
64
+ ...(cooldowns && cooldowns.length > 0
65
+ ? {
66
+ providerCooldowns: cooldowns.map((c) => ({
67
+ providerId: c.providerId,
68
+ until: new Date(c.until).toISOString(),
69
+ reason: c.reason,
70
+ blocked: isBlockedEntry(c),
71
+ })),
72
+ }
73
+ : {}),
74
+ };
75
+ }
76
+
48
77
  const operations = {
49
78
  async listPools(access: AccessDecision | undefined): Promise<NetworkPoolResponse[]> {
50
79
  const a = requireTenantScope(access, "dashboard:read");
51
80
  const rawPools = await config.store.list(a.tenantId);
52
- return Promise.all(
53
- rawPools.map(async (r) => {
54
- const res = sanitizePoolResponse(r);
55
- const cooldowns = await config.poolSelector?.getPoolCooldowns(r.id);
56
- const inflight = config.poolSelector
57
- ? await config.poolSelector.getInflightAuthoritative(r.id)
58
- : res.inflight;
59
- return {
60
- ...res,
61
- inflight,
62
- available: Math.max(0, res.maxInflight - inflight),
63
- utilization: res.maxInflight > 0 ? Math.min(1, inflight / res.maxInflight) : 1,
64
- ...(cooldowns && cooldowns.length > 0
65
- ? {
66
- providerCooldowns: cooldowns.map((c) => ({
67
- providerId: c.providerId,
68
- until: new Date(c.until).toISOString(),
69
- reason: c.reason,
70
- blocked: isBlockedEntry(c),
71
- })),
72
- }
73
- : {}),
74
- };
75
- }),
76
- );
81
+ return Promise.all(rawPools.map(async (r) => enrichPoolResponse(sanitizePoolResponse(r), r.id)));
77
82
  },
78
83
  async getPoolDetail(
79
84
  access: AccessDecision | undefined,
@@ -82,27 +87,7 @@ export function createNetworkPoolOperations(config: NetworkPoolConfig) {
82
87
  const a = requireTenantScope(access, "dashboard:read");
83
88
  const rec = await config.store.get(a.tenantId, poolId);
84
89
  if (!rec) throw new ConsoleDomainError("pool_not_found", 404, `Pool ${poolId} not found`);
85
- const res = sanitizePoolResponse(rec);
86
- const cooldowns = await config.poolSelector?.getPoolCooldowns(poolId);
87
- const inflight = config.poolSelector
88
- ? await config.poolSelector.getInflightAuthoritative(poolId)
89
- : res.inflight;
90
- return {
91
- ...res,
92
- inflight,
93
- available: Math.max(0, res.maxInflight - inflight),
94
- utilization: res.maxInflight > 0 ? Math.min(1, inflight / res.maxInflight) : 1,
95
- ...(cooldowns && cooldowns.length > 0
96
- ? {
97
- providerCooldowns: cooldowns.map((c) => ({
98
- providerId: c.providerId,
99
- until: new Date(c.until).toISOString(),
100
- reason: c.reason,
101
- blocked: isBlockedEntry(c),
102
- })),
103
- }
104
- : {}),
105
- };
90
+ return enrichPoolResponse(sanitizePoolResponse(rec), poolId);
106
91
  },
107
92
  async clearPoolCooldown(
108
93
  access: AccessDecision | undefined,
@@ -59,6 +59,15 @@ export interface RuntimeSettingsResponse {
59
59
  * used instead.
60
60
  */
61
61
  readonly visionHandoffModels: readonly string[];
62
+ /**
63
+ * Auto-disable provider accounts stuck on upstream rate-limit/quota verdicts.
64
+ * Default false — the risky direction stays off until an operator flips it.
65
+ */
66
+ readonly autoDisableRateLimited: boolean;
67
+ /** Modelprobe mode; `detail` when unset. */
68
+ readonly modelProbeMode: ModelProbeMode;
69
+ /** Fast-probe timeout in seconds; null means the built-in default (10). */
70
+ readonly modelProbeFastTimeoutSeconds: number | null;
62
71
  readonly updatedAt: string;
63
72
  /*
64
73
  * What each `null` actually resolves to, in the same units the row stores.
@@ -136,6 +145,10 @@ export type PonyTailLevel = (typeof PONYTAIL_LEVELS)[number];
136
145
  /** RTK prune strength accepted by the runtime-settings PATCH. */
137
146
  export type RtkLevel = (typeof RTK_LEVELS)[number];
138
147
 
148
+ export const MODEL_PROBE_MODES = ["fast", "detail"] as const;
149
+ /** Modelprobe mode accepted by the runtime-settings PATCH and the probe route. */
150
+ export type ModelProbeMode = (typeof MODEL_PROBE_MODES)[number];
151
+
139
152
  /** Upper bound on the number of ordered vision-handoff models. */
140
153
  export const VISION_HANDOFF_MAX_MODELS = 8;
141
154
 
@@ -272,4 +285,9 @@ export const runtimeUpdateBody = t.Object({
272
285
  t.Union([t.Null(), t.Number({ minimum: 64 * 1024, maximum: 100 * 1024 * 1024 })]),
273
286
  ),
274
287
  visionHandoffModels: t.Optional(t.Array(t.String())),
288
+ autoDisableRateLimited: t.Optional(t.Boolean()),
289
+ modelProbeMode: t.Optional(literalUnion(MODEL_PROBE_MODES)),
290
+ modelProbeFastTimeoutSeconds: t.Optional(
291
+ t.Union([t.Null(), t.Number({ minimum: 1, maximum: 120 })]),
292
+ ),
275
293
  });
@@ -10,6 +10,7 @@ import { resolveUpstreamTimeoutMs } from "../../config/timeouts";
10
10
  import { payloadRetentionMs } from "../../observability/payload-capture";
11
11
  import { IMAGE_CLAMP_MAX_BYTES } from "../../protocol/image-clamp";
12
12
  import {
13
+ MODEL_PROBE_MODES,
13
14
  PONYTAIL_LEVELS,
14
15
  RESPONSES_REASONING_SUMMARIES,
15
16
  RTK_LEVELS,
@@ -23,6 +24,7 @@ import {
23
24
  type RuntimeSettingsStore,
24
25
  type TelemetryPayloadDepth,
25
26
  type TelemetryPayloadMode,
27
+ type ModelProbeMode,
26
28
  type UpdateRuntimeSettingsRequest,
27
29
  } from "./contracts";
28
30
 
@@ -116,6 +118,23 @@ function normalizeConcurrencyTolerance(value: unknown): number | null {
116
118
  return normalizeBoundedNumber(value, 0, 1024);
117
119
  }
118
120
 
121
+ /** Modelprobe mode: only the two known modes survive; anything else = `detail`. */
122
+ function normalizeModelProbeMode(value: unknown): ModelProbeMode {
123
+ return typeof value === "string" && (MODEL_PROBE_MODES as readonly string[]).includes(value)
124
+ ? (value as ModelProbeMode)
125
+ : "detail";
126
+ }
127
+
128
+ /**
129
+ * Fast-probe timeout in seconds: 1–120, `null` = deployment default, and any
130
+ * other absent/non-finite value reads as the built-in default of 10 (the UI
131
+ * select's middle choice, and the value probing uses when unset).
132
+ */
133
+ function normalizeModelProbeFastTimeoutSeconds(value: unknown): number | null {
134
+ if (value === null) return null;
135
+ return normalizeBoundedNumber(value, 1, 120) ?? 10;
136
+ }
137
+
119
138
  /** Ordered vision-handoff models: non-empty strings, trimmed, de-duplicated. */
120
139
  function normalizeVisionHandoffModels(value: unknown): readonly string[] {
121
140
  if (!Array.isArray(value)) return [];
@@ -166,6 +185,11 @@ function mapRuntimeSettingsRow(
166
185
  telemetryWindowMinutes: normalizeTelemetryWindowMinutes(prefs.telemetryWindowMinutes),
167
186
  imageClampMaxBytes: normalizeImageClampMaxBytes(prefs.imageClampMaxBytes),
168
187
  visionHandoffModels: normalizeVisionHandoffModels(prefs.visionHandoffModels),
188
+ autoDisableRateLimited: prefs.autoDisableRateLimited === true,
189
+ modelProbeMode: normalizeModelProbeMode(prefs.modelProbeMode),
190
+ modelProbeFastTimeoutSeconds: normalizeModelProbeFastTimeoutSeconds(
191
+ prefs.modelProbeFastTimeoutSeconds,
192
+ ),
169
193
  updatedAt,
170
194
  /*
171
195
  * Resolved at request time, not at module load: all four come from env the
@@ -231,6 +255,14 @@ export class DrizzleRuntimeSettingsStore implements RuntimeSettingsStore {
231
255
  patchPrefs.imageClampMaxBytes = normalizeImageClampMaxBytes(patch.imageClampMaxBytes);
232
256
  if (patch.visionHandoffModels !== undefined)
233
257
  patchPrefs.visionHandoffModels = normalizeVisionHandoffModels(patch.visionHandoffModels);
258
+ if (patch.autoDisableRateLimited !== undefined)
259
+ patchPrefs.autoDisableRateLimited = patch.autoDisableRateLimited === true;
260
+ if (patch.modelProbeMode !== undefined)
261
+ patchPrefs.modelProbeMode = normalizeModelProbeMode(patch.modelProbeMode);
262
+ if (patch.modelProbeFastTimeoutSeconds !== undefined)
263
+ patchPrefs.modelProbeFastTimeoutSeconds = normalizeModelProbeFastTimeoutSeconds(
264
+ patch.modelProbeFastTimeoutSeconds,
265
+ );
234
266
 
235
267
  const rows = await this.db
236
268
  .insert(consoleSettings)
@@ -1,7 +1,7 @@
1
1
  import { ValidationError } from "elysia";
2
2
  import type { AccessDecision, AccessScope } from "../../security/access-control";
3
3
  import { isRecord } from "../../protocol/codecs";
4
- import { formatPublicErrorMessage } from "../../protocol/gateway-error";
4
+ import { formatPublicErrorMessage, GatewayError } from "../../protocol/gateway-error";
5
5
 
6
6
  // Single console error/access contract. Every domain throws ConsoleDomainError
7
7
  // (or the storage-layer BackupError, same code/status/message shape) and
@@ -24,8 +24,27 @@ export class ConsoleDomainError extends Error {
24
24
  }
25
25
  }
26
26
 
27
- function asDomainError(error: unknown): { code: string; status: number; message: string; details?: unknown } | undefined {
27
+ interface ShapedDomainError {
28
+ readonly code: string;
29
+ readonly status: number;
30
+ readonly message: string;
31
+ readonly details?: unknown;
32
+ /** Upstream/network origin, preserved so the dashboard can tell a provider
33
+ * rejection from a console-session expiry (see `maybeSignalSessionTransition`). */
34
+ readonly origin?: string;
35
+ }
36
+
37
+ function asDomainError(error: unknown): ShapedDomainError | undefined {
28
38
  if (error instanceof ConsoleDomainError) return error;
39
+ if (error instanceof GatewayError) {
40
+ return {
41
+ code: error.code,
42
+ status: error.status,
43
+ message: error.message,
44
+ details: error.details,
45
+ ...(error.origin === "cartethyia" ? {} : { origin: error.origin }),
46
+ };
47
+ }
29
48
  if (
30
49
  typeof error === "object" &&
31
50
  error !== null &&
@@ -33,8 +52,14 @@ function asDomainError(error: unknown): { code: string; status: number; message:
33
52
  typeof (error as { status?: unknown }).status === "number" &&
34
53
  typeof (error as { message?: unknown }).message === "string"
35
54
  ) {
36
- const shaped = error as { code: string; status: number; message: string; details?: unknown };
37
- return { code: shaped.code, status: shaped.status, message: shaped.message, details: shaped.details };
55
+ const shaped = error as { code: string; status: number; message: string; details?: unknown; origin?: unknown };
56
+ return {
57
+ code: shaped.code,
58
+ status: shaped.status,
59
+ message: shaped.message,
60
+ details: shaped.details,
61
+ ...(typeof shaped.origin === "string" && shaped.origin !== "cartethyia" ? { origin: shaped.origin } : {}),
62
+ };
38
63
  }
39
64
  return undefined;
40
65
  }
@@ -47,6 +72,7 @@ export function errorResponse(
47
72
  ): {
48
73
  error: string;
49
74
  code: string;
75
+ origin?: string;
50
76
  details?: Record<string, unknown>;
51
77
  } {
52
78
  const shaped = asDomainError(error);
@@ -63,16 +89,17 @@ export function errorResponse(
63
89
  const body: Record<string, unknown> = {
64
90
  error: message,
65
91
  code: shaped.code,
92
+ ...(shaped.origin === undefined ? {} : { origin: shaped.origin }),
66
93
  };
67
94
  if (includeDetails) body.details = details;
68
- return body as { error: string; code: string; details?: Record<string, unknown> };
95
+ return body as { error: string; code: string; origin?: string; details?: Record<string, unknown> };
69
96
  }
70
97
  set.status = 500;
71
98
  const body: Record<string, unknown> = {
72
99
  error: formatPublicErrorMessage("internal_error", fallbackMessage),
73
100
  code: "internal_error",
74
101
  };
75
- return body as { error: string; code: string; details?: Record<string, unknown> };
102
+ return body as { error: string; code: string; origin?: string; details?: Record<string, unknown> };
76
103
  }
77
104
 
78
105
  export function requireScope(
@@ -115,7 +115,6 @@ export const SYSTEM_PROMPT_ALIAS_LIMIT = 512;
115
115
  const WRAPPED_INJECTION_PROVIDERS: ReadonlySet<string> = new Set([
116
116
  "cb", // CodeBuddy International
117
117
  "cbcn", // CodeBuddy CN
118
- "workbuddy",
119
118
  ]);
120
119
 
121
120
  /** Whether a provider's tenant text rides wrapped after the fixed identity prompt. */
package/src/main.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  import "./boot/elysia-typebox-bridge";
4
4
  import { createGatewayApp, createGatewayShell } from "./boot/app";
5
5
  import { bootstrap } from "./boot/lifecycle";
6
+ import { flushAllBuffers } from "./boot/flush";
6
7
  import type { CartethyiaBoot } from "./boot/lifecycle";
7
8
  import {
8
9
  resolveDrainToken,
@@ -84,14 +85,13 @@ const app = boot
84
85
  oauthRefreshService: boot.deps.oauthRefreshService,
85
86
  admissionService: boot.deps.admissionService,
86
87
  readRoutingAccountInflight: boot.deps.readRoutingAccountInflight,
88
+ ipAbuseStore: boot.deps.ipAbuseStore,
87
89
  // The same three writers the shutdown coordinator drains, so a manual
88
90
  // flush from the console does exactly what a graceful stop would do
89
91
  // for the queues, minus the stop.
90
92
  flushBuffers: {
91
93
  flush: async () => {
92
- await boot.deps.telemetryBuffer.flush();
93
- await boot.deps.usageFlush.flush();
94
- await boot.deps.healthEventFlush.flush();
94
+ await flushAllBuffers(boot.deps);
95
95
  },
96
96
  },
97
97
  },
@@ -13,6 +13,8 @@
13
13
  * byte, and nothing is allocated.
14
14
  */
15
15
 
16
+ import { evictOldestEntry } from "../../transport/routing/bounded-lru";
17
+
16
18
  /** Running totals for one pool. */
17
19
  interface PoolByteTotals {
18
20
  /** Bytes sent to the proxy (uploads, TLS handshake, CONNECT). */
@@ -33,8 +35,7 @@ function entry(poolId: string): { sent: number; received: number } {
33
35
  let current = totals.get(poolId);
34
36
  if (!current) {
35
37
  if (totals.size >= MAX_BYTE_ENTRY_COUNT) {
36
- const oldest = totals.keys().next().value;
37
- if (oldest !== undefined) totals.delete(oldest);
38
+ evictOldestEntry(totals, MAX_BYTE_ENTRY_COUNT - 1);
38
39
  }
39
40
  current = { sent: 0, received: 0 };
40
41
  totals.set(poolId, current);
@@ -108,6 +108,13 @@ type PoolSelectionFailureReason =
108
108
  | "coordination_unavailable"
109
109
  | "no_active_pool";
110
110
 
111
+ interface PoolCooldownState {
112
+ readonly inCooldown: boolean;
113
+ readonly resetsAt: Date | null;
114
+ readonly reason: string | null;
115
+ readonly blocked: boolean;
116
+ }
117
+
111
118
  interface PoolCapacitySnapshot {
112
119
  readonly poolId: string;
113
120
  readonly maxInflight: number;
@@ -399,12 +406,7 @@ export class NetworkPoolSelector {
399
406
  async isProviderCooldown(
400
407
  poolId: string,
401
408
  providerId: string,
402
- ): Promise<{
403
- inCooldown: boolean;
404
- resetsAt: Date | null;
405
- reason: string | null;
406
- blocked: boolean;
407
- }> {
409
+ ): Promise<PoolCooldownState> {
408
410
  const k = this.cooldownKey(poolId, providerId);
409
411
  const local = this.state.cooldowns.get(k);
410
412
  if (local) {
@@ -880,6 +882,171 @@ export class NetworkPoolSelector {
880
882
  }
881
883
  return undefined;
882
884
  }
885
+
886
+ /**
887
+ * Infight counter from a Redis `MGET` row. `null` (unset counter) is zero;
888
+ * a malformed counter is the same hard error the per-key read raises, so a
889
+ * corrupt key is never silently treated as 0.
890
+ */
891
+ private parseInflightCounter(raw: string | null, what: string): number {
892
+ if (raw === null) return 0;
893
+ const value = Number(raw);
894
+ if (!Number.isInteger(value) || value < 0) {
895
+ throw new Error(`invalid pool inflight counter: ${what}`);
896
+ }
897
+ return value;
898
+ }
899
+
900
+ /**
901
+ * Per-pool cooldown resolution with the same semantics as
902
+ * `isProviderCooldown`: the local cooldown map wins when it has a live
903
+ * entry; otherwise a non-expired Redis row is admitted (bounded) and an
904
+ * expired Redis row is cleared. Used by the batched failure path, which
905
+ * reads all cooldown keys in one `MGET` and resolves each row here.
906
+ */
907
+ private cooldownFromBatchState(
908
+ poolId: string,
909
+ providerId: string,
910
+ raw: string | null,
911
+ ): PoolCooldownState {
912
+ const k = this.cooldownKey(poolId, providerId);
913
+ const local = this.state.cooldowns.get(k);
914
+ if (local) {
915
+ if (Date.now() < local.until) {
916
+ return {
917
+ inCooldown: true,
918
+ resetsAt: new Date(local.until),
919
+ reason: local.reason,
920
+ blocked: local.blocked === true,
921
+ };
922
+ }
923
+ this.state.cooldowns.delete(k);
924
+ }
925
+ const parsed = raw ? parseCooldownEntry(raw) : undefined;
926
+ if (parsed && Date.now() < parsed.until) {
927
+ enforceCooldownLimit(this.state.cooldowns);
928
+ this.state.cooldowns.set(k, parsed);
929
+ return {
930
+ inCooldown: true,
931
+ resetsAt: new Date(parsed.until),
932
+ reason: parsed.reason,
933
+ blocked: parsed.blocked === true,
934
+ };
935
+ }
936
+ if (raw) {
937
+ void this.clearRedisCooldown(poolId, providerId);
938
+ }
939
+ return { inCooldown: false, resetsAt: null, reason: null, blocked: false };
940
+ }
941
+
942
+ /**
943
+ * The original per-pool failure check: one Redis read per pool for each of
944
+ * cooldown, pool inflight, and provider inflight. Retained as the fallback
945
+ * for the non-Redis path and for a Redis outage during the batched read.
946
+ */
947
+ private async perPoolSelectionChecks(
948
+ eligiblePoolIds: readonly string[],
949
+ providerId: string,
950
+ limitsByPool?: Record<string, number>,
951
+ weightsByPool?: Record<string, number>,
952
+ providerCapacity?: number,
953
+ ): Promise<Array<{ cooldown: PoolCooldownState; pool: PoolCapacitySnapshot }>> {
954
+ return Promise.all(
955
+ eligiblePoolIds.map(async (poolId) => {
956
+ const cooldown = await this.isProviderCooldown(poolId, providerId);
957
+ const maxInflight = effectiveConcurrency(
958
+ limitsByPool?.[poolId] ?? DEFAULT_PROXY_CONCURRENCY,
959
+ );
960
+ const weight = normalizedWeight(weightsByPool?.[poolId]);
961
+ const currentInflight = await this.getInflightAuthoritative(poolId);
962
+ const providerInflight =
963
+ providerCapacity === undefined
964
+ ? undefined
965
+ : await this.getProviderInflightAuthoritative(poolId, providerId);
966
+ return {
967
+ cooldown,
968
+ pool: {
969
+ poolId,
970
+ maxInflight,
971
+ currentInflight,
972
+ available: Math.max(0, maxInflight - currentInflight),
973
+ weight,
974
+ ...(cooldown.resetsAt ? { retryAt: cooldown.resetsAt.getTime() } : {}),
975
+ ...(cooldown.blocked ? { blocked: true } : {}),
976
+ ...(providerCapacity === undefined
977
+ ? {}
978
+ : { providerMaxInflight: providerCapacity, providerInflight: providerInflight ?? 0 }),
979
+ } satisfies PoolCapacitySnapshot,
980
+ };
981
+ }),
982
+ );
983
+ }
984
+
985
+ /**
986
+ * Batched failure check: one `MGET` per key family (cooldowns, pool
987
+ * inflight, provider inflight) instead of three serial GETs per pool. On a
988
+ * Redis error the whole read falls back to per-pool reads so the failure
989
+ * report keeps the "cooldown store unavailable" per-pool detail.
990
+ */
991
+ private async batchSelectionChecks(
992
+ eligiblePoolIds: readonly string[],
993
+ providerId: string,
994
+ limitsByPool?: Record<string, number>,
995
+ weightsByPool?: Record<string, number>,
996
+ providerCapacity?: number,
997
+ ): Promise<Array<{ cooldown: PoolCooldownState; pool: PoolCapacitySnapshot }>> {
998
+ const cooldownKeys = eligiblePoolIds.map((poolId) => proxyCooldownKey(poolId, providerId));
999
+ const inflightKeys = eligiblePoolIds.map((poolId) => proxyInflightKey(poolId));
1000
+ const providerInflightKeys =
1001
+ providerCapacity === undefined
1002
+ ? []
1003
+ : eligiblePoolIds.map((poolId) => proxyProviderInflightKey(poolId, providerId));
1004
+ try {
1005
+ const [cooldownRaws, inflightRaws, providerRaws] = await Promise.all([
1006
+ this.redis!.mget(...cooldownKeys),
1007
+ this.redis!.mget(...inflightKeys),
1008
+ providerInflightKeys.length === 0
1009
+ ? Promise.resolve([] as Array<string | null>)
1010
+ : this.redis!.mget(...providerInflightKeys),
1011
+ ]);
1012
+ return eligiblePoolIds.map((poolId, index) => {
1013
+ const cooldown = this.cooldownFromBatchState(poolId, providerId, cooldownRaws[index] ?? null);
1014
+ const maxInflight = effectiveConcurrency(
1015
+ limitsByPool?.[poolId] ?? DEFAULT_PROXY_CONCURRENCY,
1016
+ );
1017
+ const weight = normalizedWeight(weightsByPool?.[poolId]);
1018
+ const currentInflight = this.parseInflightCounter(inflightRaws[index] ?? null, poolId);
1019
+ const providerInflight =
1020
+ providerCapacity === undefined
1021
+ ? undefined
1022
+ : this.parseInflightCounter(providerRaws[index] ?? null, `${poolId}/${providerId}`);
1023
+ return {
1024
+ cooldown,
1025
+ pool: {
1026
+ poolId,
1027
+ maxInflight,
1028
+ currentInflight,
1029
+ available: Math.max(0, maxInflight - currentInflight),
1030
+ weight,
1031
+ ...(cooldown.resetsAt ? { retryAt: cooldown.resetsAt.getTime() } : {}),
1032
+ ...(cooldown.blocked ? { blocked: true } : {}),
1033
+ ...(providerCapacity === undefined
1034
+ ? {}
1035
+ : { providerMaxInflight: providerCapacity, providerInflight: providerInflight ?? 0 }),
1036
+ } satisfies PoolCapacitySnapshot,
1037
+ };
1038
+ });
1039
+ } catch {
1040
+ return this.perPoolSelectionChecks(
1041
+ eligiblePoolIds,
1042
+ providerId,
1043
+ limitsByPool,
1044
+ weightsByPool,
1045
+ providerCapacity,
1046
+ );
1047
+ }
1048
+ }
1049
+
883
1050
  async getSelectionFailure(
884
1051
  eligiblePoolIds: readonly string[],
885
1052
  providerId: string,
@@ -891,39 +1058,15 @@ export class NetworkPoolSelector {
891
1058
  const providerCapacity =
892
1059
  providerMaxInflight === undefined ? undefined : effectiveConcurrency(providerMaxInflight);
893
1060
  try {
894
- // One pass, not two: the cooldown lookup is a Redis read per pool, and
895
- // the first version read it once for the capacity snapshot and again for
896
- // the rejection decision, doubling the round trips on exactly the path
897
- // that runs when selection fails.
898
- const checks = await Promise.all(
899
- eligiblePoolIds.map(async (poolId) => {
900
- const cooldown = await this.isProviderCooldown(poolId, providerId);
901
- const maxInflight = effectiveConcurrency(
902
- limitsByPool?.[poolId] ?? DEFAULT_PROXY_CONCURRENCY,
903
- );
904
- const weight = normalizedWeight(weightsByPool?.[poolId]);
905
- const currentInflight = await this.getInflightAuthoritative(poolId);
906
- const providerInflight =
907
- providerCapacity === undefined
908
- ? undefined
909
- : await this.getProviderInflightAuthoritative(poolId, providerId);
910
- return {
911
- cooldown,
912
- pool: {
913
- poolId,
914
- maxInflight,
915
- currentInflight,
916
- available: Math.max(0, maxInflight - currentInflight),
917
- weight,
918
- ...(cooldown.resetsAt ? { retryAt: cooldown.resetsAt.getTime() } : {}),
919
- ...(cooldown.blocked ? { blocked: true } : {}),
920
- ...(providerCapacity === undefined
921
- ? {}
922
- : { providerMaxInflight: providerCapacity, providerInflight: providerInflight ?? 0 }),
923
- } satisfies PoolCapacitySnapshot,
924
- };
925
- }),
926
- );
1061
+ // One batch pass instead of per-pool Redis reads: cooldowns and inflight
1062
+ // counters are read in one `MGET` per key family (three total), not
1063
+ // three serial GETs per pool. The per-pool resolution below consumes the
1064
+ // raw rows with the same semantics `isProviderCooldown` /
1065
+ // `getInflightAuthoritative` / `getProviderInflightAuthoritative` apply.
1066
+ const checks =
1067
+ this.redis === undefined
1068
+ ? await this.perPoolSelectionChecks(eligiblePoolIds, providerId, limitsByPool, weightsByPool, providerCapacity)
1069
+ : await this.batchSelectionChecks(eligiblePoolIds, providerId, limitsByPool, weightsByPool, providerCapacity);
927
1070
  const pools = checks.map((check) => check.pool);
928
1071
  const cooldowns = checks.map((check) => check.cooldown);
929
1072
  if (cooldowns.some((entry) => entry.reason === "cooldown store unavailable")) {