cartethyia 2.8.0 → 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 (348) hide show
  1. package/.env.example +3 -3
  2. package/dist/dashboard/assets/{BannedPage-Cc0vKpMG.js → BannedPage-Dq530awD.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-Bqj2aa1T.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-CXsaOAO2.js → CliToolsPage-DaZZ3cqP.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-BeLpJ4ma.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-DnV0uZ3m.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-CBxAjo63.js → CustomizationPage-7xT5yg2E.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
  14. package/dist/dashboard/assets/{HealthEventsModal-B5yEE0k6.js → HealthEventsModal-Clq8E9cW.js} +1 -1
  15. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
  16. package/dist/dashboard/assets/{InjectSystemPromptPanel-BWFLECmz.js → InjectSystemPromptPanel-6Teoq9Se.js} +1 -1
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-CgoPc5B9.js → LoginPage-CZuI0MAR.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
  20. package/dist/dashboard/assets/{ModelPicker-D9bFQern.js → ModelPicker-C1hNTlkr.js} +1 -1
  21. package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-B_p4tL8k.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-D_0PiZtg.js → ProviderIcon-Ddhm1InL.js} +1 -1
  27. package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
  28. package/dist/dashboard/assets/{ProvidersPage-CLI2bJTK.js → ProvidersPage-C3fYuu6i.js} +1 -1
  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-DA-a1iSN.js → QuotaPage-D1RV_2BX.js} +1 -1
  33. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-CctTga2C.js → SettingsPage-BzZAvYSX.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
  36. package/dist/dashboard/assets/{SetupPage-febLU4r1.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-M6fvR5Wz.js → StudioPage-BjXFGSK4.js} +1 -1
  39. package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
  40. package/dist/dashboard/assets/{UsagePage-nyeuRqz9.js → UsagePage-CP8F8BN7.js} +1 -1
  41. package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BFOlvrJC.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-CqTLw5e7.js → assert-BMcj1xZE.js} +1 -1
  47. package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-C83EqLB0.js → badge-DkRUuVfz.js} +1 -1
  49. package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-D1xn7pIe.js → banner-CDjW841Q.js} +1 -1
  51. package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-DU3Lq5Lm.js → card-Bto5UlL9.js} +1 -1
  53. package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-Dw1U_drK.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-CEL5Sg_J.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-BjFR5UHy.js → dialog-DL0Zftwv.js} +1 -1
  59. package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-HvBW0AjI.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-CECVELyy.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-DYYofQ7W.js → entry-CGvdNU6r.js} +1 -1
  65. package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
  66. package/dist/dashboard/assets/{entry-BSu3gFlt.js → entry-DScKYvZj.js} +3 -3
  67. package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
  68. package/dist/dashboard/assets/{formatters-DLW4i8zK.js → formatters-BS3DZlZj.js} +1 -1
  69. package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-1-fCqe64.js → index-SQnvvTWX.js} +2 -2
  71. package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-DGvU2oLK.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-iNnO1X2K.js → input-Bfx0EzRc.js} +1 -1
  75. package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-0q8UGypb.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-C0w2bQjJ.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-M5aJ_xCa.js → live-CbVTdX9V.js} +1 -1
  81. package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
  82. package/dist/dashboard/assets/{network-BKeQGT3K.js → network-JGvDveof.js} +1 -1
  83. package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
  84. package/dist/dashboard/assets/{numbers-CivAk3da.js → numbers-Dw6kiew-.js} +1 -1
  85. package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
  86. package/dist/dashboard/assets/{pagination-CpzvI5ep.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/{routing-BFQX2VBc.js → routing-8jZr-3WE.js} +1 -1
  91. package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
  92. package/dist/dashboard/assets/{select-CXbck0jG.js → select-oO1FxMVb.js} +1 -1
  93. package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
  94. package/dist/dashboard/assets/{settings-C_XvMHh9.js → settings-CLr3kdIx.js} +1 -1
  95. package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
  96. package/dist/dashboard/assets/{stack-DggRiCxE.js → stack-Y3pDteZr.js} +1 -1
  97. package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
  98. package/dist/dashboard/assets/{state-ClFzfbYp.js → state-CArX_Iak.js} +1 -1
  99. package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
  100. package/dist/dashboard/assets/{switch-CTIFxKqS.js → switch-DwlwmDO-.js} +1 -1
  101. package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
  102. package/dist/dashboard/assets/{useMutation-BClG3yWj.js → useMutation-DJ9GYEgf.js} +1 -1
  103. package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
  104. package/dist/dashboard/index.html +2 -2
  105. package/dist/dashboard/index.html.gz +0 -0
  106. package/dist/dashboard/precompressed-assets.json +51 -51
  107. package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
  108. package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
  109. package/package.json +1 -1
  110. package/src/boot/app.ts +1 -1
  111. package/src/boot/dependencies.ts +4 -6
  112. package/src/config/providers.ts +6 -5
  113. package/src/config/spec.ts +8 -8
  114. package/src/console/backup/nine-router.ts +71 -10
  115. package/src/console/backup/service.ts +8 -1
  116. package/src/console/console-router.ts +1 -1
  117. package/src/console/domain-registration.ts +7 -2
  118. package/src/console/inferhub/client.ts +1 -1
  119. package/src/console/observability/live.ts +2 -2
  120. package/src/console/providers/catalog/contracts.ts +9 -3
  121. package/src/console/providers/catalog/model-operations.ts +2 -3
  122. package/src/console/providers/catalog/provider-operations.ts +1 -1
  123. package/src/console/providers/catalog/routes.ts +2 -2
  124. package/src/console/providers/catalog/store.ts +1914 -1907
  125. package/src/console/providers/detail/routes.ts +1 -0
  126. package/src/console/providers/detail/store.ts +4 -0
  127. package/src/console/providers/oauth/callback-listener.ts +30 -11
  128. package/src/console/providers/oauth/device-egress.ts +123 -0
  129. package/src/console/providers/oauth/routes.ts +123 -16
  130. package/src/console/quota/account-quota-tenant.ts +5 -5
  131. package/src/console/quota/account-test-egress.ts +2 -2
  132. package/src/console/quota/daily-growth-job.ts +1 -1
  133. package/src/console/routing/pools/contracts.ts +1 -1
  134. package/src/console/routing/pools/operations.ts +1 -1
  135. package/src/console/routing/pools/probe-verdict.ts +2 -2
  136. package/src/console/routing/pools/relay-deploy.ts +1 -1
  137. package/src/console/routing/pools/store.ts +4 -4
  138. package/src/network/client.ts +67 -0
  139. package/src/network/egress/bidi-session.ts +322 -0
  140. package/src/network/egress/dial.ts +201 -0
  141. package/src/network/egress/factory.ts +191 -0
  142. package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
  143. package/src/network/egress/tls-profile.ts +123 -0
  144. package/src/network/egress/ws.ts +175 -0
  145. package/src/network/{types.ts → pool/agent-config.ts} +1 -1
  146. package/src/network/pool/agent.ts +2 -2
  147. package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +7 -7
  148. package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
  149. package/src/network/pool/resolver.ts +10 -849
  150. package/src/network/pool/selector.ts +2 -2
  151. package/src/network/ssrf.ts +1 -1
  152. package/src/network/{retry.ts → util/retry.ts} +1 -1
  153. package/src/observability/telemetry-buffer.ts +1 -1
  154. package/src/persistence/schema.ts +9 -0
  155. package/src/persistence/ttl-cache.ts +1 -1
  156. package/src/protocol/codecs.ts +58 -1
  157. package/src/protocol/request/messages.ts +31 -18
  158. package/src/protocol/response/messages.ts +34 -7
  159. package/src/protocol/translation/quirks.ts +15 -0
  160. package/src/protocol/upstream/send-claude-messages.ts +2 -2
  161. package/src/providers/adapters/compatible-adapter.ts +5 -0
  162. package/src/providers/authentication/oauth-client.ts +40 -6
  163. package/src/providers/authentication/oauth-device-flow.ts +7 -3
  164. package/src/providers/authentication/oauth-flow-store.ts +82 -1
  165. package/src/providers/authentication/oauth-refresh-service.ts +766 -739
  166. package/src/providers/default-registry.ts +76 -0
  167. package/src/providers/discovery/discovery-types.ts +7 -0
  168. package/src/providers/discovery/probe-phases.ts +6 -2
  169. package/src/providers/discovery/probing-service.ts +7 -0
  170. package/src/providers/integrations/antigravity/antigravity-protocol.ts +11 -0
  171. package/src/providers/integrations/buddy/account.ts +969 -0
  172. package/src/providers/integrations/buddy/capabilities.ts +10 -10
  173. package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
  174. package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +5 -5
  175. package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
  176. package/src/providers/integrations/buddy/codebuddy/shared.ts +4 -4
  177. package/src/providers/integrations/buddy/{buddy-identity.ts → headers.ts} +138 -140
  178. package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
  179. package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
  180. package/src/providers/integrations/cline/cline-oauth.ts +10 -5
  181. package/src/providers/integrations/cline/cline.ts +3 -4
  182. package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
  183. package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
  184. package/src/providers/integrations/clixad/provider.ts +145 -0
  185. package/src/providers/integrations/codex/codex-oauth.ts +8 -5
  186. package/src/providers/integrations/cursor/cursor-auth.ts +1 -1
  187. package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
  188. package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
  189. package/src/providers/integrations/cursor/cursor-shared.ts +1 -1
  190. package/src/providers/integrations/cursor/cursor.ts +485 -70
  191. package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
  192. package/src/providers/integrations/deepai/deepai-common.ts +145 -0
  193. package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
  194. package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
  195. package/src/providers/integrations/deepai/provider.ts +42 -0
  196. package/src/providers/integrations/devin/devin-auth.ts +7 -0
  197. package/src/providers/integrations/factory/factory-oauth.ts +16 -7
  198. package/src/providers/integrations/freebuff/freebuff.ts +2 -2
  199. package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
  200. package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
  201. package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
  202. package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
  203. package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
  204. package/src/providers/integrations/freepi/freepi-session.ts +158 -0
  205. package/src/providers/integrations/freepi/freepi.ts +80 -54
  206. package/src/providers/integrations/github/github-discovery.ts +30 -13
  207. package/src/providers/integrations/github/github-oauth.ts +217 -44
  208. package/src/providers/integrations/github/github-quota.ts +22 -5
  209. package/src/providers/integrations/github/github.ts +40 -32
  210. package/src/providers/integrations/grok/grok-oauth.ts +4 -4
  211. package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
  212. package/src/providers/integrations/hermes/hermes.ts +34 -5
  213. package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
  214. package/src/providers/integrations/kilo/kilo.ts +1 -1
  215. package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
  216. package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
  217. package/src/providers/integrations/muse/muse-oauth.ts +5 -5
  218. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  219. package/src/providers/integrations/openrouter/provider.ts +1 -1
  220. package/src/providers/integrations/perch/perch-protocol.ts +0 -3
  221. package/src/providers/integrations/perch/perch.ts +3 -3
  222. package/src/providers/integrations/qoder/claim-qoder-credits.ts +1 -1
  223. package/src/providers/integrations/qoder/qoder-adapter.ts +19 -113
  224. package/src/providers/integrations/qoder/qoder-auth.ts +8 -1
  225. package/src/providers/integrations/qoder/qoder-catalog.ts +1 -1
  226. package/src/providers/integrations/qoder/qoder-chat.ts +25 -8
  227. package/src/providers/integrations/qoder/qoder-checkin.ts +1 -1
  228. package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
  229. package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
  230. package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
  231. package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
  232. package/src/providers/integrations/qoder/qoder-types.ts +9 -0
  233. package/src/providers/integrations/qoder/qoder.ts +3 -25
  234. package/src/providers/integrations/sensenova/provider.ts +144 -0
  235. package/src/providers/integrations/trae/trae-quota.ts +15 -1
  236. package/src/providers/integrations/vercel/provider.ts +117 -0
  237. package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
  238. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  239. package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
  240. package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
  241. package/src/providers/integrations/zro/zro.ts +2 -1
  242. package/src/providers/operations/account-refresh-status.ts +14 -0
  243. package/src/providers/operations/client-versions.ts +95 -30
  244. package/src/providers/operations/oauth-refresh-lead.ts +46 -22
  245. package/src/providers/operations/provider-caches.ts +1 -1
  246. package/src/providers/operations/provider-credential-service.ts +22 -25
  247. package/src/providers/operations/session-resolution.ts +17 -1
  248. package/src/providers/provider-contracts.ts +2 -2
  249. package/src/providers/provider-metadata.ts +30 -0
  250. package/src/providers/quota/quota-contracts.ts +2 -0
  251. package/src/providers/quota/refresh.ts +25 -3
  252. package/src/security/ip-boundary.ts +1 -1
  253. package/src/transport/dispatch/attempt-finalize.ts +1 -1
  254. package/src/transport/dispatch/attempt-loop.ts +3 -3
  255. package/src/transport/dispatch/handler-deps.ts +1 -1
  256. package/src/transport/dispatch/leases.ts +1 -1
  257. package/src/transport/dispatch/retry-policy.ts +1 -1
  258. package/src/transport/dispatch/streaming/finalize.ts +2 -2
  259. package/src/transport/dispatch/streaming/stream.ts +1 -1
  260. package/src/transport/dispatch/upstream.ts +1 -1
  261. package/src/transport/shutdown-notice.ts +1 -1
  262. package/src/workers/buddy-growth-tasks.ts +8 -8
  263. package/src/workers/daily-checkin.ts +4 -4
  264. package/src/workers/quota-refresh-sweep.ts +1 -1
  265. package/src/workers/zcode-gift-claim.ts +1 -1
  266. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
  267. package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
  268. package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
  269. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
  270. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
  271. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
  272. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
  273. package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
  274. package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
  275. package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
  276. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
  277. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +0 -8
  278. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
  279. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
  280. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
  281. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js +0 -9
  282. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
  283. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
  284. package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
  285. package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
  286. package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
  287. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
  288. package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
  289. package/dist/dashboard/assets/app-Dx2QgsNj.css +0 -2
  290. package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
  291. package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
  292. package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
  293. package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
  294. package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
  295. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
  296. package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
  297. package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
  298. package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
  299. package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
  300. package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
  301. package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
  302. package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
  303. package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
  304. package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
  305. package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
  306. package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
  307. package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
  308. package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
  309. package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
  310. package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
  311. package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
  312. package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
  313. package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
  314. package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
  315. package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
  316. package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
  317. package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
  318. package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
  319. package/src/console/providers/catalog/discover-constants.ts +0 -6
  320. package/src/network/pool-health.ts +0 -198
  321. package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
  322. package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
  323. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
  324. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
  325. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
  326. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
  327. package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
  328. package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
  329. package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
  330. package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
  331. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
  332. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -188
  333. package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
  334. package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -179
  335. package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
  336. package/src/providers/integrations/qoder/chat-proto.ts +0 -328
  337. package/src/providers/integrations/qoder/qoder-grpc.ts +0 -870
  338. package/src/providers/integrations/qoder/qoder-ide.ts +0 -481
  339. /package/dist/dashboard/assets/{app-DIXLjojg.js → app-C0gTXEoN.js} +0 -0
  340. /package/dist/dashboard/assets/{app-DIXLjojg.js.gz → app-C0gTXEoN.js.gz} +0 -0
  341. /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
  342. /package/src/network/pool/{byte-accounting.ts → accounting.ts} +0 -0
  343. /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
  344. /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
  345. /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
  346. /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
  347. /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
  348. /package/src/network/{rate-limit-verdict.ts → verdict.ts} +0 -0
@@ -19,16 +19,16 @@ import {
19
19
  /**
20
20
  * Newest Trae IDE version from the official changelog.
21
21
  *
22
- * Trae publishes no npm package and no release API, so the changelog is the
22
+ * Trae publishes no npm package and no release API, so the changelog was the
23
23
  * only machine-readable source of the current client version. It matters
24
24
  * upstream: intl `llm_raw_chat` answers `4015` unless the request carries the
25
25
  * *current* client version, so a stale pin is an outage, not a cosmetic drift.
26
26
  *
27
- * The page embeds its content as JSON, and its newest entry is the first dated
28
- * heading — not the highest semver, because this vendor's hotfix line numbers
29
- * past its feature line (3.5.104 shipped after 3.5.97). Taking `max(semver)`
30
- * would report an old build as current, so this walks the document in order and
31
- * returns the first version following a dated release heading.
27
+ * The page is now an SPA (2026-11-11): its HTML no longer embeds the release
28
+ * data, so this parser returns null and the pinned fallback carries the wire
29
+ * identity until a machine-readable feed exists again (see
30
+ * `VERSION_SOURCES.trae`). The parser is kept for when the page re-exposes
31
+ * its entries.
32
32
  */
33
33
  async function traeVersion(response: Response): Promise<string | null> {
34
34
  try {
@@ -179,11 +179,24 @@ const KIRO_SYSTEM_VERSION = "win32#10.0.22631";
179
179
  /** Node version the observed Kiro client reports (Electron's bundled Node). */
180
180
  const KIRO_NODE_VERSION = "24.18.0";
181
181
 
182
+ /** Clixad gateway's own CLI version: `cli_latest` on `/v1/models`. */
183
+ async function clixadVersion(response: Response): Promise<string | null> {
184
+ const body = await response.text().catch(() => "");
185
+ if (body.length === 0) return null;
186
+ try {
187
+ const parsed = JSON.parse(body) as { cli_latest?: unknown };
188
+ const candidate = parsed.cli_latest;
189
+ return typeof candidate === "string" && isSemverish(candidate) ? candidate.trim() : null;
190
+ } catch {
191
+ return null;
192
+ }
193
+ }
194
+
182
195
  /** Ordered upstream sources and pinned fallbacks for every client version. */
183
196
  export const VERSION_SOURCES = {
184
197
  antigravity: {
185
198
  key: "antigravity",
186
- fallback: "2.29.0",
199
+ fallback: "2.31.0",
187
200
  sources: [
188
201
  {
189
202
  url: "https://antigravity-hub-auto-updater-974169037036.us-central1.run.app/manifest/latest-arm64-mac.yml",
@@ -207,7 +220,7 @@ export const VERSION_SOURCES = {
207
220
  // under `@opencode/cli`, and this feed is what its own installer reads.
208
221
  // Fallback = the current latest release; raise it whenever the package
209
222
  // moves so a cold process does not present a stale client build.
210
- fallback: "2.2.6",
223
+ fallback: "2.2.7",
211
224
  minVersion: "2.0.0",
212
225
  sources: [{ url: "https://opencode.ai/update/api/latest/cli/npm" }],
213
226
  },
@@ -247,7 +260,7 @@ export const VERSION_SOURCES = {
247
260
  },
248
261
  codex: {
249
262
  key: "codex",
250
- fallback: "0.167.0",
263
+ fallback: "0.167.1",
251
264
  sources: [{ url: "https://registry.npmjs.org/@openai/codex/latest" }],
252
265
  },
253
266
  kimiCli: {
@@ -257,7 +270,7 @@ export const VERSION_SOURCES = {
257
270
  },
258
271
  kiro: {
259
272
  key: "kiro",
260
- fallback: "1.4.121",
273
+ fallback: "1.4.154",
261
274
  sources: [
262
275
  {
263
276
  // Kiro's downloads page exposes no stable version string. Homebrew
@@ -270,7 +283,7 @@ export const VERSION_SOURCES = {
270
283
  },
271
284
  codebuddy: {
272
285
  key: "codebuddy",
273
- fallback: "2.181.0",
286
+ fallback: "2.183.0",
274
287
  sources: [
275
288
  { url: "https://registry.npmjs.org/@tencent-ai/codebuddy-code/latest" },
276
289
  { url: "https://registry.npmmirror.com/@tencent-ai/codebuddy-code/latest" },
@@ -278,7 +291,7 @@ export const VERSION_SOURCES = {
278
291
  },
279
292
  codebuddyCn: {
280
293
  key: "codebuddy-cn",
281
- fallback: "4.12.3.42658816",
294
+ fallback: "4.12.4.43368774",
282
295
  sources: [
283
296
  {
284
297
  // Official CN builds are listed by Homebrew's maintained cask feed;
@@ -293,7 +306,7 @@ export const VERSION_SOURCES = {
293
306
  // Current `@anthropic-ai/claude-code` release, so the billing
294
307
  // `cc_version=` suffix and the `claude-cli/` User-Agent stay on what
295
308
  // upstream ships.
296
- fallback: "2.1.319",
309
+ fallback: "2.1.322",
297
310
  sources: [{ url: "https://registry.npmjs.org/@anthropic-ai/claude-code/latest" }],
298
311
  },
299
312
  claudeSdk: {
@@ -320,21 +333,22 @@ export const VERSION_SOURCES = {
320
333
  },
321
334
  kilo: {
322
335
  key: "kilo-code",
323
- fallback: "7.8.27",
336
+ fallback: "7.8.28",
324
337
  sources: [{ url: "https://open-vsx.org/api/kilocode/kilo-code" }],
325
338
  },
326
339
  muse: {
327
340
  key: "muse-code",
328
- fallback: "1.4.14-R7309.1",
341
+ fallback: "1.4.15-R8339.1",
329
342
  sources: [{ url: "https://api.meta.ai/muse-code/channels/muse-stable" }],
330
343
  },
331
344
  trae: {
332
345
  key: "trae",
333
- // Fallback = the newest release the changelog names, which is the *high*
334
- // end of its newest line ("TRAE v3.6.1 ~ 3.6.4"). Discovery replaces it
335
- // within one monitor tick; it is only what a cold process sends before
336
- // the first successful fetch.
337
- fallback: "3.6.4",
346
+ // Fallback = the current desktop release per the official winget manifest
347
+ // (ByteDance.Trae 3.6.13). The docs changelog renders as an SPA and no
348
+ // longer exposes its newest entries to the parser below, so it is not a
349
+ // reliable discovery source right now — raise this pin when the desktop
350
+ // build moves.
351
+ fallback: "3.6.13",
338
352
  sources: [
339
353
  {
340
354
  url: "https://docs.trae.ai/ide/changelog?_lang=en",
@@ -345,24 +359,63 @@ export const VERSION_SOURCES = {
345
359
  freepi: {
346
360
  key: "freepi",
347
361
  // The free-pi CLI version — the `x-client-version` the gateway reports.
348
- // Fallback = the unpacked 0.6.14 bundle; npm discovery replaces it.
349
- fallback: "0.6.14",
362
+ // Fallback = the current npm latest (0.6.19 as of 2026-11-12); a cold
363
+ // process sends it until the monitor's first successful discovery.
364
+ fallback: "0.6.19",
350
365
  sources: [{ url: "https://registry.npmjs.org/free-pi-cli/latest" }],
351
366
  },
352
367
  perch: {
353
368
  key: "perch",
354
369
  // The official Perch CLI, spawned by the adapter for hosted inference.
355
- fallback: "2.4.125",
370
+ // Fallback = the current npm latest (2.4.128 as of 2026-11-11); a cold
371
+ // process sends it until the monitor's first successful discovery.
372
+ fallback: "2.4.128",
356
373
  sources: [{ url: "https://registry.npmjs.org/perchai-cli/latest" }],
357
374
  },
375
+ clixad: {
376
+ key: "clixad",
377
+ // The Clixad gateway publishes the CLI build it is paired with as
378
+ // `cli_latest` on `/v1/models` (unauthenticated). Fallback = the version
379
+ // the 0.0.1-beta.50 bundle shipped (verified live 2026-11-11); a cold
380
+ // process sends it until the monitor's first successful discovery.
381
+ fallback: "0.0.1-beta.50",
382
+ sources: [
383
+ {
384
+ url: "https://clixad.onrender.com/v1/models",
385
+ extract: clixadVersion,
386
+ },
387
+ ],
388
+ },
389
+ freebuff: {
390
+ key: "freebuff",
391
+ // No machine-readable public feed exists for Freebuff Desktop. GitHub
392
+ // releases only publish Codecane (a separate CLI line) staging builds,
393
+ // npm `freebuff` is the CLI (a different product), and the site's
394
+ // download links are client-rendered with no stable asset path.
395
+ // Fallback = the version unpacked from the official Windows installer
396
+ // during the wire audit (2026-11-09, Freebuff Desktop 0.0.201). Raise it
397
+ // when the next installer bundle is audited.
398
+ fallback: "0.0.201",
399
+ sources: [],
400
+ },
401
+ deepai: {
402
+ key: "deepai",
403
+ // No machine-readable public feed exists for the DeepAI Code desktop app
404
+ // (macOS/Windows only, no npm/CLI line). Fallback = the version unpacked
405
+ // from the audited bundle (2026-11-11, DeepAI Code 0.3.35). Raise it
406
+ // when the next bundle is audited. Pinned-only by design for the same
407
+ // reason as `freebuff`.
408
+ fallback: "0.3.35",
409
+ sources: [],
410
+ },
358
411
  cursor: {
359
412
  key: "cursor",
360
- // Fallback = the last observed wire version (CLI ~2026.11.07-306d153).
361
- // Discovery replaces it within one monitor tick; it is only what a cold
362
- // process sends before the first successful fetch. Kept equal to
363
- // `FALLBACK_CLIENT_VERSION` in the cursor adapter so the badge and the
364
- // request header always send one identity.
365
- fallback: "cli-3.29.9-306d153",
413
+ // Fallback = the newest stable build from the official download API
414
+ // (cli-3.29.15-86776fc). Discovery replaces it within one monitor tick; it
415
+ // is only what a cold process sends before the first successful fetch.
416
+ // Kept equal to `FALLBACK_CLIENT_VERSION` in the cursor adapter so the
417
+ // badge and the request header always send one identity.
418
+ fallback: "cli-3.29.15-86776fc",
366
419
  sources: [
367
420
  {
368
421
  url: "https://cursor.com/api/download?platform=win32-x64-user&releaseTrack=stable",
@@ -376,7 +429,7 @@ export const VERSION_SOURCES = {
376
429
  // comment this replaced claimed no semver existed and pinned a date tag
377
430
  // (`v2026.9.24`) as the version — a tag, not a release, and not the
378
431
  // identity the client sends.
379
- fallback: "0.23.0",
432
+ fallback: "0.23.1",
380
433
  sources: [
381
434
  {
382
435
  url: "https://api.github.com/repos/NousResearch/hermes-agent/releases/latest",
@@ -403,6 +456,9 @@ const resolvers = {
403
456
  trae: createClientVersionResolver({ ...VERSION_SOURCES.trae, client: "trae" }),
404
457
  freepi: createClientVersionResolver({ ...VERSION_SOURCES.freepi, client: "freepi" }),
405
458
  perch: createClientVersionResolver({ ...VERSION_SOURCES.perch, client: "perch" }),
459
+ clixad: createClientVersionResolver({ ...VERSION_SOURCES.clixad, client: "clixad" }),
460
+ freebuff: createClientVersionResolver({ ...VERSION_SOURCES.freebuff, client: "freebuff" }),
461
+ deepai: createClientVersionResolver({ ...VERSION_SOURCES.deepai, client: "deepai" }),
406
462
  cursor: createClientVersionResolver({ ...VERSION_SOURCES.cursor, client: "cursor" }),
407
463
  opencode: createClientVersionResolver({ ...VERSION_SOURCES.opencode, client: "opencode" }),
408
464
  commandcode: createClientVersionResolver({ ...VERSION_SOURCES.commandcode, client: "command-code" }),
@@ -424,6 +480,11 @@ const resolvers = {
424
480
  } satisfies Record<keyof typeof VERSION_SOURCES, ClientVersionResolver>;
425
481
  const PROVIDER_VERSION_RESOLVERS: Readonly<Record<string, ClientVersionResolver>> = {
426
482
  antigravity: resolvers.antigravity,
483
+ freepi: resolvers.freepi,
484
+ perch: resolvers.perch,
485
+ clixad: resolvers.clixad,
486
+ freebuff: resolvers.freebuff,
487
+ deepai: resolvers.deepai,
427
488
  grok: resolvers.grok,
428
489
  xai: resolvers.grok,
429
490
  opencode: resolvers.opencode,
@@ -537,6 +598,10 @@ export const getQoderVersion = accessor("qoder").get;
537
598
  export const getHermesVersion = accessor("hermes").get;
538
599
  export const getTraeVersion = accessor("trae").get;
539
600
  export const getAntigravityVersion = accessor("antigravity").get;
601
+ export const getFreepiVersion = accessor("freepi").get;
602
+ export const getPerchVersion = accessor("perch").get;
603
+ export const getFreebuffVersion = accessor("freebuff").get;
604
+ export const getDeepAIVersion = accessor("deepai").get;
540
605
 
541
606
  export const getOpenCodeVersion = accessor("opencode").get;
542
607
 
@@ -1,22 +1,43 @@
1
1
  /**
2
2
  * Per-provider proactive OAuth refresh lead.
3
3
  *
4
- * A single global "refresh 5 minutes before expiry" window is wrong for two
5
- * reasons. Some providers issue short-lived tokens that a 5-minute lead can
6
- * miss entirely under a loaded sweep, while others issue long-lived tokens
7
- * where re-minting every few minutes is pointless traffic. And some providers
8
- * (Google-backed) rate-limit the token endpoint aggressively, so refreshing
9
- * only when genuinely close to expiry matters more than reacting fast.
4
+ * The lead is how long *before* expiry a token becomes eligible for the
5
+ * scheduled refresh sweep. It exists so the sweep re-mints a token before it
6
+ * dies — never to push refreshes any more often than that.
10
7
  *
11
- * The lead is how long *before* expiry the token becomes eligible for
12
- * proactive refresh. It is a floor: a caller may pass a larger `skewMs`
13
- * explicitly, and `DEFAULT_REFRESH_LEAD_MS` applies to any provider not listed.
8
+ * Buckets are deliberately coarse. A single global "5 minutes before expiry"
9
+ * window is wrong: some providers issue short-lived tokens that a 5-minute
10
+ * lead can miss under a loaded sweep, while others issue very long-lived
11
+ * tokens where re-minting days early is pointless traffic. Three buckets cover
12
+ * both ends without a hand-tuned value per provider:
14
13
  *
15
- * Values mirror the leads published by the upstream clients these providers
16
- * model (Claude Code ~4h, Codex ~5 days, Antigravity/Kimi ~5 min), so a token
17
- * is re-minted on the same schedule the first-party tooling uses.
14
+ * - daily (default): tokens that live hours to days — refresh once a day at
15
+ * most, or when the sweep sees the token enter its final 24h.
16
+ * - 3 days: multi-day tokens — refresh at most ~3 days before expiry.
17
+ * - 7 weeks: very long-lived tokens — refresh ~7 weeks before expiry, so a
18
+ * ~6-month token is touched roughly once a quarter.
19
+ *
20
+ * Short-lived tokens (below the daily bucket) keep tighter leads because the
21
+ * invariant is "never let the token expire": a lead larger than the token
22
+ * lifetime would make the token permanently due and the sweep would re-mint it
23
+ * on every tick, which is the wasteful behavior these buckets exist to remove.
24
+ *
25
+ * Real expiries (from the first-party clients these providers model):
26
+ * - Google-backed (Antigravity): access tokens live ~1h; 5 min lead.
27
+ * - Claude Code: CLI session tokens live ~8h; 4h lead keeps one work
28
+ * session on a single token.
29
+ * - xAI/Grok CLI and Kimi: short-lived tokens; 5 min lead.
30
+ * - ChatGPT/Codex: access tokens live ~5 days; 3-day lead re-mints ~2 days
31
+ * before expiry.
32
+ * - iFlow: very long-lived tokens; 7-week lead.
18
33
  */
19
- export const DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
34
+ export const DEFAULT_REFRESH_LEAD_MS = 24 * 60 * 60 * 1000;
35
+
36
+ /** 3-day lead for multi-day tokens. */
37
+ export const REFRESH_LEAD_3DAY_MS = 3 * 24 * 60 * 60 * 1000;
38
+
39
+ /** 7-week lead for very long-lived tokens. */
40
+ export const REFRESH_LEAD_7WEEK_MS = 7 * 7 * 24 * 60 * 60 * 1000;
20
41
 
21
42
  /**
22
43
  * Per-provider refresh lead in milliseconds. Providers absent here use
@@ -24,19 +45,22 @@ export const DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
24
45
  * `provider_accounts.provider_id`.
25
46
  */
26
47
  export const REFRESH_LEAD_MS: Readonly<Record<string, number>> = Object.freeze({
27
- // Google-backed token endpoints throttle; a wide lead with infrequent
28
- // refreshes is safer than a tight one that retries often.
48
+ // Google-backed token endpoints throttle, and the access token itself lives
49
+ // only ~1h — a tight lead (below the token lifetime) is what keeps the
50
+ // sweep from re-minting on every tick.
29
51
  antigravity: 5 * 60 * 1000,
30
- // Claude Code access tokens live ~8h and the CLI re-mints well ahead of
31
- // expiry; 4h keeps a full work session on one token.
52
+ // Claude Code access tokens live ~8h; 4h keeps a full work session on one
53
+ // token without permanent due-state.
32
54
  claude: 4 * 60 * 60 * 1000,
33
- // Codex/ChatGPT access tokens live for days; refresh on a 5-day lead.
34
- codex: 5 * 24 * 60 * 60 * 1000,
35
- // xAI/Grok CLI and Kimi both re-mint on a 5-minute lead.
55
+ // xAI/Grok CLI and Kimi re-mint on a 5-minute lead (short-lived tokens).
36
56
  grok: 5 * 60 * 1000,
37
57
  kimi: 5 * 60 * 1000,
38
- // iFlow tokens are long-lived; a 1-day lead avoids pointless churn.
39
- iflow: 24 * 60 * 60 * 1000,
58
+ // ChatGPT/Codex access tokens live ~5 days; a 3-day lead re-mints ~2 days
59
+ // before expiry — one refresh per weekend, not per request.
60
+ codex: REFRESH_LEAD_3DAY_MS,
61
+ // iFlow tokens are very long-lived; a 7-week lead avoids pointless churn
62
+ // while still re-minting well before expiry.
63
+ iflow: REFRESH_LEAD_7WEEK_MS,
40
64
  });
41
65
 
42
66
  /** The refresh lead for `providerId`, falling back to the shared default. */
@@ -7,7 +7,7 @@ import { trackModelCatalogLoad } from "../../observability/performance-metrics";
7
7
  import { resolveRedisClient } from "../../persistence/redis";
8
8
  import { cachedRead } from "../../console/shared/read-through-cache";
9
9
  import { TtlCacheFamily } from "../../persistence/ttl-cache";
10
- import { retryWithBackoff } from "../../network/retry";
10
+ import { retryWithBackoff } from "../../network/util/retry";
11
11
 
12
12
  /* -------------------------------------------------------------------------- */
13
13
  /* 1. Model Catalog Cache */
@@ -161,6 +161,8 @@ export interface AccountWithOauthStateRow {
161
161
  export interface LoadAccountWithOauthStateOptions {
162
162
  /** Select `status` from providerAccounts. */
163
163
  readonly includeStatus?: boolean;
164
+ /** Select the last health category from providerAccounts. */
165
+ readonly includeLastErrorCategory?: boolean;
164
166
  /** Select `expiresAt` from providerOauthStates. */
165
167
  readonly includeExpiry?: boolean;
166
168
  /** Also compute the skew-adjusted OAuth due time (see `loadAccountWithFreshness`). */
@@ -174,6 +176,7 @@ export interface LoadedAccountWithOauthState {
174
176
  readonly id: string;
175
177
  readonly providerId: string;
176
178
  readonly credentialKind: CredentialKind;
179
+ readonly lastErrorCategory?: string | null;
177
180
  readonly credentialCiphertext: Buffer | null;
178
181
  readonly refreshCiphertext: Buffer | null;
179
182
  readonly authState: unknown;
@@ -209,6 +212,9 @@ export async function loadAccountWithOauthState(
209
212
  clientSecretCiphertext: providerOauthStates.clientSecretCiphertext,
210
213
  staticToken: providerAccounts.staticToken,
211
214
  ...(options.includeStatus === true ? { status: providerAccounts.status } : {}),
215
+ ...(options.includeLastErrorCategory === true
216
+ ? { lastErrorCategory: providerAccounts.lastErrorCategory }
217
+ : {}),
212
218
  ...(options.includeExpiry === true || options.includeDueAt === true
213
219
  ? { expiresAt: providerOauthStates.expiresAt }
214
220
  : {}),
@@ -225,14 +231,12 @@ export async function loadAccountWithOauthState(
225
231
  if (!row) return undefined;
226
232
  if (options.includeDueAt !== true) return row;
227
233
  const lead = options.skewMs ?? refreshLeadMs(row.providerId);
228
- // A refresh-capable OAuth account with no stored expiry is unknown-liveness:
229
- // we have no evidence the access token still works. Marking the token due
230
- // immediately refreshes it on first use; the sweep handles the normal case
231
- // where `expires_at` is populated.
232
- const hasRefreshGrant = row.refreshCiphertext != null;
233
- const kindOauth = row.credentialKind === "oauth";
234
- const unknownExpiryButRefreshable =
235
- kindOauth && !row.staticToken && hasRefreshGrant && row.expiresAt == null;
234
+ // A refresh-capable OAuth account with no stored expiry has an unknown
235
+ // liveness, but the scheduled sweep is the only refresher: marking it due
236
+ // here would refresh on every load, and the dispatch path deliberately no
237
+ // longer refreshes inline. Unknown expiry therefore means "not due yet" —
238
+ // if the token actually died, the upstream's 401 triggers the dispatch
239
+ // retry refresh (see `attempt-loop.ts`).
236
240
  // A static token is never "due": it is used as issued and there is no
237
241
  // refresh to run, so the dispatch path must not treat it as stale.
238
242
  const dueAt =
@@ -240,9 +244,7 @@ export async function loadAccountWithOauthState(
240
244
  ? undefined
241
245
  : row.expiresAt != null
242
246
  ? row.expiresAt.getTime() - lead
243
- : unknownExpiryButRefreshable
244
- ? 0
245
- : undefined;
247
+ : undefined;
246
248
  return {
247
249
  ...row,
248
250
  ...(dueAt === undefined ? {} : { dueAt }),
@@ -293,7 +295,7 @@ export async function resolveCredentialForAccount(
293
295
  db: CartethyiaDatabase,
294
296
  providerId: string,
295
297
  accountId: string,
296
- oauth?: ResolveCredentialOAuth,
298
+ _oauth?: ResolveCredentialOAuth,
297
299
  ): Promise<ResolvedCredential> {
298
300
  // Cache hit: skip DB round-trip + AES-GCM decrypt.
299
301
  // Don't cache OAuth credentials whose token is due for refresh soon —
@@ -338,19 +340,14 @@ export async function resolveCredentialForAccount(
338
340
  );
339
341
  }
340
342
  }
341
- if (row.credentialKind === "oauth" && oauth && dueAt !== undefined && dueAt <= Date.now()) {
342
- const refresher = await oauth.resolveRefresher(row.providerId);
343
- if (refresher) {
344
- // Try to refresh. If it fails (refresh grant dead, upstream unreachable,
345
- // etc.) keep the already-decrypted stale secret rather than wiping it:
346
- // dispatch then sends the token we have, and the upstream's own 401 is
347
- // the authoritative verdict. Wiping it here turned "token expired" into
348
- // a credential-less GatewayError before the request ever left, which is
349
- // worse than letting the server say so.
350
- const refreshed = await oauth.refreshService.ensureFreshAccessToken(accountId, refresher);
351
- if (refreshed) secret = refreshed;
352
- }
353
- }
343
+ // No inline refresh on the request path. OAuth tokens are kept fresh by the
344
+ // scheduled refresh sweep (`oauth-refresh-sweep`, due by `refreshLeadMs`),
345
+ // and a token that still expired before its sweep turn is answered by the
346
+ // upstream's own 401, which the dispatch attempt-loop turns into a forced
347
+ // refresh (see `attempt-loop.ts`). Refreshing here blocked a client request
348
+ // on the token endpoint — a hung endpoint held the request for the full 30 s
349
+ // refresh timeout — and re-minted tokens on every expired request instead of
350
+ // once per schedule.
354
351
 
355
352
  const authState = jsonbRecord(row.authState);
356
353
  const alternative: CredentialAlternative = {
@@ -13,13 +13,29 @@ const SESSION_HEADERS = [
13
13
  "session-id",
14
14
  ] as const;
15
15
 
16
+ /** Ceiling for a session id, mirroring the OpenCode free tier's 256-char cap. */
17
+ const MAX_INBOUND_SESSION_LENGTH = 256;
18
+
19
+ /**
20
+ * Normalize an inbound session id the same way the OpenCode CLI does: trim,
21
+ * reject empty strings, and cap pathological lengths so a defective header can
22
+ * never become a cache key or a sticky-routing key hundreds of bytes long.
23
+ * Returns undefined when the value is unusable.
24
+ */
25
+ export function normalizeInboundSessionId(value: string | undefined): string | undefined {
26
+ if (typeof value !== "string") return undefined;
27
+ const trimmed = value.trim();
28
+ if (trimmed.length === 0 || trimmed.length > MAX_INBOUND_SESSION_LENGTH) return undefined;
29
+ return trimmed;
30
+ }
31
+
16
32
  export function resolveInboundSessionId(
17
33
  context?: ProviderDispatchContext,
18
34
  request?: CanonicalRequest,
19
35
  ): string | undefined {
20
36
  const headers = context?.request_headers;
21
37
  for (const name of SESSION_HEADERS) {
22
- const value = headers?.[name]?.trim();
38
+ const value = normalizeInboundSessionId(headers?.[name]);
23
39
  if (value) return value;
24
40
  }
25
41
  const conversationId = request?.conversation?.conversation_id;
@@ -5,7 +5,7 @@
5
5
  import type { CanonicalEvent, CanonicalRequest, WireFamily } from "../protocol/canonical-model";
6
6
  import { GatewayError } from "../protocol/gateway-error";
7
7
  import { BUNDLED_PROVIDER_METADATA, type BundledProviderId } from "./provider-metadata";
8
- import type { ValidatedOutboundHttp2Bidi } from "../network/http2-bidi";
8
+ import type { ValidatedOutboundHttp2Bidi } from "../network/bidi";
9
9
 
10
10
  /** Closed union of every shipped provider ID. */
11
11
  export const BUNDLED_PROVIDER_IDS: ReadonlySet<BundledProviderId> = new Set(
@@ -134,7 +134,7 @@ export type ValidatedOutboundWebSocket = (
134
134
  /**
135
135
  * The dialer contract handed to a provider adapter.
136
136
  *
137
- * Declared in `network/http2-bidi.ts` — the transport owns the contract — and
137
+ * Declared in `network/bidi.ts` — the transport owns the contract — and
138
138
  * re-exported here so provider code keeps one import site.
139
139
  */
140
140
  export type { ValidatedOutboundHttp2Bidi };
@@ -172,6 +172,14 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
172
172
  { id: "opencode-go", displayName: "OpenCode Go", baseUrl: "https://opencode.ai", credentialUrl: "https://opencode.ai/auth" },
173
173
  { id: "cerebras", displayName: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", credentialUrl: "https://cloud.cerebras.ai/platform" },
174
174
  { id: "openrouter", displayName: "OpenRouter", baseUrl: "https://openrouter.ai/api/v1", credentialUrl: "https://openrouter.ai/settings/keys" },
175
+ {
176
+ id: "vercel",
177
+ displayName: "Vercel AI Gateway",
178
+ baseUrl: "https://ai-gateway.vercel.sh/v1",
179
+ defaultSkipProxy: true,
180
+ credentialUrl: "https://vercel.com/dashboard/ai",
181
+ credentialHint: "Create an AI Gateway API key (`vercel ai-gateway api-keys create`) or paste an existing `vck_` key. The key is bearer-authenticated against the OpenAI-compatible /v1 surface.",
182
+ },
175
183
  { id: "mistral", displayName: "Mistral AI", baseUrl: "https://api.mistral.ai/v1", credentialUrl: "https://console.mistral.ai/api-keys" },
176
184
  { id: "fireworks", displayName: "Fireworks AI", baseUrl: "https://api.fireworks.ai/inference/v1", credentialUrl: "https://fireworks.ai/account/api-keys" },
177
185
  { id: "nvidia", displayName: "NVIDIA NIM", baseUrl: "https://integrate.api.nvidia.com/v1", credentialUrl: "https://build.nvidia.com/settings/api-keys" },
@@ -180,6 +188,22 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
180
188
  // and the registry pins the Messages endpoint separately.
181
189
  { id: "deepseek", displayName: "DeepSeek", baseUrl: "https://api.deepseek.com", wireFamilyDefault: "messages", credentialUrl: "https://platform.deepseek.com/api_keys" },
182
190
  { id: "gmi", displayName: "GMI Cloud", baseUrl: "https://api.gmi-serving.com/v1", credentialUrl: "https://console.gmicloud.ai" },
191
+ {
192
+ id: "clixad",
193
+ displayName: "Clixad",
194
+ baseUrl: "https://clixad.onrender.com/v1",
195
+ requiresAccount: true,
196
+ credentialUrl: "https://clixad.io",
197
+ credentialHint: "Sign in with GitHub (device flow) or paste a Clixad token (adg_...). Model calls draw from the account balance and a daily free tier (120k credits/day).",
198
+ },
199
+ {
200
+ id: "deepai",
201
+ displayName: "DeepAI",
202
+ baseUrl: "https://api.deepai.org",
203
+ requiresAccount: true,
204
+ credentialUrl: "https://deepai.org",
205
+ credentialHint: "Sign in via the DeepAI desktop flow (loopback exchange → api_key) or paste a DeepAI api_key. Model calls are authenticated with the Api-Key header; the daily free allowance is 50 Theos and 11 models draw nothing from it.",
206
+ },
183
207
 
184
208
  // OpenAI-compatible bearer aggregators. Each is a bare host already rooted at
185
209
  // its own version prefix, so the `/v1`-prefixed wire paths stay pinned by the
@@ -198,6 +222,12 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
198
222
  { id: "synthetic", displayName: "Synthetic", baseUrl: "https://api.synthetic.new/v1", credentialUrl: "https://app.synthetic.new/api-keys" },
199
223
  { id: "stepfun", displayName: "StepFun", baseUrl: "https://api.stepfun.com/v1", credentialUrl: "https://platform.stepfun.com/console/keys" },
200
224
  { id: "sakana", displayName: "Sakana AI", baseUrl: "https://api.sakana.ai/v1", credentialUrl: "https://console.sakana.ai/api-keys" },
225
+ // SenseNova keys are per-site: China keys (token.sensenova.cn) and
226
+ // international keys (token.sensenova.ai) reject each other with 401
227
+ // "Forbidden", so the two deployments are separate providers with separate
228
+ // hosts and catalogs — the same split as CodeBuddy cb/cbcn.
229
+ { id: "sensenova", displayName: "SenseNova", baseUrl: "https://token.sensenova.cn/v1", serviceKinds: ["llm", "imageGeneration"], credentialUrl: "https://token.sensenova.cn", credentialHint: "Paste a SenseNova API key created on platform.sensenova.cn. International accounts use SenseNova (Intl)." },
230
+ { id: "sensenova-intl", displayName: "SenseNova (Intl)", baseUrl: "https://token.sensenova.ai/v1", serviceKinds: ["llm", "imageGeneration"], credentialUrl: "https://platform.sensenova.ai", credentialHint: "Paste a SenseNova API key created on platform.sensenova.ai. China accounts use SenseNova." },
201
231
  { id: "helmcode", displayName: "HelmCode", baseUrl: "https://api.helmcode.com/v1", credentialUrl: "https://helmcode.com/dashboard" },
202
232
  { id: "coralbricks", displayName: "CoralBricks", baseUrl: "https://api.coralbricks.com/v1", credentialUrl: "https://coralbricks.com/api-keys" },
203
233
  { id: "aiand", displayName: "ai&", baseUrl: "https://api.aiand.com/v1", credentialUrl: "https://console.aiand.com/api-keys" },
@@ -29,6 +29,8 @@ export interface ProviderQuotaResult {
29
29
  readonly plan: string | null;
30
30
  readonly windows: readonly ProviderQuotaWindow[];
31
31
  readonly error: string | null;
32
+ /** The provider explicitly rejected the account credential; do not retry it automatically. */
33
+ readonly authInvalidated?: boolean;
32
34
  /** Display identity the billing surface reported, when it reported one. */
33
35
  readonly accountLabel?: string;
34
36
  }
@@ -21,6 +21,7 @@ import { persistQuotaSnapshot } from "./snapshot";
21
21
  import { applyAccountVerdict } from "../operations/account-health-service";
22
22
  import { asJsonbRecord } from "../../shared/coerce";
23
23
  import { KIRO_DEFAULT_LABELS } from "../integrations/kiro/kiro-oauth";
24
+ import { invalidateCredentialCache } from "../operations/provider-credential-service";
24
25
 
25
26
  /**
26
27
  * The stock Kiro fallback labels as a SQL literal list. Built once from the
@@ -34,7 +35,7 @@ const KIRO_DEFAULT_LABEL_IN = sql.raw(
34
35
  );
35
36
 
36
37
  /** Why an account quota refresh failed; `null` means the fetch succeeded. */
37
- export type QuotaRefreshFailureCode = "missing_credential" | "quota_fetch_failed";
38
+ export type QuotaRefreshFailureCode = "missing_credential" | "quota_fetch_failed" | "auth_invalidated";
38
39
 
39
40
  export interface QuotaRefreshOutcome {
40
41
  readonly quota: ProviderQuotaResult;
@@ -150,9 +151,25 @@ function isTransientTimeout(error: string | null | undefined): boolean {
150
151
  export async function recordAccountCheck(
151
152
  db: CartethyiaDatabase,
152
153
  accountId: string,
153
- outcome: { ok: boolean; error?: string | null; category?: string | null },
154
+ outcome: { ok: boolean; error?: string | null; category?: string | null; authInvalidated?: boolean },
154
155
  snapshotInvalidator?: { invalidate(): unknown },
155
156
  ): Promise<void> {
157
+ if (outcome.authInvalidated === true) {
158
+ const disabled = await applyAccountVerdict(db, {
159
+ accountId,
160
+ status: "disabled",
161
+ reason: outcome.error ?? "Provider rejected account authentication",
162
+ category: "auth_invalidated",
163
+ // No deadline: a credential rejected as invalid cannot heal by waiting,
164
+ // and the OAuth refresh sweep must not keep spending refresh attempts.
165
+ cooldownUntil: null,
166
+ });
167
+ if (disabled) {
168
+ invalidateCredentialCache(accountId);
169
+ await snapshotInvalidator?.invalidate();
170
+ }
171
+ return;
172
+ }
156
173
  if (outcome.ok) {
157
174
  // A successful quota refresh is the freshest evidence about the account's
158
175
  // health: heal a cooldown that the refresh shows is no longer justified, so
@@ -453,7 +470,11 @@ async function runQuotaRefresh(
453
470
  error: error instanceof Error ? error.message : "Failed to refresh quota",
454
471
  };
455
472
  }
456
- failure = quota.error === null ? null : "quota_fetch_failed";
473
+ failure = quota.error === null
474
+ ? null
475
+ : quota.authInvalidated === true
476
+ ? "auth_invalidated"
477
+ : "quota_fetch_failed";
457
478
  }
458
479
 
459
480
  // Cache even a failed fetch: the error view ("credential expired", "quota
@@ -527,6 +548,7 @@ async function runQuotaRefresh(
527
548
  {
528
549
  ok: quota.error === null,
529
550
  ...(quota.error === null ? {} : { error: quota.error, category: failure ?? "quota_fetch_failed" }),
551
+ ...(quota.authInvalidated === true ? { authInvalidated: true } : {}),
530
552
  },
531
553
  deps.snapshotInvalidator,
532
554
  );
@@ -9,7 +9,7 @@ import {
9
9
  ipv6ToBigInt,
10
10
  isLoopbackAddress,
11
11
  unwrapMappedIpv4,
12
- } from "../network/ip-utils";
12
+ } from "../network/util/ip";
13
13
 
14
14
  export { isLoopbackAddress };
15
15
 
@@ -20,7 +20,7 @@ import { modelsDevCatalog } from "../../providers/discovery/models-dev-catalog";
20
20
  import {
21
21
  disablePoolForProxyHttpStatus,
22
22
  recordPoolDispatchOutcome,
23
- } from "../../network/pool-health-machine";
23
+ } from "../../network/pool/health";
24
24
  import { ProxyConnectRefusalError } from "../../network/pool/agent";
25
25
  import { captureDepthMaxBytes, providerCaptureMaxBytes } from "./capture-policy";
26
26