cartethyia 2.7.0 → 2.7.2

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 (330) hide show
  1. package/.env.example +107 -106
  2. package/README.md +25 -2
  3. package/dist/dashboard/assets/{BannedPage-P76QLGJA.js → BannedPage-XaUmiBrC.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-XaUmiBrC.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-DdBggJS9.js → CliToolDetailPage-CqD7QiLM.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-aN1iXhhP.js → CliToolsPage-0XUo4UMn.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js.gz +0 -0
  9. package/dist/dashboard/assets/{CombosPage-DE-F1nxZ.js → CombosPage-CXGS_Ct-.js} +1 -1
  10. package/dist/dashboard/assets/CombosPage-CXGS_Ct-.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-3CUxMy2X.js → CustomizationPage-DAG5J9EY.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js.gz +0 -0
  17. package/dist/dashboard/assets/LoginPage-BdmRLnq2.js +1 -0
  18. package/dist/dashboard/assets/LoginPage-BdmRLnq2.js.gz +0 -0
  19. package/dist/dashboard/assets/{ModelPicker-DKVslqG3.js → ModelPicker-BH8Tehdi.js} +1 -1
  20. package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-CqMxibJB.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-CqMxibJB.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js +4 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +1 -0
  26. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  27. package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js +1 -0
  28. package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js.gz +0 -0
  29. package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js +9 -0
  30. package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js.gz +0 -0
  31. package/dist/dashboard/assets/{QuotaPage-BjldQwIU.js → QuotaPage-7P2n--Fv.js} +1 -1
  32. package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js.gz +0 -0
  33. package/dist/dashboard/assets/{SettingsPage-DUIoLRxk.js → SettingsPage-CxZ1fTkN.js} +1 -1
  34. package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js.gz +0 -0
  35. package/dist/dashboard/assets/SetupPage-Co12Zzaw.js +1 -0
  36. package/dist/dashboard/assets/SetupPage-Co12Zzaw.js.gz +0 -0
  37. package/dist/dashboard/assets/{SortableList-fI7JkdIB.js → SortableList-Dqf4zpS-.js} +1 -1
  38. package/dist/dashboard/assets/SortableList-Dqf4zpS-.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-CHVZqskZ.js → StudioPage-Byj2lAEm.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-Byj2lAEm.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-CTkLAuRc.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-CTkLAuRc.js.gz +0 -0
  43. package/dist/dashboard/assets/{api-keys-DFY_fZU4.js → api-keys-CUi6UXsR.js} +1 -1
  44. package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
  45. package/dist/dashboard/assets/app-KvsUJUMF.css +2 -0
  46. package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-CwdnZARt.js → assert-67xXH5rf.js} +1 -1
  48. package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-Dr-uf0Au.js → badge-DC2L43Mj.js} +1 -1
  50. package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-BddmMdoS.js → banner-BBVcb0J3.js} +1 -1
  52. package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
  53. package/dist/dashboard/assets/{card-CK0PXWlX.js → card-Cca1XC6G.js} +1 -1
  54. package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
  55. package/dist/dashboard/assets/{cli-tools-CcB6mGh2.js → cli-tools-KvsWa1Jl.js} +1 -1
  56. package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
  57. package/dist/dashboard/assets/{clipboard-button-DGb2P9Yx.js → clipboard-button-DUzEbynq.js} +1 -1
  58. package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
  59. package/dist/dashboard/assets/{dialog-LkfuL50u.js → dialog-DFvWhwTa.js} +1 -1
  60. package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
  61. package/dist/dashboard/assets/{drawer-ZbEZ_84c.js → drawer-CFM2sMX1.js} +1 -1
  62. package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-BJfsVMvA.js → entry-Bqy8RFvM.js} +1 -1
  64. package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
  65. package/dist/dashboard/assets/{entry-CWBY5A0f.js → entry-ClCMCWWQ.js} +3 -3
  66. package/dist/dashboard/assets/entry-ClCMCWWQ.js.gz +0 -0
  67. package/dist/dashboard/assets/{entry-CvC1-Zqx.js → entry-_xHo5x6r.js} +1 -1
  68. package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
  69. package/dist/dashboard/assets/formatters-CWWZ-uCZ.js +1 -0
  70. package/dist/dashboard/assets/formatters-CWWZ-uCZ.js.gz +0 -0
  71. package/dist/dashboard/assets/{github-badge-BxpznFLt.js → github-badge-C9e8Ts7e.js} +1 -1
  72. package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
  73. package/dist/dashboard/assets/{index-CDzP3ovJ.js → index-DFVFrnWW.js} +2 -2
  74. package/dist/dashboard/assets/index-DFVFrnWW.js.gz +0 -0
  75. package/dist/dashboard/assets/{inline-Ae13MQ5w.js → inline-BFUcvllj.js} +1 -1
  76. package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
  77. package/dist/dashboard/assets/{input-D23fo7cp.js → input-C_ILAvBb.js} +1 -1
  78. package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
  79. package/dist/dashboard/assets/{layout-CSsxjj5P.js → layout-CBIyT6Rt.js} +1 -1
  80. package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
  81. package/dist/dashboard/assets/{list-row-dmbwZMDY.js → list-row-ebJ_-Nsb.js} +1 -1
  82. package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
  83. package/dist/dashboard/assets/{live-BG1oUsZZ.js → live-D0mm--ME.js} +1 -1
  84. package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-DSF-e4ho.js → mutation-sMD965oO.js} +1 -1
  86. package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
  87. package/dist/dashboard/assets/{numbers-Bm_aECY7.js → numbers-BO9dKaFr.js} +1 -1
  88. package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
  89. package/dist/dashboard/assets/providers/inception.svg +5 -0
  90. package/dist/dashboard/assets/providers/inception.svg.gz +0 -0
  91. package/dist/dashboard/assets/{query-keys-CF56nYIV.js → query-keys-CfL_Zdoh.js} +1 -1
  92. package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
  93. package/dist/dashboard/assets/{routing-CeA71n7G.js → routing-BnIpi1IJ.js} +1 -1
  94. package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
  95. package/dist/dashboard/assets/{select-KIT0rC6G.js → select-DBNVLdUu.js} +1 -1
  96. package/dist/dashboard/assets/select-DBNVLdUu.js.gz +0 -0
  97. package/dist/dashboard/assets/{settings-B_2gkePf.js → settings-BAFhDgX8.js} +1 -1
  98. package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
  99. package/dist/dashboard/assets/{stack-D7WOSzcX.js → stack-EMrOMt4I.js} +1 -1
  100. package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
  101. package/dist/dashboard/assets/{state-BBxtKZOQ.js → state-CWoo7ty5.js} +1 -1
  102. package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
  103. package/dist/dashboard/assets/{switch-DNS_D5Ea.js → switch-CCjT6IkF.js} +1 -1
  104. package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
  105. package/dist/dashboard/assets/{useMutation-BgoxdoMk.js → useMutation-DjkLHUcE.js} +1 -1
  106. package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
  107. package/dist/dashboard/index.html +1 -1
  108. package/dist/dashboard/index.html.gz +0 -0
  109. package/dist/dashboard/precompressed-assets.json +54 -53
  110. package/migrations/full-postgres/0024_add_pool_provider_cooldowns.sql +12 -0
  111. package/migrations/full-postgres/0025_add_pool_random_strategy.sql +3 -0
  112. package/migrations/lite-sqlite3/0024_add_pool_provider_cooldowns.sql +12 -0
  113. package/migrations/lite-sqlite3/0025_add_pool_random_strategy.sql +20 -0
  114. package/package.json +96 -96
  115. package/scripts/cli/console-menu.ts +5 -2
  116. package/scripts/cli/tray.ts +21 -6
  117. package/src/boot/app.ts +684 -714
  118. package/src/boot/dependencies.ts +65 -62
  119. package/src/config/cooldowns.ts +16 -0
  120. package/src/config/providers.ts +13 -1
  121. package/src/config/resources.ts +70 -70
  122. package/src/config/security.ts +37 -37
  123. package/src/config/spec.ts +371 -358
  124. package/src/console/cli-tools/contracts.ts +10 -4
  125. package/src/console/console-router.ts +233 -225
  126. package/src/console/domain-registration.ts +398 -381
  127. package/src/console/providers/catalog/catalog-projections.ts +2 -6
  128. package/src/console/providers/catalog/contracts.ts +19 -0
  129. package/src/console/providers/catalog/provider-operations.ts +38 -0
  130. package/src/console/providers/catalog/routes.ts +14 -0
  131. package/src/console/providers/catalog/store-helpers.ts +326 -0
  132. package/src/console/providers/catalog/store.ts +1292 -1563
  133. package/src/console/quota/account-quota-global.ts +2 -2
  134. package/src/console/quota/account-quota-shared.ts +1 -1
  135. package/src/console/quota/account-quota-tenant.ts +35 -12
  136. package/src/console/quota/quota-view.ts +2 -2
  137. package/src/console/routing/pools/contracts.ts +41 -1
  138. package/src/console/routing/pools/operations.ts +183 -4
  139. package/src/console/routing/pools/relay-deploy.ts +17 -4
  140. package/src/console/routing/pools/routes.ts +30 -0
  141. package/src/console/routing/pools/store.ts +98 -8
  142. package/src/main.ts +183 -167
  143. package/src/network/outbound-fetch.ts +124 -208
  144. package/src/network/pool/agent.ts +15 -0
  145. package/src/network/pool/pool-state.ts +7 -0
  146. package/src/network/pool/selector.ts +110 -2
  147. package/src/network/pool-cooldown-store.ts +252 -0
  148. package/src/network/pool-health-machine.ts +5 -3
  149. package/src/network/pool-health.ts +36 -9
  150. package/src/observability/event-query.ts +57 -0
  151. package/src/observability/metrics.ts +467 -467
  152. package/src/observability/runtime-metrics.ts +269 -269
  153. package/src/persistence/health-event-flush.ts +2 -0
  154. package/src/persistence/schema.ts +19 -0
  155. package/src/persistence/usage-flush.ts +8 -3
  156. package/src/protocol/canonical-model.ts +1 -1
  157. package/src/protocol/dsml.ts +377 -0
  158. package/src/protocol/gateway-error.ts +232 -232
  159. package/src/{providers/provider-reasoning.ts → protocol/reasoning.ts} +10 -9
  160. package/src/protocol/request/chat.ts +7 -7
  161. package/src/protocol/request/codex.ts +2 -2
  162. package/src/protocol/request/gemini.ts +1 -1
  163. package/src/protocol/request/messages.ts +6 -6
  164. package/src/protocol/request/responses.ts +7 -7
  165. package/src/protocol/response/dsml.ts +12 -133
  166. package/src/{transport → protocol}/translation/attachment-integrity.ts +2 -2
  167. package/src/{transport → protocol}/translation/cache-controls.ts +1 -1
  168. package/src/{transport → protocol}/translation/capabilities.ts +2 -2
  169. package/src/{transport → protocol}/translation/normalize.ts +1 -1
  170. package/src/{transport → protocol}/translation/quirks.ts +1 -1
  171. package/src/{transport → protocol}/translation/search.ts +1 -1
  172. package/src/{transport → protocol}/translation/thinking.ts +23 -1
  173. package/src/{transport → protocol}/translation/tool-id.ts +1 -1
  174. package/src/{transport → protocol}/translation/tool-repair.ts +2 -2
  175. package/src/protocol/upstream-parsing.ts +176 -0
  176. package/src/providers/adapters/compatible-adapter.ts +11 -2
  177. package/src/providers/authentication/oauth-refresh-service.ts +9 -5
  178. package/src/providers/default-registry.ts +22 -5
  179. package/src/providers/discovery/probe-phases.ts +16 -3
  180. package/src/providers/discovery/probing-service.ts +4 -1
  181. package/src/providers/integrations/anthropic.ts +1 -1
  182. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -4
  183. package/src/providers/integrations/buddy/buddy-chat-shared.ts +141 -8
  184. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -1
  185. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -82
  186. package/src/providers/integrations/buddy/buddy-identity.ts +25 -0
  187. package/src/providers/integrations/buddy/buddy-server-tools.ts +240 -0
  188. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +36 -0
  189. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +6 -31
  190. package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -0
  191. package/src/providers/integrations/buddy/codebuddy/shared.ts +19 -4
  192. package/src/providers/integrations/buddy/turing-device-token.ts +262 -0
  193. package/src/providers/integrations/buddy/workbuddy/provider.ts +2 -0
  194. package/src/providers/integrations/buddy/workbuddy/shared.ts +56 -3
  195. package/src/providers/integrations/claude/claude.ts +1 -1
  196. package/src/providers/integrations/cloudflareai/cloudflareai.ts +1 -1
  197. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  198. package/src/providers/integrations/codex/codex.ts +1 -1
  199. package/src/providers/integrations/factory/factory.ts +1 -1
  200. package/src/providers/integrations/github/github.ts +1 -1
  201. package/src/providers/integrations/grok/grok.ts +1 -1
  202. package/src/providers/integrations/inception/provider.ts +91 -0
  203. package/src/providers/integrations/inferhub.ts +1 -1
  204. package/src/providers/integrations/kiro/kiro-request.ts +1 -1
  205. package/src/providers/integrations/mistral.ts +193 -19
  206. package/src/providers/integrations/ollamacloud-models.ts +50 -0
  207. package/src/providers/integrations/opencode/fingerprint.ts +19 -2
  208. package/src/providers/integrations/opencode/prompt.ts +111 -0
  209. package/src/providers/integrations/opencode/provider.ts +101 -23
  210. package/src/providers/integrations/opencode/tool-definitions.ts +4 -4
  211. package/src/providers/integrations/qoder/qoder-checkin.ts +303 -0
  212. package/src/providers/integrations/qoder/qoder-oauth.ts +12 -1
  213. package/src/providers/integrations/qoder/qoder-realm.ts +20 -0
  214. package/src/providers/integrations/qoder/qoder-text-tools.ts +277 -0
  215. package/src/providers/integrations/qoder/qoder.ts +315 -400
  216. package/src/providers/integrations/shared/install-id.ts +2 -1
  217. package/src/providers/integrations/trae/trae-cache.ts +110 -0
  218. package/src/providers/integrations/trae/trae-oauth.ts +29 -4
  219. package/src/providers/integrations/trae/trae-protocol.ts +2 -1
  220. package/src/providers/integrations/trae/trae.ts +106 -23
  221. package/src/providers/operations/account-error-classification.ts +1684 -0
  222. package/src/providers/operations/account-health-service.ts +27 -1503
  223. package/src/providers/operations/account-reset-service.ts +2 -0
  224. package/src/providers/operations/provider-catalog-service.ts +11 -1
  225. package/src/providers/provider-contracts.ts +7 -4
  226. package/src/providers/provider-metadata.ts +25 -4
  227. package/src/providers/provider-probe.ts +2 -2
  228. package/src/{console → providers}/quota/cache.ts +1 -1
  229. package/src/{console → providers}/quota/refresh.ts +5 -5
  230. package/src/security/cli-client-fingerprint.ts +9 -2
  231. package/src/transport/dispatch/attempt-finalize.ts +114 -12
  232. package/src/transport/dispatch/attempt-loop.ts +3 -0
  233. package/src/transport/dispatch/empty-stream.ts +9 -2
  234. package/src/transport/dispatch/generation.ts +37 -55
  235. package/src/transport/dispatch/handler-deps.ts +32 -0
  236. package/src/transport/dispatch/leases.ts +9 -4
  237. package/src/transport/dispatch/proxy-request.ts +6 -30
  238. package/src/transport/dispatch/responses-compact.ts +22 -43
  239. package/src/transport/dispatch/streaming/finalize.ts +3 -0
  240. package/src/transport/dispatch/streaming/stream.ts +0 -8
  241. package/src/transport/dispatch/systemone.ts +22 -42
  242. package/src/transport/dispatch/token-count.ts +23 -39
  243. package/src/transport/dispatch/upstream.ts +1 -3
  244. package/src/transport/dispatch/websearch.ts +22 -41
  245. package/src/transport/failure-policy.ts +92 -178
  246. package/src/transport/guards/abuse-guard.ts +10 -0
  247. package/src/transport/guards/auth-guard.ts +109 -109
  248. package/src/transport/guards/error-lifecycle.ts +1 -1
  249. package/src/transport/guards/pipeline.ts +220 -220
  250. package/src/transport/guards/request-context.ts +245 -238
  251. package/src/transport/request/degradation.ts +20 -1
  252. package/src/transport/request/inflight.ts +4 -114
  253. package/src/transport/request/preparer.ts +6 -6
  254. package/src/transport/request/state.ts +66 -38
  255. package/src/transport/request/tenant-preferences.ts +1 -1
  256. package/src/transport/routing/route-catalog.ts +75 -43
  257. package/src/transport/routing/router.ts +30 -2
  258. package/src/transport/surface/messages/parse.ts +1 -1
  259. package/src/transport/surface/responses/encode.ts +2 -3
  260. package/src/transport/surface/responses/parse.ts +49 -7
  261. package/src/types/undici.d.ts +33 -0
  262. package/src/workers/buddy-growth-tasks.ts +346 -425
  263. package/src/workers/daily-checkin.ts +17 -6
  264. package/src/workers/quota-refresh-sweep.ts +2 -2
  265. package/src/workers/scheduler.ts +17 -1
  266. package/dist/dashboard/assets/BannedPage-P76QLGJA.js.gz +0 -0
  267. package/dist/dashboard/assets/CliToolDetailPage-DdBggJS9.js.gz +0 -0
  268. package/dist/dashboard/assets/CliToolsPage-aN1iXhhP.js.gz +0 -0
  269. package/dist/dashboard/assets/CombosPage-DE-F1nxZ.js.gz +0 -0
  270. package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js +0 -1
  271. package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js.gz +0 -0
  272. package/dist/dashboard/assets/CustomizationPage-3CUxMy2X.js.gz +0 -0
  273. package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js +0 -1
  274. package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js.gz +0 -0
  275. package/dist/dashboard/assets/LoginPage-B_0FUKWc.js +0 -1
  276. package/dist/dashboard/assets/LoginPage-B_0FUKWc.js.gz +0 -0
  277. package/dist/dashboard/assets/ModelPicker-DKVslqG3.js.gz +0 -0
  278. package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js +0 -1
  279. package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js.gz +0 -0
  280. package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js +0 -3
  281. package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js.gz +0 -0
  282. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +0 -1
  283. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  284. package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js +0 -1
  285. package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js.gz +0 -0
  286. package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js +0 -7
  287. package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js.gz +0 -0
  288. package/dist/dashboard/assets/QuotaPage-BjldQwIU.js.gz +0 -0
  289. package/dist/dashboard/assets/SettingsPage-DUIoLRxk.js.gz +0 -0
  290. package/dist/dashboard/assets/SetupPage-_bCaTM0v.js +0 -1
  291. package/dist/dashboard/assets/SetupPage-_bCaTM0v.js.gz +0 -0
  292. package/dist/dashboard/assets/SortableList-fI7JkdIB.js.gz +0 -0
  293. package/dist/dashboard/assets/StudioPage-CHVZqskZ.js.gz +0 -0
  294. package/dist/dashboard/assets/UsagePage-DiFBymHj.js +0 -56
  295. package/dist/dashboard/assets/UsagePage-DiFBymHj.js.gz +0 -0
  296. package/dist/dashboard/assets/api-keys-DFY_fZU4.js.gz +0 -0
  297. package/dist/dashboard/assets/app-BOJ1_xUB.css +0 -2
  298. package/dist/dashboard/assets/app-BOJ1_xUB.css.gz +0 -0
  299. package/dist/dashboard/assets/assert-CwdnZARt.js.gz +0 -0
  300. package/dist/dashboard/assets/badge-Dr-uf0Au.js.gz +0 -0
  301. package/dist/dashboard/assets/banner-BddmMdoS.js.gz +0 -0
  302. package/dist/dashboard/assets/card-CK0PXWlX.js.gz +0 -0
  303. package/dist/dashboard/assets/cli-tools-CcB6mGh2.js.gz +0 -0
  304. package/dist/dashboard/assets/clipboard-button-DGb2P9Yx.js.gz +0 -0
  305. package/dist/dashboard/assets/cog-CdWhsajs.js +0 -1
  306. package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
  307. package/dist/dashboard/assets/dialog-LkfuL50u.js.gz +0 -0
  308. package/dist/dashboard/assets/drawer-ZbEZ_84c.js.gz +0 -0
  309. package/dist/dashboard/assets/entry-BJfsVMvA.js.gz +0 -0
  310. package/dist/dashboard/assets/entry-CWBY5A0f.js.gz +0 -0
  311. package/dist/dashboard/assets/entry-CvC1-Zqx.js.gz +0 -0
  312. package/dist/dashboard/assets/github-badge-BxpznFLt.js.gz +0 -0
  313. package/dist/dashboard/assets/index-CDzP3ovJ.js.gz +0 -0
  314. package/dist/dashboard/assets/inline-Ae13MQ5w.js.gz +0 -0
  315. package/dist/dashboard/assets/input-D23fo7cp.js.gz +0 -0
  316. package/dist/dashboard/assets/layout-CSsxjj5P.js.gz +0 -0
  317. package/dist/dashboard/assets/list-row-dmbwZMDY.js.gz +0 -0
  318. package/dist/dashboard/assets/live-BG1oUsZZ.js.gz +0 -0
  319. package/dist/dashboard/assets/mutation-DSF-e4ho.js.gz +0 -0
  320. package/dist/dashboard/assets/numbers-Bm_aECY7.js.gz +0 -0
  321. package/dist/dashboard/assets/query-keys-CF56nYIV.js.gz +0 -0
  322. package/dist/dashboard/assets/routing-CeA71n7G.js.gz +0 -0
  323. package/dist/dashboard/assets/select-KIT0rC6G.js.gz +0 -0
  324. package/dist/dashboard/assets/settings-B_2gkePf.js.gz +0 -0
  325. package/dist/dashboard/assets/stack-D7WOSzcX.js.gz +0 -0
  326. package/dist/dashboard/assets/state-BBxtKZOQ.js.gz +0 -0
  327. package/dist/dashboard/assets/switch-DNS_D5Ea.js.gz +0 -0
  328. package/dist/dashboard/assets/useMutation-BgoxdoMk.js.gz +0 -0
  329. /package/dist/dashboard/assets/{app-CaXZ9SCn.js → app-BaWd0puM.js} +0 -0
  330. /package/dist/dashboard/assets/{app-CaXZ9SCn.js.gz → app-BaWd0puM.js.gz} +0 -0
@@ -1,120 +1,10 @@
1
1
  /**
2
- * In-flight provider-dispatch registry — the single source of truth for the
3
- * live "in flight" gauge. One entry per request that has actually acquired
4
- * provider-dispatch leases, keyed by `requestId`, carrying the client IP
5
- * captured at ingress and the hard deadline the flight must not outlive.
6
- *
7
- * The registry is *owned* by the `ProxyRequestStateStore` (`state.ts`), which
8
- * is also the one place that evicts a request, so a flight cannot outlive its
9
- * state. A separate process-global counter alongside the store's
10
- * `liveControllers` map would be a second registry able to disagree with it: a
11
- * flight the store had already torn down would stay counted there forever.
12
- *
13
- * The store hands each entry an `expiresAtMs` (the request's hard deadline). A
14
- * periodic backstop sweep reads `overdue()` and force-releases any flight whose
15
- * deadline has passed, so the gauge settles even if an abort path never fires.
16
- *
17
- * Pub/sub lets the console push the live snapshot over SSE instead of polling.
18
- * Process-local: each gateway instance reports its own flights.
2
+ * The live "in flight" snapshot shape, shared by the store, the console live
3
+ * domain, and the dashboard. Derived from the request state store's own
4
+ * `liveControllers` map — the single registry that already owns a request's
5
+ * lifetime — so the gauge cannot disagree with what is actually running.
19
6
  */
20
- import { metrics } from "../../observability/metrics";
21
-
22
7
  export interface InFlightSnapshot {
23
8
  readonly inFlight: number;
24
9
  readonly uniqueIps: number;
25
- /**
26
- * How long the longest-lived flight has been tracked, in milliseconds.
27
- *
28
- * The count alone cannot tell a healthy queue from a leak: six flights
29
- * could be six requests in progress or six that never finished. The age
30
- * separates them — a flight far past its own deadline is the backstop's
31
- * job, so a growing age with a flat count means the sweep is not running.
32
- */
33
- readonly oldestAgeMs: number;
34
- }
35
-
36
- interface Flight {
37
- readonly clientIp: string;
38
- /** Hard deadline after which the backstop sweep releases this flight. */
39
- expiresAtMs: number;
40
- /** When this flight was tracked, for the age reported in a snapshot. */
41
- readonly startedAtMs: number;
42
- }
43
-
44
- export interface InFlightRegistry {
45
- /** Register a flight once its dispatch leases are acquired. Idempotent per id. */
46
- track(requestId: string, clientIp: string, expiresAtMs: number): void;
47
- /** Release a flight. Idempotent; an unknown id is a no-op. */
48
- untrack(requestId: string): void;
49
- /** Re-arm a flight's hard deadline (streaming swaps the pre-stream budget). */
50
- extend(requestId: string, expiresAtMs: number): void;
51
- snapshot(): InFlightSnapshot;
52
- count(): number;
53
- /** Ids whose deadline plus the sweep grace has elapsed — the backstop's worklist. */
54
- overdue(nowMs: number, graceMs: number): readonly string[];
55
- subscribe(listener: (snapshot: InFlightSnapshot) => void): () => void;
56
- }
57
-
58
- export function createInFlightRegistry(): InFlightRegistry {
59
- const flights = new Map<string, Flight>();
60
- const listeners = new Set<(snapshot: InFlightSnapshot) => void>();
61
-
62
- const snapshot = (): InFlightSnapshot => {
63
- let uniqueIps = 0;
64
- let oldest = 0;
65
- const now = Date.now();
66
- const seen = new Set<string>();
67
- for (const flight of flights.values()) {
68
- if (!seen.has(flight.clientIp)) {
69
- seen.add(flight.clientIp);
70
- uniqueIps += 1;
71
- }
72
- const age = now - flight.startedAtMs;
73
- if (age > oldest) oldest = age;
74
- }
75
- return { inFlight: flights.size, uniqueIps, oldestAgeMs: oldest };
76
- };
77
-
78
- const publish = (): void => {
79
- const current = snapshot();
80
- metrics.proxy_in_flight.set(current.inFlight);
81
- for (const listener of listeners) listener(current);
82
- };
83
-
84
- return {
85
- track(requestId, clientIp, expiresAtMs) {
86
- flights.set(requestId, {
87
- clientIp: clientIp.length > 0 ? clientIp : "unknown",
88
- expiresAtMs,
89
- startedAtMs: Date.now(),
90
- });
91
- publish();
92
- },
93
- untrack(requestId) {
94
- if (!flights.delete(requestId)) return;
95
- publish();
96
- },
97
- extend(requestId, expiresAtMs) {
98
- const flight = flights.get(requestId);
99
- if (flight === undefined) return;
100
- flight.expiresAtMs = expiresAtMs;
101
- },
102
- snapshot,
103
- count() {
104
- return flights.size;
105
- },
106
- overdue(nowMs, graceMs) {
107
- const ids: string[] = [];
108
- for (const [requestId, flight] of flights) {
109
- if (nowMs >= flight.expiresAtMs + graceMs) ids.push(requestId);
110
- }
111
- return ids;
112
- },
113
- subscribe(listener) {
114
- listeners.add(listener);
115
- return () => {
116
- listeners.delete(listener);
117
- };
118
- },
119
- };
120
10
  }
@@ -5,14 +5,14 @@ import type { ApiKeyAdmissionService } from "../../security/admission/service";
5
5
  import type { RouteCandidate as RouteCandidate, InMemoryRouteSnapshotService, RoutePlan } from "../routing/route-model";
6
6
  import type { RouteContinuationAffinity } from "../routing/route-model";
7
7
  import { resolveAliasTarget, type RoutingEngine } from "../routing/router";
8
- import { projectForRoute, routeCapabilitiesFor } from "../translation/capabilities";
9
- import type { RequiredCapability } from "../translation/capabilities";
8
+ import { projectForRoute, routeCapabilitiesFor } from "../../protocol/translation/capabilities";
9
+ import type { RequiredCapability } from "../../protocol/translation/capabilities";
10
10
  import { isModelAllowed, type ResolvedApiKey } from "../../security/api-key-auth";
11
11
  import { allowsCliToolMappings } from "../../security/cli-client-fingerprint";
12
- import { dropIncompleteToolRounds, repairRequestToolCalls } from "../translation/tool-repair";
13
- import { sanitizeRequestToolIds } from "../translation/tool-id";
14
- import { dropCorruptAttachments } from "../translation/attachment-integrity";
15
- import { parseThinkingSuffix, withThinkingSuffixIntent } from "../translation/thinking";
12
+ import { dropIncompleteToolRounds, repairRequestToolCalls } from "../../protocol/translation/tool-repair";
13
+ import { sanitizeRequestToolIds } from "../../protocol/translation/tool-id";
14
+ import { dropCorruptAttachments } from "../../protocol/translation/attachment-integrity";
15
+ import { parseThinkingSuffix, withThinkingSuffixIntent } from "../../protocol/translation/thinking";
16
16
  import { nativeServicePathFor } from "../contracts";
17
17
  import { log } from "../../observability/logger";
18
18
  import { BUDDY_PROVIDER_IDS } from "../../providers/provider-metadata";
@@ -5,8 +5,8 @@ import type { ResolvedApiKey } from "../../security/api-key-auth";
5
5
  import type { PreparedProxyRequest } from "./preparer";
6
6
  import type { PayloadCaptureState, TelemetryPayloadCapture } from "../../observability/payload-capture";
7
7
  import type { TelemetryPayloadDepth, TelemetryPayloadMode } from "../../console/settings/contracts";
8
- import { createInFlightRegistry, type InFlightSnapshot } from "./inflight";
9
8
  import { log } from "../../observability/logger";
9
+ import type { InFlightSnapshot } from "./inflight";
10
10
  import { fastPathname } from "../pathname";
11
11
 
12
12
  export interface ProxyRequestOutcome {
@@ -182,15 +182,39 @@ export interface RequestTracker {
182
182
  * shutdown coordination, which waits on tracked requests (or aborts them
183
183
  * past the grace window) instead of relying on connection close events.
184
184
  */
185
+ /**
186
+ * Minimal pub/sub over a store's snapshot. Polling already re-reads
187
+ * `inFlightSnapshot()`, so this only pushes the same derived value to the
188
+ * console's SSE stream; no second registry behind it.
189
+ */
190
+ function subscribeInFlightSnapshot(
191
+ store: { inFlightSnapshot(): InFlightSnapshot },
192
+ listener: (snapshot: InFlightSnapshot) => void,
193
+ ): () => void {
194
+ const timers = new Set<ReturnType<typeof setTimeout>>();
195
+ const push = (): void => listener(store.inFlightSnapshot());
196
+ push();
197
+ const handle = setInterval(push, 1_000);
198
+ (handle as unknown as { unref?: () => void })?.unref?.();
199
+ return () => {
200
+ clearInterval(handle);
201
+ for (const t of timers) clearTimeout(t);
202
+ timers.clear();
203
+ };
204
+ }
205
+
185
206
  export class ProxyRequestStateStore {
186
207
  private readonly states = new WeakMap<Request, ProxyRequestState>();
187
208
  private readonly liveControllers = new Map<string, AbortController>();
188
209
  /**
189
- * The in-flight gauge lives here, not in a free-standing module: this store
190
- * is the one place that owns a request's lifetime and evicts it, so the
191
- * gauge can never count a request the store has already torn down.
210
+ * The in-flight gauge is derived from the same map that owns a request's
211
+ * lifetime. There is no second registry: a count that disagreed with
212
+ * `liveControllers` was the ghost — entries that outlived the request they
213
+ * described. The deadline each flight last armed is kept alongside, so the
214
+ * backstop sweep still finds requests whose abort path never fired.
192
215
  */
193
- private readonly inFlight = createInFlightRegistry();
216
+ private readonly inFlightDeadlines = new Map<string, number>();
217
+ private readonly inFlightIps = new Map<string, string>();
194
218
 
195
219
  constructor(private readonly tracker?: RequestTracker) {}
196
220
 
@@ -199,43 +223,38 @@ export class ProxyRequestStateStore {
199
223
  }
200
224
 
201
225
  inFlightSnapshot(): InFlightSnapshot {
202
- return this.inFlight.snapshot();
226
+ let uniqueIps = 0;
227
+ const seen = new Set<string>();
228
+ for (const ip of this.inFlightIps.values()) {
229
+ if (!seen.has(ip)) {
230
+ seen.add(ip);
231
+ uniqueIps += 1;
232
+ }
233
+ }
234
+ return { inFlight: this.inFlightDeadlines.size, uniqueIps };
203
235
  }
204
236
 
205
237
  /** Live flight count for this store. */
206
238
  inFlightCount(): number {
207
- return this.inFlight.count();
239
+ return this.inFlightDeadlines.size;
208
240
  }
209
241
 
210
242
  subscribeInFlight(listener: (snapshot: InFlightSnapshot) => void): () => void {
211
- return this.inFlight.subscribe(listener);
243
+ return subscribeInFlightSnapshot(this, listener);
212
244
  }
213
245
 
214
246
  /**
215
- * Backstop worklist: ids whose hard deadline plus `graceMs` has elapsed while
216
- * still tracked. A correctly-released request never appears here — this is
217
- * the safety net for an abort path that did not fire (a half-closed socket
218
- * that neither pulls, cancels, nor aborts).
219
- */
220
- overdueInFlight(nowMs: number, graceMs: number): readonly string[] {
221
- return this.inFlight.overdue(nowMs, graceMs);
222
- }
223
-
224
- /**
225
- * Releases every flight past its deadline. Two tiers, because aborting the
226
- * controller unwinds the fetch but only *releases* the flight if some path
227
- * observes the abort:
228
- *
229
- * - Past `expiresAtMs + graceMs`: abort the controller, which unblocks a
230
- * paused read and fires the release in `dispatch/proxy-request`.
231
- * - Past `expiresAtMs + hardGraceMs`: the abort did not release it, so
232
- * force-drop the gauge entry. The fetch is already torn down by the abort,
233
- * so leaving the entry would only mean a permanently wrong number — the
234
- * exact defect this backstop exists to end. Logged, never silent.
247
+ * Releases every flight past its deadline. Aborting the controller unwinds
248
+ * the fetch and lets the request's own `cleanup` run, which drops the
249
+ * flight's entry with it. A request nothing observes — a half-closed socket
250
+ * that neither pulls, cancels, nor aborts — never runs that cleanup, so
251
+ * after a further grace window the sweep drops the entry itself: the fetch
252
+ * is already torn down by the abort, and a permanently wrong number is the
253
+ * exact defect this backstop exists to end. Logged, never silent.
235
254
  */
236
255
  sweepOverdueInFlight(nowMs: number, graceMs: number, hardGraceMs: number): number {
237
- const released = this.inFlight.overdue(nowMs, graceMs);
238
- for (const requestId of released) {
256
+ for (const [requestId, deadline] of this.inFlightDeadlines) {
257
+ if (nowMs < deadline + graceMs) continue;
239
258
  const controller = this.liveControllers.get(requestId);
240
259
  try {
241
260
  if (controller && !controller.signal.aborted) {
@@ -246,14 +265,19 @@ export class ProxyRequestStateStore {
246
265
  } catch {
247
266
  }
248
267
  }
249
- const stuck = this.inFlight.overdue(nowMs, hardGraceMs);
250
- for (const requestId of stuck) {
268
+ let forced = 0;
269
+ for (const [requestId, deadline] of this.inFlightDeadlines) {
270
+ if (nowMs < deadline + hardGraceMs) continue;
271
+ this.inFlightDeadlines.delete(requestId);
272
+ this.inFlightIps.delete(requestId);
273
+ forced += 1;
274
+ }
275
+ if (forced > 0) {
251
276
  log.error(
252
- `[inflight-backstop] force-releasing request ${requestId}: abort did not release it within ${hardGraceMs}ms`,
277
+ `[inflight-backstop] force-releasing ${forced} flight(s): abort did not release them within ${hardGraceMs}ms`,
253
278
  );
254
- this.inFlight.untrack(requestId);
255
279
  }
256
- return stuck.length > 0 ? stuck.length : released.length;
280
+ return forced;
257
281
  }
258
282
 
259
283
  abortAll(reason?: unknown): void {
@@ -325,7 +349,8 @@ export class ProxyRequestStateStore {
325
349
  startProviderFlight: () => {
326
350
  if (cleaned || providerFlightStarted) return;
327
351
  providerFlightStarted = true;
328
- this.inFlight.track(requestId, state.clientIdentity?.address ?? "unknown", state.deadlineMs);
352
+ this.inFlightDeadlines.set(requestId, state.deadlineMs);
353
+ this.inFlightIps.set(requestId, state.clientIdentity?.address || "unknown");
329
354
  },
330
355
  releaseIngressBody: () => {
331
356
  state.ingressBody = undefined;
@@ -342,7 +367,7 @@ export class ProxyRequestStateStore {
342
367
  // request that legitimately extends its budget must move the flight's
343
368
  // deadline too — otherwise the sweep would force-release a healthy
344
369
  // long stream the moment the pre-stream budget elapsed.
345
- this.inFlight.extend(requestId, state.deadlineMs);
370
+ if (providerFlightStarted) this.inFlightDeadlines.set(requestId, state.deadlineMs);
346
371
  armDeadline(ms);
347
372
  },
348
373
  cleanup: () => {
@@ -351,7 +376,10 @@ export class ProxyRequestStateStore {
351
376
  // Every step is guarded: one throwing cleanup must not skip the
352
377
  // remaining cleanups, the abort, or the map eviction (leak).
353
378
  try {
354
- if (providerFlightStarted) this.inFlight.untrack(state.requestId);
379
+ if (providerFlightStarted) {
380
+ this.inFlightDeadlines.delete(state.requestId);
381
+ this.inFlightIps.delete(state.requestId);
382
+ }
355
383
  } catch {
356
384
  }
357
385
  try {
@@ -10,7 +10,7 @@
10
10
  import type { CartethyiaDatabase } from "../../persistence/db-handle";
11
11
  import type { CanonicalRequest } from "../../protocol/canonical-model";
12
12
  import type { PreparedProxyRequest } from "./preparer";
13
- import { normalizeThinkingConfig } from "../translation/thinking";
13
+ import { normalizeThinkingConfig } from "../../protocol/translation/thinking";
14
14
  import { compressRequest } from "./rtk/compress-request";
15
15
  import { preferencesReaderFor } from "../../persistence/tenant-preferences";
16
16
  import { resolveUpstreamTimeoutMs } from "../../config/timeouts";
@@ -35,7 +35,19 @@ import { providerHasAdapterUserAgent, providerUsesBespokeWire } from "../../prov
35
35
 
36
36
  const CLAUDE_MODEL_FAMILIES = new Set(["opus", "sonnet", "haiku", "fable", "mythos"]);
37
37
 
38
+ /** Codex's built-in (non-routeable) model spellings worth matching. */
39
+ const CODEX_REVIEW_MODELS = new Set(["codex-auto-review"]);
40
+
38
41
  export function cliMappingSourceKeys(toolId: string, sourceModel: string): readonly string[] {
42
+ if (toolId === "codex") {
43
+ const normalized = sourceModel.trim().toLowerCase();
44
+ if (CODEX_REVIEW_MODELS.has(normalized)) {
45
+ // Guardian's auto-review model is one fixed spelling; surface the full
46
+ // spelling plus the bare family so a slot mapped to either one hits.
47
+ return [...new Set([sourceModel, "codex-auto-review"])];
48
+ }
49
+ return [sourceModel];
50
+ }
39
51
  if (toolId !== "claude") return [sourceModel];
40
52
  const normalized = sourceModel.trim().toLowerCase();
41
53
  const family =
@@ -199,23 +211,33 @@ export function capabilityProfileCacheSize(): number {
199
211
  return capabilityProfileCache.size;
200
212
  }
201
213
 
202
- /** Resolves route identity only for bundled adapters without a native User-Agent builder. */
214
+ /** Resolves route identity: bundled adapters without a native User-Agent builder, plus custom providers. */
203
215
  export function resolveRouteUserAgent(
204
216
  providerId: string,
205
217
  tenantUserAgent: string | undefined,
206
218
  globalUserAgent: string | undefined,
207
219
  ): string | undefined {
208
- if (!isBundledProviderId(providerId) || providerHasAdapterUserAgent(providerId)) return undefined;
209
- // A provider whose operator set no identity of their own gets the Claude Code
210
- // CLI identity, resolved live so the stamped version tracks discovery. This
211
- // used to be a frozen `codex_cli_rs` literal, which named a different vendor's
212
- // client on requests that mostly reached neither.
213
- const selected = resolveTenantOverride(
214
- tenantUserAgent,
215
- globalUserAgent,
216
- buildClaudeCliUserAgent(),
217
- );
218
- return selected.trim() === "" ? undefined : selected;
220
+ if (isBundledProviderId(providerId)) {
221
+ // A bundled adapter that frames its own identity headers never consumes a
222
+ // route User-Agent (Command Code, Qoder, buddy, ...), so it never gets one
223
+ // — the control is hidden for it too.
224
+ if (providerHasAdapterUserAgent(providerId)) return undefined;
225
+ // Bundled API-key providers: an operator who set no identity of their own
226
+ // gets the Claude Code CLI identity, resolved live so the stamped version
227
+ // tracks discovery. This used to be a frozen `codex_cli_rs` literal, which
228
+ // named a different vendor's client on requests that mostly reached neither.
229
+ const selected = resolveTenantOverride(
230
+ tenantUserAgent,
231
+ globalUserAgent,
232
+ buildClaudeCliUserAgent(),
233
+ );
234
+ return selected.trim() === "" ? undefined : selected;
235
+ }
236
+ // Custom (BYOK) providers: only an explicit operator choice rides on the
237
+ // route; the adapter owns the default otherwise (see `registerByokProviders`).
238
+ // An explicit empty choice ("None") is preserved so the adapter can honor it.
239
+ const selected = resolveTenantOverride(tenantUserAgent, globalUserAgent, undefined);
240
+ return selected === undefined ? undefined : selected.trim();
219
241
  }
220
242
 
221
243
  /** One persisted model row ready for candidate construction. */
@@ -741,43 +763,53 @@ class RouteCatalogRepository {
741
763
  : {}),
742
764
  };
743
765
  if (account.status === "cooldown") {
744
- // Expired cooldowns read as healthy here without a fire-and-forget
745
- // write: AccountHealthSweeper materializes the recovery every 30s,
746
- // so the builder stays a pure read (no UPDATE racing the request).
766
+ // A row that still says `cooldown` stays excluded until the sweeper
767
+ // flips it back to `active`. An *expired* deadline used to read as
768
+ // healthy here (the comment below's "pure read" reasoning), which let
769
+ // routing redial the account in the window between the deadline
770
+ // elapsing and the next 30s sweep — reproducing the refusal that
771
+ // cooled it and logging a fresh `cooldown → cooldown` row each time
772
+ // (the operator's "still hit a cooled-down account" report). The
773
+ // sweep is the only exit from `cooldown`; until it runs the row is
774
+ // not proof the account can serve.
747
775
  //
748
776
  // `forceDisableCooldown` skips the exclusion entirely. The cooldown
749
777
  // is still real and still recorded — it simply stops deciding
750
778
  // routing. That is what makes the setting reversible: nothing was
751
779
  // written, so turning it off needs no cleanup, and Health still
752
780
  // reports the truth about the account.
753
- if (account.cooldownUntil && account.cooldownUntil.getTime() > Date.now()) {
754
- const overrideActive =
755
- rowTenantId === null
756
- ? anyTenantOverrides
757
- : cooldownOverrideTenants.has(rowTenantId);
758
- if (overrideActive) {
759
- // The account is still cooling — this only stops it from being
760
- // excluded. `plan()` reads `cooldown_until` to answer the
761
- // all-cooling case, so it stays populated either way.
762
- //
763
- // Neither `health_status` nor `cooldown_kind` is set: the
764
- // evaluator excludes a hard cooldown on `health_status ===
765
- // "cooldown"` alone, so stamping it here would keep excluding
766
- // the very accounts the override exists to reach — a spent quota
767
- // or a policy block, which are the ones an operator actually
768
- // wants to override.
769
- candidate.cooldown_until = account.cooldownUntil.getTime();
770
- } else {
771
- candidate.health_status = "cooldown";
772
- candidate.cooldown_kind = hardCooldownCategory(account.lastErrorCategory)
773
- ? "hard"
774
- : "soft";
775
- // The deadline rides with the exclusion it explains. `plan()`
776
- // reads it to answer the all-cooling case with the real reset
777
- // instead of a bare 429, so a client backs off for the window
778
- // that is actually open rather than inventing one.
779
- candidate.cooldown_until = account.cooldownUntil.getTime();
780
- }
781
+ const overrideActive =
782
+ rowTenantId === null
783
+ ? anyTenantOverrides
784
+ : cooldownOverrideTenants.has(rowTenantId);
785
+ const accountDeadline = account.cooldownUntil;
786
+ const unexpired =
787
+ accountDeadline !== null && accountDeadline.getTime() > Date.now();
788
+ if (overrideActive) {
789
+ // The account is still cooling — this only stops it from being
790
+ // excluded. `plan()` reads `cooldown_until` to answer the
791
+ // all-cooling case, so it stays populated either way.
792
+ //
793
+ // Neither `health_status` nor `cooldown_kind` is set: the
794
+ // evaluator excludes a hard cooldown on `health_status ===
795
+ // "cooldown"` alone, so stamping it here would keep excluding
796
+ // the very accounts the override exists to reach — a spent quota
797
+ // or a policy block, which are the ones an operator actually
798
+ // wants to override.
799
+ if (unexpired) candidate.cooldown_until = accountDeadline!.getTime();
800
+ } else {
801
+ candidate.health_status = "cooldown";
802
+ candidate.cooldown_kind = hardCooldownCategory(account.lastErrorCategory)
803
+ ? "hard"
804
+ : "soft";
805
+ // The deadline rides with the exclusion it explains. `plan()`
806
+ // reads it to answer the all-cooling case with the real reset
807
+ // instead of a bare 429, so a client backs off for the window
808
+ // that is actually open rather than inventing one. An expired
809
+ // deadline carries no reset — the sweep is about to recover the
810
+ // row, and quoting a past instant would tell the client to retry
811
+ // "immediately" for a wait that is already over.
812
+ if (unexpired) candidate.cooldown_until = accountDeadline!.getTime();
781
813
  }
782
814
  }
783
815
 
@@ -21,7 +21,7 @@ import {
21
21
  import {
22
22
  candidateSupportsRequest,
23
23
  type RequiredCapability,
24
- } from "../translation/capabilities";
24
+ } from "../../protocol/translation/capabilities";
25
25
  import { planAllowsCandidate } from "./plan-gate";
26
26
  import { InMemoryAdmissionController, admissionKey } from "./admission";
27
27
  import { ReservationManager } from "./reservations";
@@ -32,7 +32,7 @@ import {
32
32
  routeCandidateIndex,
33
33
  } from "./candidate-index";
34
34
  import { totalRemainingCredit, totalRemainingPercent, type ProviderQuotaWindow } from "../../providers/quota/quota-contracts";
35
- import { getCachedQuotaEntry, quotaLens } from "../../console/quota/cache";
35
+ import { getCachedQuotaEntry, quotaLens } from "../../providers/quota/cache";
36
36
  import type { RedisClient } from "../../persistence/redis";
37
37
  import { sharedClaudeSlowLanes } from "../../providers/integrations/claude/claude-slow-lane";
38
38
 
@@ -579,6 +579,34 @@ export class RoutingEngine {
579
579
  },
580
580
  );
581
581
  }
582
+ // The pinned account owns the live session, so it is the only correct
583
+ // target for this tool result — but health still applies. Forcing a
584
+ // session whose account is hard-cooling (quota exhausted) reproduces the
585
+ // refusal that cooled it, which is how "still hit a cooled-down account"
586
+ // leaked through continuation affinity. Evaluate the pin before
587
+ // returning: time-bounded cooling answers 429 with the real reset; an
588
+ // account that is disabled/locked answers a clear 400.
589
+ const pinnedDecisions = pinned.map((candidate) => this.eligibility.evaluate(candidate));
590
+ const blocked = pinnedDecisions.find((decision) => !decision.eligible);
591
+ if (blocked !== undefined) {
592
+ const reason = blocked.reason;
593
+ if (
594
+ reason === "cooldown_hard" ||
595
+ reason === "model_cooldown" ||
596
+ (reason === "quota_exhausted" && typeof blocked.cooldown_until === "number")
597
+ ) {
598
+ throw accountsRateLimitedError(requestedModel, resolved.model, earliestResetMs(pinnedDecisions));
599
+ }
600
+ throw new GatewayError(
601
+ "invalid_request",
602
+ 400,
603
+ "This tool result belongs to an account that is no longer routable. Reconnect the account, or resend the complete conversation.",
604
+ {
605
+ providerId: continuationAffinity.providerId,
606
+ accountId: continuationAffinity.providerAccountId,
607
+ },
608
+ );
609
+ }
582
610
  return {
583
611
  revision: snapshot.revision,
584
612
  candidates: pinned,
@@ -6,7 +6,7 @@ import { parseToolDefinition } from "../dialects";
6
6
  import { canContainToolResult } from "../../../protocol/canonical-model";
7
7
  import { MessagesLedgerError } from "./errors";
8
8
  import { finiteNumber, stringValue } from "../../../protocol/codecs";
9
- import { LEVEL_TO_BUDGET } from "../../translation/thinking";
9
+ import { LEVEL_TO_BUDGET } from "../../../protocol/translation/thinking";
10
10
 
11
11
  export interface MessagesParseOptions {
12
12
  /** Explicit Claude model generation (for example `4.6` or `4.7`). */
@@ -339,8 +339,7 @@ export class ResponsesEventEncoder extends SurfaceStreamEncoder<CanonicalEvent,
339
339
  summary: [],
340
340
  content: [],
341
341
  };
342
- if (content.encrypted_content !== undefined) item.encrypted_content = content.encrypted_content;
343
- this.open = {
342
+ if (typeof content.encrypted_content === "string") item.encrypted_content = content.encrypted_content; this.open = {
344
343
  kind: "reasoning",
345
344
  item,
346
345
  itemId,
@@ -501,7 +500,7 @@ export class ResponsesEventEncoder extends SurfaceStreamEncoder<CanonicalEvent,
501
500
  );
502
501
  summaryChunks.push(delta);
503
502
  }
504
- if (content.encrypted_content !== undefined)
503
+ if (typeof content.encrypted_content === "string")
505
504
  events.push(
506
505
  this.emit("response.reasoning.encrypted_content", {
507
506
  item_id: this.open?.itemId,