cartethyia 2.7.9 → 2.8.1

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 (496) hide show
  1. package/.env.example +6 -5
  2. package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Dq530awD.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-DaZZ3cqP.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BlC_xdkX.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-7xT5yg2E.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
  16. package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-6Teoq9Se.js} +2 -2
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CZuI0MAR.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
  20. package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-C1hNTlkr.js} +1 -1
  21. package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-0yfhSI1C.js} +1 -1
  23. package/dist/dashboard/assets/OverviewPage-0yfhSI1C.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js +8 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js.gz +0 -0
  26. package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-Ddhm1InL.js} +1 -1
  27. package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js.gz +0 -0
  30. package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js +9 -0
  31. package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-BzZAvYSX.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
  36. package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage--s-EfEIL.js} +1 -1
  37. package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-BjXFGSK4.js} +1 -1
  39. package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
  40. package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-CP8F8BN7.js} +1 -1
  41. package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-2hc12Uz2.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-2hc12Uz2.js.gz +0 -0
  44. package/dist/dashboard/assets/app-DGXbNfAR.css +2 -0
  45. package/dist/dashboard/assets/app-DGXbNfAR.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-BMcj1xZE.js +1 -0
  47. package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-DkRUuVfz.js} +1 -1
  49. package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-CDjW841Q.js} +1 -1
  51. package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-Bto5UlL9.js} +1 -1
  53. package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-DaH6g9hL.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-BYKBCzjk.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-DL0Zftwv.js} +1 -1
  59. package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-Dpx_UXaS.js} +1 -1
  61. package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-BV7-1NoM.js} +1 -1
  63. package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
  64. package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-CGvdNU6r.js} +1 -1
  65. package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-DScKYvZj.js +4 -0
  67. package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
  68. package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-BS3DZlZj.js} +1 -1
  69. package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-BU64Lqvp.js → index-SQnvvTWX.js} +2 -2
  71. package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-Dl-cuLK0.js} +1 -1
  73. package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-BCVkdR9d.js → input-Bfx0EzRc.js} +1 -1
  75. package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-BxhbQ_JS.js} +1 -1
  77. package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-D49Y4ptn.js} +1 -1
  79. package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-Cae01iu8.js → live-CbVTdX9V.js} +1 -1
  81. package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
  82. package/dist/dashboard/assets/{network-CRib7H_2.js → network-JGvDveof.js} +1 -1
  83. package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
  84. package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-Dw6kiew-.js} +1 -1
  85. package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
  86. package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-iUoI8GeE.js} +1 -1
  87. package/dist/dashboard/assets/pagination-iUoI8GeE.js.gz +0 -0
  88. package/dist/dashboard/assets/providers/sensenova-intl.png +0 -0
  89. package/dist/dashboard/assets/providers/sensenova.png +0 -0
  90. package/dist/dashboard/assets/providers/zro.svg +3 -0
  91. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  92. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  93. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  94. package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-8jZr-3WE.js} +1 -1
  95. package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
  96. package/dist/dashboard/assets/{select-ChoFWyKz.js → select-oO1FxMVb.js} +2 -2
  97. package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
  98. package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-CLr3kdIx.js} +1 -1
  99. package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
  100. package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-Y3pDteZr.js} +1 -1
  101. package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
  102. package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-CArX_Iak.js} +1 -1
  103. package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
  104. package/dist/dashboard/assets/{switch-Db70BMix.js → switch-DwlwmDO-.js} +1 -1
  105. package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
  106. package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-DJ9GYEgf.js} +1 -1
  107. package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
  108. package/dist/dashboard/index.html +2 -2
  109. package/dist/dashboard/index.html.gz +0 -0
  110. package/dist/dashboard/precompressed-assets.json +54 -53
  111. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  112. package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
  113. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  114. package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
  115. package/package.json +1 -1
  116. package/src/boot/app.ts +11 -14
  117. package/src/boot/dependencies.ts +5 -7
  118. package/src/boot/flush.ts +20 -0
  119. package/src/boot/lifecycle.ts +7 -5
  120. package/src/config/providers.ts +6 -5
  121. package/src/config/routing.ts +26 -0
  122. package/src/config/spec-metadata.ts +72 -0
  123. package/src/config/spec.ts +28 -8
  124. package/src/console/api-keys/contracts.ts +12 -1
  125. package/src/console/api-keys/routes.ts +40 -28
  126. package/src/console/auth/account-reset.ts +7 -9
  127. package/src/console/backup/contracts.ts +4 -0
  128. package/src/console/backup/export-endpoint.ts +7 -5
  129. package/src/console/backup/nine-router.ts +71 -10
  130. package/src/console/backup/service.ts +8 -1
  131. package/src/console/cli-tools/service.ts +6 -12
  132. package/src/console/console-router-guards.ts +23 -16
  133. package/src/console/console-router.ts +1 -1
  134. package/src/console/domain-registration.ts +7 -4
  135. package/src/console/inferhub/client.ts +1 -1
  136. package/src/console/inferhub/normalize.ts +24 -28
  137. package/src/console/inferhub/types.ts +3 -0
  138. package/src/console/ip-bans/routes.ts +7 -13
  139. package/src/console/observability/live.ts +2 -2
  140. package/src/console/observability/store.ts +0 -53
  141. package/src/console/providers/catalog/contracts.ts +9 -3
  142. package/src/console/providers/catalog/model-operations.ts +2 -3
  143. package/src/console/providers/catalog/provider-operations.ts +1 -1
  144. package/src/console/providers/catalog/routes.ts +3 -2
  145. package/src/console/providers/catalog/store.ts +1914 -1902
  146. package/src/console/providers/detail/routes.ts +1 -0
  147. package/src/console/providers/detail/store.ts +4 -0
  148. package/src/console/providers/oauth/callback-listener.ts +30 -11
  149. package/src/console/providers/oauth/device-egress.ts +123 -0
  150. package/src/console/providers/oauth/routes.ts +123 -16
  151. package/src/console/quota/account-quota-global.ts +14 -24
  152. package/src/console/quota/account-quota-shared.ts +49 -0
  153. package/src/console/quota/account-quota-tenant.ts +11 -23
  154. package/src/console/quota/account-test-egress.ts +2 -2
  155. package/src/console/quota/daily-growth-job.ts +1 -1
  156. package/src/console/quota/quota-view.ts +19 -3
  157. package/src/console/routing/pools/contracts.ts +1 -1
  158. package/src/console/routing/pools/operations.ts +32 -47
  159. package/src/console/routing/pools/probe-verdict.ts +2 -2
  160. package/src/console/routing/pools/relay-deploy.ts +1 -1
  161. package/src/console/routing/pools/store.ts +4 -4
  162. package/src/console/shared/errors.ts +33 -6
  163. package/src/main.ts +2 -3
  164. package/src/network/client.ts +67 -0
  165. package/src/network/egress/bidi-session.ts +322 -0
  166. package/src/network/egress/dial.ts +201 -0
  167. package/src/network/egress/factory.ts +191 -0
  168. package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
  169. package/src/network/egress/tls-profile.ts +123 -0
  170. package/src/network/egress/ws.ts +175 -0
  171. package/src/network/pool/{byte-accounting.ts → accounting.ts} +3 -2
  172. package/src/network/{types.ts → pool/agent-config.ts} +1 -1
  173. package/src/network/pool/agent.ts +2 -2
  174. package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +95 -20
  175. package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
  176. package/src/network/pool/resolver.ts +10 -849
  177. package/src/network/pool/selector.ts +184 -41
  178. package/src/network/ssrf.ts +7 -7
  179. package/src/network/{retry.ts → util/retry.ts} +1 -1
  180. package/src/network/{rate-limit-verdict.ts → verdict.ts} +1 -1
  181. package/src/observability/event-log.ts +7 -361
  182. package/src/observability/performance-metrics.ts +9 -0
  183. package/src/observability/runtime-metrics.ts +14 -9
  184. package/src/observability/telemetry-buffer.ts +16 -26
  185. package/src/observability/telemetry-reconciliation.ts +9 -37
  186. package/src/observability/telemetry-row.ts +125 -0
  187. package/src/persistence/db-mode.ts +4 -1
  188. package/src/persistence/dialect.ts +18 -0
  189. package/src/persistence/migrate.ts +6 -3
  190. package/src/persistence/schema.ts +17 -0
  191. package/src/persistence/ttl-cache.ts +21 -3
  192. package/src/protocol/codecs.ts +59 -2
  193. package/src/protocol/dsml.ts +34 -86
  194. package/src/protocol/markup-stream-parser.ts +195 -0
  195. package/src/protocol/reasoning-text.ts +19 -0
  196. package/src/protocol/request/chat.ts +10 -10
  197. package/src/protocol/request/gemini.ts +8 -2
  198. package/src/protocol/request/messages.ts +34 -22
  199. package/src/protocol/response/chat.ts +12 -20
  200. package/src/protocol/response/messages.ts +34 -7
  201. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  202. package/src/protocol/response/responses.ts +18 -2
  203. package/src/protocol/stop-reason.ts +23 -1
  204. package/src/protocol/think-split.ts +112 -0
  205. package/src/protocol/translation/dialect/qwen.ts +26 -58
  206. package/src/protocol/translation/quirks.ts +15 -0
  207. package/src/protocol/upstream/send-claude-messages.ts +2 -2
  208. package/src/providers/adapters/compatible-adapter.ts +76 -12
  209. package/src/providers/authentication/oauth-client.ts +40 -6
  210. package/src/providers/authentication/oauth-device-flow.ts +105 -11
  211. package/src/providers/authentication/oauth-flow-store.ts +82 -1
  212. package/src/providers/authentication/oauth-refresh-service.ts +766 -758
  213. package/src/providers/configured-provider.ts +148 -0
  214. package/src/providers/default-registry.ts +137 -36
  215. package/src/providers/discovery/discovery-types.ts +7 -0
  216. package/src/providers/discovery/probe-phases.ts +20 -6
  217. package/src/providers/discovery/probing-service.ts +22 -2
  218. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  219. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  220. package/src/providers/integrations/antigravity/antigravity-protocol.ts +16 -1
  221. package/src/providers/integrations/buddy/account.ts +969 -0
  222. package/src/providers/integrations/buddy/capabilities.ts +12 -12
  223. package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
  224. package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +176 -20
  225. package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
  226. package/src/providers/integrations/buddy/codebuddy/shared.ts +41 -26
  227. package/src/providers/integrations/buddy/headers.ts +311 -0
  228. package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
  229. package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
  230. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  231. package/src/providers/integrations/cline/cline-oauth.ts +10 -5
  232. package/src/providers/integrations/cline/cline.ts +5 -6
  233. package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
  234. package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
  235. package/src/providers/integrations/clixad/provider.ts +145 -0
  236. package/src/providers/integrations/codex/codex-oauth.ts +8 -5
  237. package/src/providers/integrations/commandcode/quota.ts +22 -28
  238. package/src/providers/integrations/cursor/cursor-auth.ts +1 -9
  239. package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
  240. package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
  241. package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
  242. package/src/providers/integrations/cursor/cursor-request.ts +26 -12
  243. package/src/providers/integrations/cursor/cursor-shared.ts +8 -5
  244. package/src/providers/integrations/cursor/cursor.ts +574 -110
  245. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  246. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  247. package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
  248. package/src/providers/integrations/deepai/deepai-common.ts +145 -0
  249. package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
  250. package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
  251. package/src/providers/integrations/deepai/provider.ts +42 -0
  252. package/src/providers/integrations/devin/devin-auth.ts +7 -0
  253. package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
  254. package/src/providers/integrations/factory/factory-oauth.ts +33 -51
  255. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  256. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  257. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  258. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  259. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  260. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  261. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  262. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  263. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  264. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  265. package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
  266. package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
  267. package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
  268. package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
  269. package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
  270. package/src/providers/integrations/freepi/freepi-session.ts +158 -0
  271. package/src/providers/integrations/freepi/freepi.ts +80 -54
  272. package/src/providers/integrations/github/github-discovery.ts +30 -13
  273. package/src/providers/integrations/github/github-oauth.ts +217 -44
  274. package/src/providers/integrations/github/github-quota.ts +22 -5
  275. package/src/providers/integrations/github/github.ts +40 -32
  276. package/src/providers/integrations/grok/grok-oauth.ts +52 -49
  277. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  278. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  279. package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
  280. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +42 -13
  281. package/src/providers/integrations/hermes/index.ts +2 -0
  282. package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
  283. package/src/providers/integrations/kilo/kilo.ts +1 -1
  284. package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
  285. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  286. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  287. package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
  288. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  289. package/src/providers/integrations/kiro/kiro.ts +15 -19
  290. package/src/providers/integrations/muse/muse-oauth.ts +5 -5
  291. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  292. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  293. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  294. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  295. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  296. package/src/providers/integrations/openrouter/provider.ts +1 -1
  297. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  298. package/src/providers/integrations/perch/perch-protocol.ts +0 -6
  299. package/src/providers/integrations/perch/perch-quota.ts +2 -16
  300. package/src/providers/integrations/perch/perch.ts +6 -16
  301. package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
  302. package/src/providers/integrations/qoder/qoder-adapter.ts +20 -125
  303. package/src/providers/integrations/qoder/qoder-auth.ts +109 -30
  304. package/src/providers/integrations/qoder/qoder-catalog.ts +21 -6
  305. package/src/providers/integrations/qoder/qoder-chat.ts +32 -29
  306. package/src/providers/integrations/qoder/qoder-checkin.ts +5 -5
  307. package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
  308. package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
  309. package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
  310. package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
  311. package/src/providers/integrations/qoder/qoder-types.ts +9 -0
  312. package/src/providers/integrations/qoder/qoder.ts +3 -26
  313. package/src/providers/integrations/sensenova/provider.ts +144 -0
  314. package/src/providers/integrations/trae/trae-protocol.ts +9 -5
  315. package/src/providers/integrations/trae/trae-quota.ts +21 -29
  316. package/src/providers/integrations/trae/trae-realm.ts +38 -0
  317. package/src/providers/integrations/trae/trae.ts +7 -24
  318. package/src/providers/integrations/vercel/provider.ts +117 -0
  319. package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
  320. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  321. package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
  322. package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
  323. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  324. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  325. package/src/providers/integrations/zro/zro.ts +205 -0
  326. package/src/providers/operations/account-error-classification.ts +199 -307
  327. package/src/providers/operations/account-health-service.ts +82 -1
  328. package/src/providers/operations/account-refresh-status.ts +36 -37
  329. package/src/providers/operations/account-tier.ts +15 -16
  330. package/src/providers/operations/client-version-resolver.ts +1 -1
  331. package/src/providers/operations/client-versions.ts +100 -81
  332. package/src/providers/operations/cooldown-rules.ts +11 -16
  333. package/src/providers/operations/oauth-refresh-lead.ts +46 -22
  334. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  335. package/src/providers/operations/provider-catalog-service.ts +1 -1
  336. package/src/providers/operations/provider-credential-service.ts +118 -51
  337. package/src/providers/operations/provider-rules/github.ts +48 -0
  338. package/src/providers/operations/provider-rules/grok.ts +33 -0
  339. package/src/providers/operations/provider-rules/inception.ts +49 -0
  340. package/src/providers/operations/provider-rules/index.ts +32 -0
  341. package/src/providers/operations/provider-rules/perch.ts +37 -0
  342. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  343. package/src/providers/operations/provider-rules/trae.ts +81 -0
  344. package/src/providers/operations/session-resolution.ts +17 -1
  345. package/src/providers/provider-contracts.ts +6 -4
  346. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  347. package/src/providers/provider-metadata.ts +70 -10
  348. package/src/providers/provider-probe.ts +15 -1
  349. package/src/providers/provider-registry.ts +42 -33
  350. package/src/providers/quota/quota-contracts.ts +2 -0
  351. package/src/providers/quota/quota-limit-windows.ts +16 -5
  352. package/src/providers/quota/quota-support.ts +1 -1
  353. package/src/providers/quota/quota-window-parser.ts +90 -18
  354. package/src/providers/quota/refresh.ts +100 -21
  355. package/src/security/api-key-auth-cache.ts +1 -1
  356. package/src/security/api-key-auth.ts +6 -2
  357. package/src/security/ip-boundary.ts +1 -1
  358. package/src/transport/dispatch/attempt-finalize.ts +15 -14
  359. package/src/transport/dispatch/attempt-loop.ts +73 -19
  360. package/src/transport/dispatch/generation.ts +2 -8
  361. package/src/transport/dispatch/handler-deps.ts +1 -1
  362. package/src/transport/dispatch/leases.ts +1 -1
  363. package/src/transport/dispatch/native-shared.ts +25 -0
  364. package/src/transport/dispatch/proxy-request.ts +8 -46
  365. package/src/transport/dispatch/responses-compact.ts +2 -8
  366. package/src/transport/dispatch/retry-policy.ts +1 -1
  367. package/src/transport/dispatch/streaming/finalize.ts +10 -7
  368. package/src/transport/dispatch/streaming/stream.ts +9 -37
  369. package/src/transport/dispatch/systemone.ts +2 -8
  370. package/src/transport/dispatch/token-count.ts +2 -6
  371. package/src/transport/dispatch/upstream.ts +63 -0
  372. package/src/transport/drain-endpoint.ts +31 -4
  373. package/src/transport/failure-policy.ts +16 -30
  374. package/src/transport/guards/body-policy.ts +7 -1
  375. package/src/transport/guards/error-lifecycle.ts +13 -1
  376. package/src/transport/guards/request-context.ts +22 -14
  377. package/src/transport/request/preparer.ts +81 -71
  378. package/src/transport/routing/bounded-lru.ts +14 -0
  379. package/src/transport/routing/drain-reserve.ts +129 -0
  380. package/src/transport/routing/plan-gate.ts +7 -1
  381. package/src/transport/routing/round-robin.ts +60 -0
  382. package/src/transport/routing/route-catalog.ts +123 -111
  383. package/src/transport/routing/route-model.ts +51 -9
  384. package/src/transport/routing/router.ts +393 -32
  385. package/src/transport/shutdown-notice.ts +1 -1
  386. package/src/transport/surface/adapters.ts +11 -2
  387. package/src/transport/surface/chat/adapter.ts +2 -2
  388. package/src/transport/surface/chat/encode.ts +47 -4
  389. package/src/transport/surface/chat/parse.ts +2 -12
  390. package/src/transport/surface/completion.ts +44 -33
  391. package/src/transport/surface/content-parts.ts +21 -0
  392. package/src/transport/surface/messages/adapter.ts +7 -1
  393. package/src/transport/surface/messages/encode.ts +11 -3
  394. package/src/transport/surface/messages/parse.ts +2 -1
  395. package/src/transport/surface/messages/stream.ts +33 -17
  396. package/src/transport/surface/responses/encode.ts +22 -5
  397. package/src/transport/surface/responses/parse.ts +3 -11
  398. package/src/workers/buddy-growth-tasks.ts +8 -8
  399. package/src/workers/daily-checkin.ts +4 -4
  400. package/src/workers/quota-refresh-sweep.ts +1 -1
  401. package/src/workers/zcode-gift-claim.ts +1 -1
  402. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
  403. package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
  404. package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
  405. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
  406. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
  407. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
  408. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
  409. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
  410. package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
  411. package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
  412. package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
  413. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
  414. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
  415. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
  416. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
  417. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
  418. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
  419. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js +0 -9
  420. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
  421. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
  422. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
  423. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
  424. package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
  425. package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
  426. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
  427. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
  428. package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
  429. package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
  430. package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
  431. package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
  432. package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
  433. package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
  434. package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
  435. package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
  436. package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
  437. package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
  438. package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
  439. package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
  440. package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
  441. package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
  442. package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
  443. package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
  444. package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
  445. package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
  446. package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
  447. package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
  448. package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
  449. package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
  450. package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
  451. package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
  452. package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
  453. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  454. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  455. package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
  456. package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
  457. package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
  458. package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
  459. package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
  460. package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
  461. package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
  462. package/src/network/pool-health.ts +0 -210
  463. package/src/providers/bundled-discovery-providers.ts +0 -98
  464. package/src/providers/configured-openai-providers.ts +0 -120
  465. package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
  466. package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
  467. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
  468. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
  469. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
  470. package/src/providers/integrations/buddy/buddy-identity.ts +0 -300
  471. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
  472. package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
  473. package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
  474. package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
  475. package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
  476. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
  477. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -185
  478. package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
  479. package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -175
  480. package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
  481. package/src/providers/integrations/qoder/chat-proto.ts +0 -328
  482. package/src/providers/integrations/qoder/qoder-grpc.ts +0 -871
  483. package/src/providers/integrations/qoder/qoder-ide.ts +0 -588
  484. package/src/providers/operations/model-discovery-cache.ts +0 -34
  485. package/src/providers/operations/probe-fetch.ts +0 -15
  486. package/src/providers/operations/provider-version-cache.ts +0 -30
  487. package/src/providers/provider-capabilities.ts +0 -33
  488. package/src/providers/provider-credential-envelope.ts +0 -43
  489. /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-C0gTXEoN.js} +0 -0
  490. /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-C0gTXEoN.js.gz} +0 -0
  491. /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
  492. /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
  493. /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
  494. /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
  495. /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
  496. /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
@@ -86,7 +86,6 @@ export function cliMappingSourceKeys(toolId: string, sourceModel: string): reado
86
86
 
87
87
  const HARD_COOLDOWN_CATEGORIES: Readonly<Record<string, true>> = {
88
88
  quota_exhausted: true,
89
- policy_blocked: true,
90
89
  auth_invalidated: true,
91
90
  };
92
91
 
@@ -528,7 +527,8 @@ class RouteCatalogRepository {
528
527
  }
529
528
 
530
529
  // Per-tenant pool selection strategy; an absent row reads as the
531
- // `least_loaded` default at selection time.
530
+ // `least_loaded` default at selection time. The local map resolves
531
+ // per-candidate during the build; it is NOT stored in RouteSnapshot.
532
532
  const poolRouting: Record<string, PoolRoutingSetting> = {};
533
533
  for (const row of poolSettingRows) {
534
534
  if (tenantId === undefined || row.tenantId === tenantId) {
@@ -662,6 +662,72 @@ class RouteCatalogRepository {
662
662
  return limit === undefined ? {} : { network_pool_provider_limit: limit };
663
663
  }
664
664
 
665
+ /**
666
+ * The field set shared by every `RoutedCandidate` literal in the fan-out
667
+ * below. The four sites used to re-declare the same ~15-field shape, so a
668
+ * field added to one had to be added to all four. `opts` carries only the
669
+ * deltas that genuinely differ per site; health stamping is deliberately
670
+ * NOT merged here — it happens at each site (inside the pool-account
671
+ * literal, never for configured-pool tenants, conditional for public,
672
+ * after construction for account rows), and collapsing those branches
673
+ * would change routing.
674
+ */
675
+ function buildCandidateBase(
676
+ model: MergedModelRow,
677
+ capabilityProfile: Record<string, boolean>,
678
+ opts: {
679
+ readonly tenantId: string | null;
680
+ /** `false` emits `requires_account: false` (credential-less route). */
681
+ readonly requiresAccount?: boolean;
682
+ readonly ownerTenantId?: string | null;
683
+ readonly userAgent?: string;
684
+ readonly accountId?: string;
685
+ readonly proOnly?: boolean;
686
+ readonly accountPlan?: string | null;
687
+ readonly maxInflight?: number;
688
+ readonly pools?: {
689
+ readonly ids: readonly string[];
690
+ readonly limits: Record<string, number>;
691
+ readonly weights: Record<string, number>;
692
+ /** `undefined` (from `resolveNetworkPools`) means no routing strategy. */
693
+ readonly routing?: (PoolRoutingSetting & { tenantId: string }) | undefined;
694
+ };
695
+ },
696
+ ): RoutedCandidate {
697
+ return {
698
+ provider_id: model.providerId,
699
+ model_id: model.modelId,
700
+ wire_family: model.wireFamily as WireFamily,
701
+ ...(model.dialect == null ? {} : { dialect: model.dialect }),
702
+ service_kind: model.serviceKind,
703
+ endpoint: model.endpointPath,
704
+ ...(model.outputLimit == null ? {} : { output_limit: model.outputLimit }),
705
+ capability_profile: capabilityProfile,
706
+ ...(opts.userAgent === undefined ? {} : { user_agent: opts.userAgent }),
707
+ tenant_id: opts.tenantId,
708
+ ...(model.freeTier === true ? { free_tier: true } : {}),
709
+ // Public credential-less candidates have no account to own them.
710
+ owner_tenant_id: opts.ownerTenantId ?? null,
711
+ ...(opts.accountId === undefined ? {} : { provider_account_id: opts.accountId }),
712
+ ...(opts.proOnly === true ? { pro_only: true } : {}),
713
+ ...(opts.accountPlan === null || opts.accountPlan === undefined
714
+ ? {}
715
+ : { account_plan: opts.accountPlan }),
716
+ ...(opts.maxInflight === undefined ? {} : { max_inflight: opts.maxInflight }),
717
+ ...(opts.requiresAccount === false ? { requires_account: false as const } : {}),
718
+ ...(opts.pools
719
+ ? {
720
+ network_pool_ids: opts.pools.ids,
721
+ network_pool_required: true,
722
+ network_pool_limits: opts.pools.limits,
723
+ network_pool_weights: opts.pools.weights,
724
+ ...(opts.pools.routing ? { network_pool_routing: opts.pools.routing } : {}),
725
+ ...providerLimitFor(model.providerId, opts.tenantId),
726
+ }
727
+ : {}),
728
+ };
729
+ }
730
+
665
731
  // Group usable accounts per provider (not per provider-tenant). Built-in
666
732
  // providers are `tenant_id IS NULL` but their accounts are per-tenant, so
667
733
  // grouping by `providerTenantId` would make every global model look
@@ -716,38 +782,30 @@ class RouteCatalogRepository {
716
782
  const tenantPools = resolveNetworkPools(model.providerId, tenantOwnerId);
717
783
  if (!tenantPools) continue;
718
784
  emittedPoolAccount = true;
719
- const tenantCandidate: RoutedCandidate = {
720
- provider_id: model.providerId,
721
- model_id: model.modelId,
722
- wire_family: model.wireFamily as WireFamily,
723
- ...(model.dialect == null ? {} : { dialect: model.dialect }),
724
- service_kind: model.serviceKind,
725
- endpoint: model.endpointPath,
726
- ...(model.outputLimit == null ? {} : { output_limit: model.outputLimit }),
727
- capability_profile: capabilityProfile,
728
- tenant_id: tenantOwnerId,
729
- ...(model.freeTier === true ? { free_tier: true } : {}),
730
- // Public credential-less candidates have no account to own them.
731
- owner_tenant_id: null,
732
- requires_account: false as const,
733
- network_pool_ids: [poolId],
734
- network_pool_required: true,
735
- network_pool_limits: tenantPools.limits,
736
- network_pool_weights: tenantPools.weights,
737
- ...(tenantPools.routing ? { network_pool_routing: tenantPools.routing } : {}),
738
- ...providerLimitFor(model.providerId, tenantOwnerId),
739
- ...(first.status === "disabled"
740
- ? { health_status: "disabled" as const }
741
- : first.status === "cooldown"
742
- ? {
743
- health_status: "cooldown" as const,
744
- cooldown_kind: hardCooldownCategory(first.lastErrorCategory) ? "hard" : "soft",
745
- ...(first.cooldownUntil && first.cooldownUntil.getTime() > Date.now()
746
- ? { cooldown_until: first.cooldownUntil.getTime() }
747
- : {}),
748
- }
749
- : {}),
750
- };
785
+ const tenantCandidate = buildCandidateBase(model, capabilityProfile, {
786
+ tenantId: tenantOwnerId,
787
+ requiresAccount: false,
788
+ pools: {
789
+ ids: [poolId],
790
+ limits: tenantPools.limits,
791
+ weights: tenantPools.weights,
792
+ routing: tenantPools.routing,
793
+ },
794
+ });
795
+ // Pool-account health from the row: a spent egress is excluded
796
+ // exactly like a cooling account, and the deadline rides along so
797
+ // `plan()` can quote the real reset on an all-cooling answer.
798
+ if (first.status === "disabled") {
799
+ tenantCandidate.health_status = "disabled";
800
+ } else if (first.status === "cooldown") {
801
+ tenantCandidate.health_status = "cooldown";
802
+ tenantCandidate.cooldown_kind = hardCooldownCategory(first.lastErrorCategory)
803
+ ? "hard"
804
+ : "soft";
805
+ if (first.cooldownUntil && first.cooldownUntil.getTime() > Date.now()) {
806
+ tenantCandidate.cooldown_until = first.cooldownUntil.getTime();
807
+ }
808
+ }
751
809
  if (tenantId === undefined || tenantId === tenantOwnerId) {
752
810
  candidates.push(tenantCandidate);
753
811
  }
@@ -757,27 +815,16 @@ class RouteCatalogRepository {
757
815
  for (const tId of configuredPoolTenants) {
758
816
  const tenantPools = resolveNetworkPools(model.providerId, tId);
759
817
  if (tenantPools) {
760
- const tenantCandidate: RoutedCandidate = {
761
- provider_id: model.providerId,
762
- model_id: model.modelId,
763
- wire_family: model.wireFamily as WireFamily,
764
- ...(model.dialect == null ? {} : { dialect: model.dialect }),
765
- service_kind: model.serviceKind,
766
- endpoint: model.endpointPath,
767
- ...(model.outputLimit == null ? {} : { output_limit: model.outputLimit }),
768
- capability_profile: capabilityProfile,
769
- tenant_id: tId,
770
- ...(model.freeTier === true ? { free_tier: true } : {}),
771
- // Public credential-less candidates have no account to own them.
772
- owner_tenant_id: null,
773
- requires_account: false as const,
774
- network_pool_ids: tenantPools.ids,
775
- network_pool_required: true,
776
- network_pool_limits: tenantPools.limits,
777
- network_pool_weights: tenantPools.weights,
778
- ...(tenantPools.routing ? { network_pool_routing: tenantPools.routing } : {}),
779
- ...providerLimitFor(model.providerId, tId),
780
- };
818
+ const tenantCandidate = buildCandidateBase(model, capabilityProfile, {
819
+ tenantId: tId,
820
+ requiresAccount: false,
821
+ pools: {
822
+ ids: tenantPools.ids,
823
+ limits: tenantPools.limits,
824
+ weights: tenantPools.weights,
825
+ routing: tenantPools.routing,
826
+ },
827
+ });
781
828
  if (tenantId === undefined || tenantId === tId) {
782
829
  candidates.push(tenantCandidate);
783
830
  }
@@ -787,33 +834,17 @@ class RouteCatalogRepository {
787
834
  const networkPools = providerRequiresAccount
788
835
  ? undefined
789
836
  : resolveNetworkPools(model.providerId, providerTenantId);
790
- const candidate: RoutedCandidate = {
791
- provider_id: model.providerId,
792
- model_id: model.modelId,
793
- wire_family: model.wireFamily as WireFamily,
794
- ...(model.dialect == null ? {} : { dialect: model.dialect }),
795
- service_kind: model.serviceKind,
796
- endpoint: model.endpointPath,
797
- ...(model.outputLimit == null ? {} : { output_limit: model.outputLimit }),
798
- capability_profile: capabilityProfile,
799
- tenant_id: providerTenantId,
800
- ...(model.freeTier === true ? { free_tier: true } : {}),
801
- // Public credential-less candidates have no account to own them.
802
- owner_tenant_id: null,
803
- ...(providerRequiresAccount
804
- ? { health_status: "disabled" as const }
805
- : { requires_account: false as const }),
806
- ...(networkPools
807
- ? {
808
- network_pool_ids: networkPools.ids,
809
- network_pool_required: true,
810
- network_pool_limits: networkPools.limits,
811
- network_pool_weights: networkPools.weights,
812
- ...(networkPools.routing ? { network_pool_routing: networkPools.routing } : {}),
813
- ...providerLimitFor(model.providerId, providerTenantId),
814
- }
815
- : {}),
816
- };
837
+ const candidate = buildCandidateBase(model, capabilityProfile, {
838
+ tenantId: providerTenantId,
839
+ // A provider that requires an account but has none is unusable: the
840
+ // public candidate is stamped disabled below. A genuinely
841
+ // credential-less provider carries `requires_account: false` instead.
842
+ ...(providerRequiresAccount ? {} : { requiresAccount: false }),
843
+ ...(networkPools ? { pools: networkPools } : {}),
844
+ });
845
+ if (providerRequiresAccount) {
846
+ candidate.health_status = "disabled";
847
+ }
817
848
  if (tenantId === undefined || candidate.tenant_id === null || candidate.tenant_id === tenantId) {
818
849
  candidates.push(candidate);
819
850
  }
@@ -831,46 +862,28 @@ class RouteCatalogRepository {
831
862
  }
832
863
  const networkPools = resolveNetworkPools(model.providerId, rowTenantId);
833
864
  const routeUserAgent = resolveUserAgent(model.providerId, rowTenantId);
834
- const candidate: RoutedCandidate = {
835
- provider_id: model.providerId,
836
- model_id: model.modelId,
837
- wire_family: model.wireFamily as WireFamily,
838
- ...(model.dialect == null ? {} : { dialect: model.dialect }),
839
- service_kind: model.serviceKind,
840
- endpoint: model.endpointPath,
841
- ...(model.outputLimit == null ? {} : { output_limit: model.outputLimit }),
842
- capability_profile: capabilityProfile,
843
- ...(routeUserAgent === undefined ? {} : { user_agent: routeUserAgent }),
844
- ...(model.freeTier === true ? { free_tier: true } : {}),
845
- tenant_id: rowTenantId,
846
- provider_account_id: account.id,
865
+ const candidate = buildCandidateBase(model, capabilityProfile, {
866
+ tenantId: rowTenantId,
867
+ accountId: account.id,
847
868
  // The account's owning tenant (null = shared pool-wide) — the bucket
848
869
  // key for admission, distinct from the requesting tenant.
849
- owner_tenant_id: account.tenantId ?? null,
870
+ ownerTenantId: account.tenantId ?? null,
850
871
  // The plan gate is per (account, model): the model may be refused on
851
872
  // a free plan and the account may be on one. Both ride here because
852
873
  // `plan()` sees the candidate and nothing else.
853
- ...(model.proOnly === true ? { pro_only: true } : {}),
874
+ ...(model.proOnly === true ? { proOnly: true } : {}),
854
875
  ...(account.quotaPlan === null || account.quotaPlan === undefined
855
876
  ? {}
856
- : { account_plan: account.quotaPlan }),
877
+ : { accountPlan: account.quotaPlan }),
857
878
  // Provider-wide concurrency ceiling from Routing Strategy; legacy
858
879
  // account overrides are never read here.
859
880
  ...(() => {
860
881
  const resolved = resolveMaxInflight(model.providerId, rowTenantId);
861
- return resolved === undefined ? {} : { max_inflight: resolved };
882
+ return resolved === undefined ? {} : { maxInflight: resolved };
862
883
  })(),
863
- ...(networkPools
864
- ? {
865
- network_pool_required: true,
866
- network_pool_ids: networkPools.ids,
867
- network_pool_limits: networkPools.limits,
868
- network_pool_weights: networkPools.weights,
869
- ...(networkPools.routing ? { network_pool_routing: networkPools.routing } : {}),
870
- ...providerLimitFor(model.providerId, rowTenantId),
871
- }
872
- : {}),
873
- };
884
+ ...(routeUserAgent === undefined ? {} : { userAgent: routeUserAgent }),
885
+ ...(networkPools ? { pools: networkPools } : {}),
886
+ });
874
887
  if (account.status === "cooldown") {
875
888
  // A row that still says `cooldown` stays excluded until the sweeper
876
889
  // flips it back to `active`. An *expired* deadline used to read as
@@ -995,7 +1008,6 @@ class RouteCatalogRepository {
995
1008
  cli_aliases: cliAliases,
996
1009
  combos,
997
1010
  ...(routingMap ? { providerRouting: routingMap } : {}),
998
- ...(Object.keys(poolRouting).length > 0 ? { poolRouting } : {}),
999
1011
  ...(invokeDisabledTenants.size > 0 ? { invokeDisabledTenants } : {}),
1000
1012
  };
1001
1013
  }
@@ -108,6 +108,8 @@ export interface RouteCandidate {
108
108
  readonly quota_windows?: readonly ProviderQuotaWindow[];
109
109
  /** A successful Start Plan balance authoritatively lists its model grants. */
110
110
  readonly quota_entitlements_known?: boolean;
111
+ /** Normalized quota/plan fact stamped at plan time (unified engine). */
112
+ readonly quota_fact?: NormalizedQuotaFact;
111
113
  }
112
114
 
113
115
  /**
@@ -141,6 +143,27 @@ export type RoutedCandidate = RouteCandidate & {
141
143
  * activity is read live via `sharedClaudeSlowLanes.isActive`).
142
144
  */
143
145
  lane?: "slow";
146
+ /**
147
+ * Normalized quota/plan fact stamped at plan time by the routing decision
148
+ * engine. The decision engine reads this instead of re-deriving window state.
149
+ */
150
+ quota_fact?: NormalizedQuotaFact;
151
+ /**
152
+ * Operator-configured account priority (0 when unset). Higher ranks ahead
153
+ * within the same rank group, before usage measurement.
154
+ */
155
+ account_priority?: number;
156
+ /**
157
+ * True when the account's remaining quota fraction is at or below the
158
+ * configured reserve percent. A reserved account stays eligible but orders
159
+ * behind non-reserve siblings.
160
+ */
161
+ in_reserve?: boolean;
162
+ /**
163
+ * Drain urgency score: headroom fraction / remaining hours until reset.
164
+ * Higher = quota about to expire unused, ranked first within a rank group.
165
+ */
166
+ drain_score?: number;
144
167
  };
145
168
 
146
169
  /**
@@ -256,10 +279,13 @@ export interface RoutePlan {
256
279
 
257
280
  /** How eligible a candidate is and why — written once, read by every layer. */
258
281
  export const ROUTING_RANK = {
259
- FRESH: 0,
260
- SLOW: 1,
261
- COOLING: 2,
262
- SATURATED: 3,
282
+ /** Fresh candidate with a positive known quota / above its usage floor. */
283
+ FRESH_KNOWN: 0,
284
+ /** Fresh candidate whose quota state is unknown (no read, unsupported, stale). */
285
+ FRESH_UNKNOWN: 1,
286
+ SLOW: 2,
287
+ COOLING: 3,
288
+ SATURATED: 4,
263
289
  } as const;
264
290
  export type RoutingRank = (typeof ROUTING_RANK)[keyof typeof ROUTING_RANK];
265
291
 
@@ -275,6 +301,22 @@ export interface RoutingDecision {
275
301
  readonly saturated?: boolean;
276
302
  }
277
303
 
304
+ /**
305
+ * The normalized quota/plan fact the unified routing engine evaluates per
306
+ * candidate. Produced once per candidate before `evaluateCandidate`, so the
307
+ * decision engine never re-reads quota sources or re-derives window state.
308
+ */
309
+ export interface NormalizedQuotaFact {
310
+ /** `available` = positive known balance; `exhausted` = known spent/below floor. */
311
+ readonly quotaState: "available" | "exhausted" | "unknown";
312
+ readonly quotaSource: "live_cache" | "snapshot" | "unsupported" | "none";
313
+ /** Epoch ms of the observation, when one exists (used for freshness). */
314
+ readonly quotaObservedAt: number | null;
315
+ /** True when the observed quota is older than the freshness horizon. */
316
+ readonly isStale: boolean;
317
+ readonly usageFloorState: "above" | "below" | "unknown";
318
+ }
319
+
278
320
  import type { ComboStrategy, PoolRoutingStrategy, ProviderRoutingStrategy } from "../../persistence/schema";
279
321
  import { providerRoutingStrategy } from "../../persistence/schema";
280
322
 
@@ -299,8 +341,6 @@ export interface RouteSnapshot {
299
341
  readonly created_at: number;
300
342
  /** Optional per-provider routing preferences keyed by tenant. */
301
343
  readonly providerRouting?: ProviderRoutingMap;
302
- /** Optional per-tenant pool selection strategy (absent = `least_loaded`). */
303
- readonly poolRouting?: Readonly<Record<string, PoolRoutingSetting>>;
304
344
  /**
305
345
  * Tenants whose invoke switch is off, so `/v1/*` must be refused outright.
306
346
  *
@@ -319,6 +359,9 @@ export type EligibilityReason =
319
359
  | "cooldown_hard"
320
360
  | "model_cooldown"
321
361
  | "quota_exhausted"
362
+ | "quota_unknown"
363
+ | "plan_gate"
364
+ | "capability_unsupported"
322
365
  | "disabled"
323
366
 
324
367
  export interface EligibilityDecision {
@@ -392,6 +435,8 @@ const ELIGIBILITY_REASON_LABELS: Readonly<Record<string, string>> = {
392
435
  disabled: "all accounts are disabled",
393
436
  credit_floor_reached: "credit or quota is exhausted",
394
437
  quota_exhausted: "every account has an exhausted quota window or is below its configured usage floor",
438
+ quota_unknown: "accounts have no known quota state (unknown, unsupported, or stale)",
439
+ plan_gate: "the plan explicitly proves these accounts cannot serve this model",
395
440
  capability_unsupported: "the accounts serving this model do not support the request's capabilities (e.g. images, tools, or reasoning)",
396
441
  };
397
442
 
@@ -703,9 +748,6 @@ export class InMemoryRouteSnapshotService implements RouteSnapshotService {
703
748
  ...(built.cli_aliases ? { cli_aliases: Object.freeze({ ...built.cli_aliases }) } : {}),
704
749
  combos: Object.freeze({ ...built.combos }),
705
750
  ...(providerRouting ? { providerRouting } : {}),
706
- // Unfrozen copy: deepFreeze must reach the inner per-tenant settings —
707
- // a pre-frozen record trips its is-frozen guard and skips recursion.
708
- ...(built.poolRouting ? { poolRouting: { ...built.poolRouting } } : {}),
709
751
  created_at: built.created_at ?? Date.now(),
710
752
  });
711
753
  // A mutation during the build means this snapshot describes a catalog that