cartethyia 2.7.9 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (496) hide show
  1. package/.env.example +6 -5
  2. package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Dq530awD.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-DaZZ3cqP.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BlC_xdkX.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-7xT5yg2E.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
  16. package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-6Teoq9Se.js} +2 -2
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CZuI0MAR.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
  20. package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-C1hNTlkr.js} +1 -1
  21. package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-0yfhSI1C.js} +1 -1
  23. package/dist/dashboard/assets/OverviewPage-0yfhSI1C.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js +8 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js.gz +0 -0
  26. package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-Ddhm1InL.js} +1 -1
  27. package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js.gz +0 -0
  30. package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js +9 -0
  31. package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-BzZAvYSX.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
  36. package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage--s-EfEIL.js} +1 -1
  37. package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-BjXFGSK4.js} +1 -1
  39. package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
  40. package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-CP8F8BN7.js} +1 -1
  41. package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-2hc12Uz2.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-2hc12Uz2.js.gz +0 -0
  44. package/dist/dashboard/assets/app-DGXbNfAR.css +2 -0
  45. package/dist/dashboard/assets/app-DGXbNfAR.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-BMcj1xZE.js +1 -0
  47. package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-DkRUuVfz.js} +1 -1
  49. package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-CDjW841Q.js} +1 -1
  51. package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-Bto5UlL9.js} +1 -1
  53. package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-DaH6g9hL.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-BYKBCzjk.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-DL0Zftwv.js} +1 -1
  59. package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-Dpx_UXaS.js} +1 -1
  61. package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-BV7-1NoM.js} +1 -1
  63. package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
  64. package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-CGvdNU6r.js} +1 -1
  65. package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-DScKYvZj.js +4 -0
  67. package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
  68. package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-BS3DZlZj.js} +1 -1
  69. package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-BU64Lqvp.js → index-SQnvvTWX.js} +2 -2
  71. package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-Dl-cuLK0.js} +1 -1
  73. package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-BCVkdR9d.js → input-Bfx0EzRc.js} +1 -1
  75. package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-BxhbQ_JS.js} +1 -1
  77. package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-D49Y4ptn.js} +1 -1
  79. package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-Cae01iu8.js → live-CbVTdX9V.js} +1 -1
  81. package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
  82. package/dist/dashboard/assets/{network-CRib7H_2.js → network-JGvDveof.js} +1 -1
  83. package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
  84. package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-Dw6kiew-.js} +1 -1
  85. package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
  86. package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-iUoI8GeE.js} +1 -1
  87. package/dist/dashboard/assets/pagination-iUoI8GeE.js.gz +0 -0
  88. package/dist/dashboard/assets/providers/sensenova-intl.png +0 -0
  89. package/dist/dashboard/assets/providers/sensenova.png +0 -0
  90. package/dist/dashboard/assets/providers/zro.svg +3 -0
  91. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  92. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  93. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  94. package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-8jZr-3WE.js} +1 -1
  95. package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
  96. package/dist/dashboard/assets/{select-ChoFWyKz.js → select-oO1FxMVb.js} +2 -2
  97. package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
  98. package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-CLr3kdIx.js} +1 -1
  99. package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
  100. package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-Y3pDteZr.js} +1 -1
  101. package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
  102. package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-CArX_Iak.js} +1 -1
  103. package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
  104. package/dist/dashboard/assets/{switch-Db70BMix.js → switch-DwlwmDO-.js} +1 -1
  105. package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
  106. package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-DJ9GYEgf.js} +1 -1
  107. package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
  108. package/dist/dashboard/index.html +2 -2
  109. package/dist/dashboard/index.html.gz +0 -0
  110. package/dist/dashboard/precompressed-assets.json +54 -53
  111. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  112. package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
  113. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  114. package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
  115. package/package.json +1 -1
  116. package/src/boot/app.ts +11 -14
  117. package/src/boot/dependencies.ts +5 -7
  118. package/src/boot/flush.ts +20 -0
  119. package/src/boot/lifecycle.ts +7 -5
  120. package/src/config/providers.ts +6 -5
  121. package/src/config/routing.ts +26 -0
  122. package/src/config/spec-metadata.ts +72 -0
  123. package/src/config/spec.ts +28 -8
  124. package/src/console/api-keys/contracts.ts +12 -1
  125. package/src/console/api-keys/routes.ts +40 -28
  126. package/src/console/auth/account-reset.ts +7 -9
  127. package/src/console/backup/contracts.ts +4 -0
  128. package/src/console/backup/export-endpoint.ts +7 -5
  129. package/src/console/backup/nine-router.ts +71 -10
  130. package/src/console/backup/service.ts +8 -1
  131. package/src/console/cli-tools/service.ts +6 -12
  132. package/src/console/console-router-guards.ts +23 -16
  133. package/src/console/console-router.ts +1 -1
  134. package/src/console/domain-registration.ts +7 -4
  135. package/src/console/inferhub/client.ts +1 -1
  136. package/src/console/inferhub/normalize.ts +24 -28
  137. package/src/console/inferhub/types.ts +3 -0
  138. package/src/console/ip-bans/routes.ts +7 -13
  139. package/src/console/observability/live.ts +2 -2
  140. package/src/console/observability/store.ts +0 -53
  141. package/src/console/providers/catalog/contracts.ts +9 -3
  142. package/src/console/providers/catalog/model-operations.ts +2 -3
  143. package/src/console/providers/catalog/provider-operations.ts +1 -1
  144. package/src/console/providers/catalog/routes.ts +3 -2
  145. package/src/console/providers/catalog/store.ts +1914 -1902
  146. package/src/console/providers/detail/routes.ts +1 -0
  147. package/src/console/providers/detail/store.ts +4 -0
  148. package/src/console/providers/oauth/callback-listener.ts +30 -11
  149. package/src/console/providers/oauth/device-egress.ts +123 -0
  150. package/src/console/providers/oauth/routes.ts +123 -16
  151. package/src/console/quota/account-quota-global.ts +14 -24
  152. package/src/console/quota/account-quota-shared.ts +49 -0
  153. package/src/console/quota/account-quota-tenant.ts +11 -23
  154. package/src/console/quota/account-test-egress.ts +2 -2
  155. package/src/console/quota/daily-growth-job.ts +1 -1
  156. package/src/console/quota/quota-view.ts +19 -3
  157. package/src/console/routing/pools/contracts.ts +1 -1
  158. package/src/console/routing/pools/operations.ts +32 -47
  159. package/src/console/routing/pools/probe-verdict.ts +2 -2
  160. package/src/console/routing/pools/relay-deploy.ts +1 -1
  161. package/src/console/routing/pools/store.ts +4 -4
  162. package/src/console/shared/errors.ts +33 -6
  163. package/src/main.ts +2 -3
  164. package/src/network/client.ts +67 -0
  165. package/src/network/egress/bidi-session.ts +322 -0
  166. package/src/network/egress/dial.ts +201 -0
  167. package/src/network/egress/factory.ts +191 -0
  168. package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
  169. package/src/network/egress/tls-profile.ts +123 -0
  170. package/src/network/egress/ws.ts +175 -0
  171. package/src/network/pool/{byte-accounting.ts → accounting.ts} +3 -2
  172. package/src/network/{types.ts → pool/agent-config.ts} +1 -1
  173. package/src/network/pool/agent.ts +2 -2
  174. package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +95 -20
  175. package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
  176. package/src/network/pool/resolver.ts +10 -849
  177. package/src/network/pool/selector.ts +184 -41
  178. package/src/network/ssrf.ts +7 -7
  179. package/src/network/{retry.ts → util/retry.ts} +1 -1
  180. package/src/network/{rate-limit-verdict.ts → verdict.ts} +1 -1
  181. package/src/observability/event-log.ts +7 -361
  182. package/src/observability/performance-metrics.ts +9 -0
  183. package/src/observability/runtime-metrics.ts +14 -9
  184. package/src/observability/telemetry-buffer.ts +16 -26
  185. package/src/observability/telemetry-reconciliation.ts +9 -37
  186. package/src/observability/telemetry-row.ts +125 -0
  187. package/src/persistence/db-mode.ts +4 -1
  188. package/src/persistence/dialect.ts +18 -0
  189. package/src/persistence/migrate.ts +6 -3
  190. package/src/persistence/schema.ts +17 -0
  191. package/src/persistence/ttl-cache.ts +21 -3
  192. package/src/protocol/codecs.ts +59 -2
  193. package/src/protocol/dsml.ts +34 -86
  194. package/src/protocol/markup-stream-parser.ts +195 -0
  195. package/src/protocol/reasoning-text.ts +19 -0
  196. package/src/protocol/request/chat.ts +10 -10
  197. package/src/protocol/request/gemini.ts +8 -2
  198. package/src/protocol/request/messages.ts +34 -22
  199. package/src/protocol/response/chat.ts +12 -20
  200. package/src/protocol/response/messages.ts +34 -7
  201. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  202. package/src/protocol/response/responses.ts +18 -2
  203. package/src/protocol/stop-reason.ts +23 -1
  204. package/src/protocol/think-split.ts +112 -0
  205. package/src/protocol/translation/dialect/qwen.ts +26 -58
  206. package/src/protocol/translation/quirks.ts +15 -0
  207. package/src/protocol/upstream/send-claude-messages.ts +2 -2
  208. package/src/providers/adapters/compatible-adapter.ts +76 -12
  209. package/src/providers/authentication/oauth-client.ts +40 -6
  210. package/src/providers/authentication/oauth-device-flow.ts +105 -11
  211. package/src/providers/authentication/oauth-flow-store.ts +82 -1
  212. package/src/providers/authentication/oauth-refresh-service.ts +766 -758
  213. package/src/providers/configured-provider.ts +148 -0
  214. package/src/providers/default-registry.ts +137 -36
  215. package/src/providers/discovery/discovery-types.ts +7 -0
  216. package/src/providers/discovery/probe-phases.ts +20 -6
  217. package/src/providers/discovery/probing-service.ts +22 -2
  218. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  219. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  220. package/src/providers/integrations/antigravity/antigravity-protocol.ts +16 -1
  221. package/src/providers/integrations/buddy/account.ts +969 -0
  222. package/src/providers/integrations/buddy/capabilities.ts +12 -12
  223. package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
  224. package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +176 -20
  225. package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
  226. package/src/providers/integrations/buddy/codebuddy/shared.ts +41 -26
  227. package/src/providers/integrations/buddy/headers.ts +311 -0
  228. package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
  229. package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
  230. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  231. package/src/providers/integrations/cline/cline-oauth.ts +10 -5
  232. package/src/providers/integrations/cline/cline.ts +5 -6
  233. package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
  234. package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
  235. package/src/providers/integrations/clixad/provider.ts +145 -0
  236. package/src/providers/integrations/codex/codex-oauth.ts +8 -5
  237. package/src/providers/integrations/commandcode/quota.ts +22 -28
  238. package/src/providers/integrations/cursor/cursor-auth.ts +1 -9
  239. package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
  240. package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
  241. package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
  242. package/src/providers/integrations/cursor/cursor-request.ts +26 -12
  243. package/src/providers/integrations/cursor/cursor-shared.ts +8 -5
  244. package/src/providers/integrations/cursor/cursor.ts +574 -110
  245. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  246. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  247. package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
  248. package/src/providers/integrations/deepai/deepai-common.ts +145 -0
  249. package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
  250. package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
  251. package/src/providers/integrations/deepai/provider.ts +42 -0
  252. package/src/providers/integrations/devin/devin-auth.ts +7 -0
  253. package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
  254. package/src/providers/integrations/factory/factory-oauth.ts +33 -51
  255. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  256. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  257. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  258. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  259. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  260. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  261. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  262. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  263. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  264. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  265. package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
  266. package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
  267. package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
  268. package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
  269. package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
  270. package/src/providers/integrations/freepi/freepi-session.ts +158 -0
  271. package/src/providers/integrations/freepi/freepi.ts +80 -54
  272. package/src/providers/integrations/github/github-discovery.ts +30 -13
  273. package/src/providers/integrations/github/github-oauth.ts +217 -44
  274. package/src/providers/integrations/github/github-quota.ts +22 -5
  275. package/src/providers/integrations/github/github.ts +40 -32
  276. package/src/providers/integrations/grok/grok-oauth.ts +52 -49
  277. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  278. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  279. package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
  280. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +42 -13
  281. package/src/providers/integrations/hermes/index.ts +2 -0
  282. package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
  283. package/src/providers/integrations/kilo/kilo.ts +1 -1
  284. package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
  285. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  286. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  287. package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
  288. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  289. package/src/providers/integrations/kiro/kiro.ts +15 -19
  290. package/src/providers/integrations/muse/muse-oauth.ts +5 -5
  291. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  292. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  293. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  294. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  295. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  296. package/src/providers/integrations/openrouter/provider.ts +1 -1
  297. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  298. package/src/providers/integrations/perch/perch-protocol.ts +0 -6
  299. package/src/providers/integrations/perch/perch-quota.ts +2 -16
  300. package/src/providers/integrations/perch/perch.ts +6 -16
  301. package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
  302. package/src/providers/integrations/qoder/qoder-adapter.ts +20 -125
  303. package/src/providers/integrations/qoder/qoder-auth.ts +109 -30
  304. package/src/providers/integrations/qoder/qoder-catalog.ts +21 -6
  305. package/src/providers/integrations/qoder/qoder-chat.ts +32 -29
  306. package/src/providers/integrations/qoder/qoder-checkin.ts +5 -5
  307. package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
  308. package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
  309. package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
  310. package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
  311. package/src/providers/integrations/qoder/qoder-types.ts +9 -0
  312. package/src/providers/integrations/qoder/qoder.ts +3 -26
  313. package/src/providers/integrations/sensenova/provider.ts +144 -0
  314. package/src/providers/integrations/trae/trae-protocol.ts +9 -5
  315. package/src/providers/integrations/trae/trae-quota.ts +21 -29
  316. package/src/providers/integrations/trae/trae-realm.ts +38 -0
  317. package/src/providers/integrations/trae/trae.ts +7 -24
  318. package/src/providers/integrations/vercel/provider.ts +117 -0
  319. package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
  320. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  321. package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
  322. package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
  323. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  324. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  325. package/src/providers/integrations/zro/zro.ts +205 -0
  326. package/src/providers/operations/account-error-classification.ts +199 -307
  327. package/src/providers/operations/account-health-service.ts +82 -1
  328. package/src/providers/operations/account-refresh-status.ts +36 -37
  329. package/src/providers/operations/account-tier.ts +15 -16
  330. package/src/providers/operations/client-version-resolver.ts +1 -1
  331. package/src/providers/operations/client-versions.ts +100 -81
  332. package/src/providers/operations/cooldown-rules.ts +11 -16
  333. package/src/providers/operations/oauth-refresh-lead.ts +46 -22
  334. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  335. package/src/providers/operations/provider-catalog-service.ts +1 -1
  336. package/src/providers/operations/provider-credential-service.ts +118 -51
  337. package/src/providers/operations/provider-rules/github.ts +48 -0
  338. package/src/providers/operations/provider-rules/grok.ts +33 -0
  339. package/src/providers/operations/provider-rules/inception.ts +49 -0
  340. package/src/providers/operations/provider-rules/index.ts +32 -0
  341. package/src/providers/operations/provider-rules/perch.ts +37 -0
  342. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  343. package/src/providers/operations/provider-rules/trae.ts +81 -0
  344. package/src/providers/operations/session-resolution.ts +17 -1
  345. package/src/providers/provider-contracts.ts +6 -4
  346. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  347. package/src/providers/provider-metadata.ts +70 -10
  348. package/src/providers/provider-probe.ts +15 -1
  349. package/src/providers/provider-registry.ts +42 -33
  350. package/src/providers/quota/quota-contracts.ts +2 -0
  351. package/src/providers/quota/quota-limit-windows.ts +16 -5
  352. package/src/providers/quota/quota-support.ts +1 -1
  353. package/src/providers/quota/quota-window-parser.ts +90 -18
  354. package/src/providers/quota/refresh.ts +100 -21
  355. package/src/security/api-key-auth-cache.ts +1 -1
  356. package/src/security/api-key-auth.ts +6 -2
  357. package/src/security/ip-boundary.ts +1 -1
  358. package/src/transport/dispatch/attempt-finalize.ts +15 -14
  359. package/src/transport/dispatch/attempt-loop.ts +73 -19
  360. package/src/transport/dispatch/generation.ts +2 -8
  361. package/src/transport/dispatch/handler-deps.ts +1 -1
  362. package/src/transport/dispatch/leases.ts +1 -1
  363. package/src/transport/dispatch/native-shared.ts +25 -0
  364. package/src/transport/dispatch/proxy-request.ts +8 -46
  365. package/src/transport/dispatch/responses-compact.ts +2 -8
  366. package/src/transport/dispatch/retry-policy.ts +1 -1
  367. package/src/transport/dispatch/streaming/finalize.ts +10 -7
  368. package/src/transport/dispatch/streaming/stream.ts +9 -37
  369. package/src/transport/dispatch/systemone.ts +2 -8
  370. package/src/transport/dispatch/token-count.ts +2 -6
  371. package/src/transport/dispatch/upstream.ts +63 -0
  372. package/src/transport/drain-endpoint.ts +31 -4
  373. package/src/transport/failure-policy.ts +16 -30
  374. package/src/transport/guards/body-policy.ts +7 -1
  375. package/src/transport/guards/error-lifecycle.ts +13 -1
  376. package/src/transport/guards/request-context.ts +22 -14
  377. package/src/transport/request/preparer.ts +81 -71
  378. package/src/transport/routing/bounded-lru.ts +14 -0
  379. package/src/transport/routing/drain-reserve.ts +129 -0
  380. package/src/transport/routing/plan-gate.ts +7 -1
  381. package/src/transport/routing/round-robin.ts +60 -0
  382. package/src/transport/routing/route-catalog.ts +123 -111
  383. package/src/transport/routing/route-model.ts +51 -9
  384. package/src/transport/routing/router.ts +393 -32
  385. package/src/transport/shutdown-notice.ts +1 -1
  386. package/src/transport/surface/adapters.ts +11 -2
  387. package/src/transport/surface/chat/adapter.ts +2 -2
  388. package/src/transport/surface/chat/encode.ts +47 -4
  389. package/src/transport/surface/chat/parse.ts +2 -12
  390. package/src/transport/surface/completion.ts +44 -33
  391. package/src/transport/surface/content-parts.ts +21 -0
  392. package/src/transport/surface/messages/adapter.ts +7 -1
  393. package/src/transport/surface/messages/encode.ts +11 -3
  394. package/src/transport/surface/messages/parse.ts +2 -1
  395. package/src/transport/surface/messages/stream.ts +33 -17
  396. package/src/transport/surface/responses/encode.ts +22 -5
  397. package/src/transport/surface/responses/parse.ts +3 -11
  398. package/src/workers/buddy-growth-tasks.ts +8 -8
  399. package/src/workers/daily-checkin.ts +4 -4
  400. package/src/workers/quota-refresh-sweep.ts +1 -1
  401. package/src/workers/zcode-gift-claim.ts +1 -1
  402. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
  403. package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
  404. package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
  405. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
  406. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
  407. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
  408. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
  409. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
  410. package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
  411. package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
  412. package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
  413. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
  414. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
  415. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
  416. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
  417. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
  418. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
  419. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js +0 -9
  420. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
  421. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
  422. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
  423. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
  424. package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
  425. package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
  426. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
  427. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
  428. package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
  429. package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
  430. package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
  431. package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
  432. package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
  433. package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
  434. package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
  435. package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
  436. package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
  437. package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
  438. package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
  439. package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
  440. package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
  441. package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
  442. package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
  443. package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
  444. package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
  445. package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
  446. package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
  447. package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
  448. package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
  449. package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
  450. package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
  451. package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
  452. package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
  453. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  454. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  455. package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
  456. package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
  457. package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
  458. package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
  459. package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
  460. package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
  461. package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
  462. package/src/network/pool-health.ts +0 -210
  463. package/src/providers/bundled-discovery-providers.ts +0 -98
  464. package/src/providers/configured-openai-providers.ts +0 -120
  465. package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
  466. package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
  467. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
  468. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
  469. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
  470. package/src/providers/integrations/buddy/buddy-identity.ts +0 -300
  471. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
  472. package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
  473. package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
  474. package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
  475. package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
  476. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
  477. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -185
  478. package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
  479. package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -175
  480. package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
  481. package/src/providers/integrations/qoder/chat-proto.ts +0 -328
  482. package/src/providers/integrations/qoder/qoder-grpc.ts +0 -871
  483. package/src/providers/integrations/qoder/qoder-ide.ts +0 -588
  484. package/src/providers/operations/model-discovery-cache.ts +0 -34
  485. package/src/providers/operations/probe-fetch.ts +0 -15
  486. package/src/providers/operations/provider-version-cache.ts +0 -30
  487. package/src/providers/provider-capabilities.ts +0 -33
  488. package/src/providers/provider-credential-envelope.ts +0 -43
  489. /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-C0gTXEoN.js} +0 -0
  490. /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-C0gTXEoN.js.gz} +0 -0
  491. /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
  492. /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
  493. /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
  494. /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
  495. /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
  496. /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
@@ -0,0 +1,144 @@
1
+ // SenseNova providers (CN + Intl) — bearer-authenticated OpenAI-compatible
2
+ // chat + image generation.
3
+ //
4
+ // SenseNova API keys are per-site: keys created on platform.sensenova.cn are
5
+ // rejected by token.sensenova.ai with 401 {"error":{"code":16,"message":"Forbidden"}}
6
+ // (and vice versa), so each deployment is its own provider with its own host
7
+ // and catalog — the same split as CodeBuddy cb/cbcn.
8
+ //
9
+ // Wire facts:
10
+ // - Chat is OpenAI-compatible `/v1/chat/completions` with Bearer auth.
11
+ // - Image generation is `/v1/images/generations` (OpenAI-shaped `{model,
12
+ // prompt, n, size, output_format, response_format, watermark,
13
+ // prompt_extend}`), answering `{data: [{url|b64_json}]}`.
14
+ // - Thinking streams under `reasoning` (delta + message) rather than
15
+ // `reasoning_content`; the shared compatible adapter's chat decoder maps
16
+ // it onto the canonical reasoning event.
17
+
18
+ import { defineModel, type ModelDefinition } from "../../provider-model-definition";
19
+ import type { ApiKeyProviderSpec } from "../../configured-provider";
20
+ import {
21
+ fetchOpenAICompatibleModels,
22
+ } from "../../discovery/openai-model-discovery";
23
+
24
+ /** Chat path below each realm's `/v1` base root. */
25
+ const SENSENOVA_CHAT_PATH = "/chat/completions";
26
+ /** Image-generation path below each realm's `/v1` base root. */
27
+ const SENSENOVA_IMAGE_PATH = "/images/generations";
28
+
29
+ export const SENSENOVA_PROVIDER_ID = "sensenova" as const;
30
+ export const SENSENOVA_INTL_PROVIDER_ID = "sensenova-intl" as const;
31
+
32
+ function sensenovaSpec(providerId: string): ApiKeyProviderSpec {
33
+ return {
34
+ provider_id: providerId as ApiKeyProviderSpec["provider_id"],
35
+ endpoint_paths_by_wire_family: { chat: SENSENOVA_CHAT_PATH },
36
+ };
37
+ }
38
+
39
+ /** SenseNova CN adapter spec. */
40
+ export const SENSENOVA_SPEC: ApiKeyProviderSpec = sensenovaSpec(SENSENOVA_PROVIDER_ID);
41
+
42
+ /** SenseNova Intl adapter spec. */
43
+ export const SENSENOVA_INTL_SPEC: ApiKeyProviderSpec = sensenovaSpec(SENSENOVA_INTL_PROVIDER_ID);
44
+
45
+ /**
46
+ * Shared chat roster (both realms expose the same model families; the intl
47
+ * deployment advertises fewer static rows, and live discovery fills both).
48
+ *
49
+ * Image rows are media-generation rows (serviceKind `imageGeneration`), not
50
+ * chat rows — they answer `/v1/images/generations` and are routed by the
51
+ * native image route.
52
+ */
53
+ const SENSENOVA_CHAT_MODELS: readonly string[] = [
54
+ "sensenova-6.8-flash",
55
+ "sensenova-6.8-flash-lite",
56
+ "sensenova-6.7-flash-lite",
57
+ "deepseek-flash",
58
+ "deepseek-v4-flash",
59
+ "glm-5.3",
60
+ "glm-5.2",
61
+ "kimi-k3",
62
+ ];
63
+
64
+ function makeChatModels(providerId: string): ModelDefinition[] {
65
+ return SENSENOVA_CHAT_MODELS.map((id) =>
66
+ defineModel({
67
+ id,
68
+ providerId,
69
+ wireFamily: "chat",
70
+ endpoint: SENSENOVA_CHAT_PATH,
71
+ ctx: 200_000,
72
+ out: 32_000,
73
+ reasoning: true,
74
+ toolCall: true,
75
+ }),
76
+ );
77
+ }
78
+
79
+ function makeImageModels(providerId: string): ModelDefinition[] {
80
+ return [
81
+ defineModel({
82
+ id: "sensenova-u1.5-lite",
83
+ providerId,
84
+ wireFamily: "chat",
85
+ serviceKind: "imageGeneration",
86
+ endpoint: SENSENOVA_IMAGE_PATH,
87
+ ctx: null,
88
+ out: null,
89
+ toolCall: false,
90
+ output: ["image"],
91
+ }),
92
+ defineModel({
93
+ id: "sensenova-u1.5-fast",
94
+ providerId,
95
+ wireFamily: "chat",
96
+ serviceKind: "imageGeneration",
97
+ endpoint: SENSENOVA_IMAGE_PATH,
98
+ ctx: null,
99
+ out: null,
100
+ toolCall: false,
101
+ output: ["image"],
102
+ }),
103
+ defineModel({
104
+ id: "sensenova-u1-fast",
105
+ providerId,
106
+ wireFamily: "chat",
107
+ serviceKind: "imageGeneration",
108
+ endpoint: SENSENOVA_IMAGE_PATH,
109
+ ctx: null,
110
+ out: null,
111
+ toolCall: false,
112
+ output: ["image"],
113
+ }),
114
+ ];
115
+ }
116
+
117
+ /** Full CN catalog: chat + image rows. */
118
+ export const SENSENOVA_MODELS: readonly ModelDefinition[] = [
119
+ ...makeChatModels(SENSENOVA_PROVIDER_ID),
120
+ ...makeImageModels(SENSENOVA_PROVIDER_ID),
121
+ ];
122
+
123
+ /** Full Intl catalog: chat + image rows. */
124
+ export const SENSENOVA_INTL_MODELS: readonly ModelDefinition[] = [
125
+ ...makeChatModels(SENSENOVA_INTL_PROVIDER_ID),
126
+ ...makeImageModels(SENSENOVA_INTL_PROVIDER_ID),
127
+ ];
128
+
129
+ /** Live `/v1/models` listing for one realm. */
130
+ export function discoverSensenovaModels(options: {
131
+ readonly baseUrl: string;
132
+ readonly credential?: string;
133
+ readonly fetcher?: typeof fetch;
134
+ readonly providerId: string;
135
+ }): Promise<readonly ModelDefinition[] | null> {
136
+ return fetchOpenAICompatibleModels({
137
+ baseUrl: options.baseUrl,
138
+ providerId: options.providerId,
139
+ ...(typeof options.credential === "string" && options.credential.trim().length > 0
140
+ ? { headers: { authorization: `Bearer ${options.credential}` } }
141
+ : {}),
142
+ ...(options.fetcher === undefined ? {} : { fetcher: options.fetcher }),
143
+ });
144
+ }
@@ -187,7 +187,7 @@ export function buildTraeChatBody(
187
187
  }
188
188
  const parts: TraeContentPart[] = [];
189
189
  const toolCalls: TraeToolCall[] = [];
190
- let toolResult: { callId: string; text: string } | undefined;
190
+ const toolResults: Array<{ callId: string; text: string }> = [];
191
191
  for (const part of message.content) {
192
192
  if (part.kind === "toolCall") {
193
193
  toolCalls.push({
@@ -198,7 +198,7 @@ export function buildTraeChatBody(
198
198
  continue;
199
199
  }
200
200
  if (part.kind === "toolResult") {
201
- toolResult = { callId: part.call_id, text: toolResultText(part.content) };
201
+ toolResults.push({ callId: part.call_id, text: toolResultText(part.content) });
202
202
  continue;
203
203
  }
204
204
  const mapped = toTraePart(part);
@@ -207,15 +207,19 @@ export function buildTraeChatBody(
207
207
 
208
208
  // A tool answer becomes its own `tool` turn, which is the only shape
209
209
  // upstream accepts for it — leaving it inside the assistant turn is
210
- // rejected as a broken tool sequence.
211
- if (toolResult !== undefined) {
210
+ // rejected as a broken tool sequence. One turn per result: a message
211
+ // whose content carries multiple `toolResult` parts (parallel tool calls)
212
+ // must emit one `tool` turn per call id, or upstream rejects the missing
213
+ // ones with `tool call failed: … No tool output found for function call
214
+ // <id>`.
215
+ for (const toolResult of toolResults) {
212
216
  messages.push({
213
217
  role: "tool",
214
218
  content: [{ type: "text", text: toolResult.text }],
215
219
  tool_call_id: toolResult.callId,
216
220
  });
217
- continue;
218
221
  }
222
+ if (toolResults.length > 0) continue;
219
223
  // Empty turns are skipped: upstream rejects them, and emitting
220
224
  // `content: ""` turns leaves a ghost turn in the conversation.
221
225
  if (parts.length === 0 && toolCalls.length === 0) continue;
@@ -40,10 +40,9 @@ import { log } from "../../../observability/logger";
40
40
  import {
41
41
  TRAE_ENT_USAGE_PATH,
42
42
  TRAE_ENT_USAGE_PATH_V2,
43
- TRAE_REGION_CORE_HOSTS,
44
- TRAE_REGION_PAY_HOSTS,
45
- TRAE_REALMS,
46
43
  TRAE_SESSION_USAGE_PATH,
44
+ traeCoreForAccount,
45
+ traePayHostForAccount,
47
46
  type TraeRealm,
48
47
  } from "./trae-realm";
49
48
 
@@ -318,6 +317,13 @@ function logTraeQuotaMiss(errors: readonly string[]): void {
318
317
  log.warn(`[quota] trae surfaces unavailable: ${errors.join("; ")}`);
319
318
  }
320
319
 
320
+ /** Only this exact response from both quota surfaces on both realms is terminal auth evidence. */
321
+ function isTraeAuthInvalidation(errors: readonly string[]): boolean {
322
+ return errors.length === 4 && errors.every((error) =>
323
+ /HTTP 401\s+\{[^}]*"code"\s*:\s*1001\b/i.test(error),
324
+ );
325
+ }
326
+
321
327
  /**
322
328
  * Reads one Trae account's quota.
323
329
  *
@@ -326,27 +332,6 @@ function logTraeQuotaMiss(errors: readonly string[]): void {
326
332
  * whose pay host is unreachable still has a readable meter, and reporting
327
333
  * "no quota" for it would park an account that can still serve.
328
334
  */
329
- /**
330
- * The core host for this account.
331
- *
332
- * A provisioned region overrides the realm default: an `sg` account sent to the
333
- * US core is answered 401 code 1001, which looks exactly like a dead token.
334
- */
335
- function coreHost(region: string, config: (typeof TRAE_REALMS)[TraeRealm]): string {
336
- return (region !== "" ? TRAE_REGION_CORE_HOSTS[region] : undefined) ?? config.core;
337
- }
338
-
339
- /**
340
- * The pay host for this account.
341
- *
342
- * The pay surface correlates the credential against its region, too: an `sg`
343
- * account sent to the US pay host is answered 401 code 1001, while the SG pay
344
- * host accepts the same token (verified live 2026-11-06).
345
- */
346
- function payHost(region: string, config: (typeof TRAE_REALMS)[TraeRealm]): string {
347
- return (region !== "" ? TRAE_REGION_PAY_HOSTS[region] : undefined) ?? config.pay;
348
- }
349
-
350
335
  export async function fetchTraeQuota(
351
336
  credential: string,
352
337
  fetcher: FetchLike,
@@ -412,13 +397,13 @@ export async function fetchTraeQuota(
412
397
  * realm is tried: one of them accepts, and that is the answer.
413
398
  */
414
399
  const tryRealms = async (
415
- path: (config: (typeof TRAE_REALMS)[TraeRealm]) => string,
400
+ path: (realm: TraeRealm) => string,
416
401
  surface: string,
417
402
  parse: (payload: unknown) => { readonly windows: readonly ProviderQuotaWindow[]; readonly plan: string | null },
418
403
  ): Promise<{ readonly windows: readonly ProviderQuotaWindow[]; readonly plan: string | null } | null> => {
419
404
  for (const candidate of realm === "cn" ? (["cn", "intl"] as const) : (["intl", "cn"] as const)) {
420
405
  try {
421
- const payload = await call(candidate, path(TRAE_REALMS[candidate]), surface);
406
+ const payload = await call(candidate, path(candidate), surface);
422
407
  return parse(payload);
423
408
  } catch (error) {
424
409
  errors.push(`${candidate} ${surface}: ${cleanError(error)}`);
@@ -427,11 +412,11 @@ export async function fetchTraeQuota(
427
412
  return null;
428
413
  };
429
414
 
430
- const session = await tryRealms((config) => coreHost(region, config) + TRAE_SESSION_USAGE_PATH, "session usage", parseSessionUsage);
415
+ const session = await tryRealms((r) => traeCoreForAccount(r, region) + TRAE_SESSION_USAGE_PATH, "session usage", parseSessionUsage);
431
416
  // The entitlement path differs by realm: the CN realm answers v2 (v1 lacks
432
417
  // `usage_summary` there), the intl realm answers v1 (v2 is a 404).
433
418
  const entPath = realm === "cn" ? TRAE_ENT_USAGE_PATH_V2 : TRAE_ENT_USAGE_PATH;
434
- const entitlement = await tryRealms((config) => payHost(region, config) + entPath, "entitlement", parseEntitlement);
419
+ const entitlement = await tryRealms((r) => traePayHostForAccount(r, region) + entPath, "entitlement", parseEntitlement);
435
420
 
436
421
  const windows = [...(session?.windows ?? []), ...(entitlement?.windows ?? [])];
437
422
  if (!session && !entitlement) {
@@ -443,7 +428,14 @@ export async function fetchTraeQuota(
443
428
  // The detail is kept for the log instead, where it cannot be read as a
444
429
  // balance.
445
430
  logTraeQuotaMiss(errors);
446
- return { source: "trae", plan: null, windows: [], error: null };
431
+ const authInvalidated = isTraeAuthInvalidation(errors);
432
+ return {
433
+ source: "trae",
434
+ plan: null,
435
+ windows: [],
436
+ error: authInvalidated ? errors.join("; ") : null,
437
+ ...(authInvalidated ? { authInvalidated: true } : {}),
438
+ };
447
439
  }
448
440
  return {
449
441
  source: "trae",
@@ -12,6 +12,9 @@
12
12
  /** One Trae deployment. */
13
13
  export type TraeRealm = "intl" | "cn";
14
14
 
15
+ /** Provider ids served by the Trae integration (intl + CN). */
16
+ export const TRAE_PROVIDER_IDS: ReadonlySet<string> = new Set(["trae", "traecn"]);
17
+
15
18
  /** Per-realm upstream addresses, client identity, and auth scheme. */
16
19
  export interface TraeRealmConfig {
17
20
  /** Data-plane host: chat + model list (`/api/ide/v1/*`). */
@@ -245,3 +248,38 @@ export function traeAccountCandidates(realm: TraeRealm): readonly string[] {
245
248
  const config = TRAE_REALMS[realm];
246
249
  return [config.account, ...config.accountFallbacks];
247
250
  }
251
+
252
+ /**
253
+ * The core host for this account.
254
+ *
255
+ * A provisioned region overrides the realm default: an `sg` account sent to the
256
+ * US core is answered 401 code 1001 (which looks exactly like a dead token), so
257
+ * the region lookup must win when it is known. Falling back to the realm keeps
258
+ * older accounts (stored before the region was captured) working exactly as
259
+ * before.
260
+ */
261
+ export function traeCoreForAccount(
262
+ realm: TraeRealm,
263
+ region: string,
264
+ ): string {
265
+ const regional = TRAE_REGION_CORE_HOSTS[region];
266
+ if (regional !== undefined) return regional;
267
+ return traeCoreCandidates(realm)[0] ?? "";
268
+ }
269
+
270
+ /**
271
+ * The pay host for this account.
272
+ *
273
+ * The pay surface correlates the credential against its region, too: an `sg`
274
+ * account sent to the US pay host is answered 401 code 1001, while the SG pay
275
+ * host accepts the same token (verified live 2026-11-06). Falls back to the
276
+ * realm default host when no region is provisioned.
277
+ */
278
+ export function traePayHostForAccount(
279
+ realm: TraeRealm,
280
+ region: string,
281
+ ): string {
282
+ const regional = TRAE_REGION_PAY_HOSTS[region];
283
+ if (regional !== undefined) return regional;
284
+ return TRAE_REALMS[realm].pay;
285
+ }
@@ -31,7 +31,7 @@ import {
31
31
  TRAE_AGENT_ENDPOINT,
32
32
  TRAE_CHAT_ENDPOINT,
33
33
  } from "./trae-protocol";
34
- import { TRAE_REGION_CORE_HOSTS, traeCoreCandidates, TRAE_MODEL_LIST_PATH, TRAE_AGENT_FUNCTION, type TraeRealm } from "./trae-realm";
34
+ import { traeCoreForAccount, TRAE_MODEL_LIST_PATH, TRAE_AGENT_FUNCTION, type TraeRealm } from "./trae-realm";
35
35
 
36
36
  export interface TraeAdapterOptions {
37
37
  readonly providerId?: string;
@@ -269,7 +269,11 @@ class TraeAdapter implements ProviderAdapter {
269
269
  trafficType: agentSurface ? "agent" : "chat",
270
270
  });
271
271
 
272
- const primary = traeCoreForAccount(this.realm, context.credential.auth_state);
272
+ const region =
273
+ typeof context.credential.auth_state?.["region"] === "string"
274
+ ? context.credential.auth_state["region"].toLowerCase()
275
+ : "";
276
+ const primary = traeCoreForAccount(this.realm, region);
273
277
  const path = agentSurface ? TRAE_AGENT_ENDPOINT : TRAE_CHAT_ENDPOINT;
274
278
  const url =
275
279
  candidate.endpoint_path && candidate.endpoint_path.startsWith("http")
@@ -572,10 +576,7 @@ export async function discoverTraeModels(input: {
572
576
  uid: input.uid,
573
577
  appVersion: getTraeVersion(),
574
578
  });
575
- const core = traeCoreForAccount(
576
- input.realm,
577
- input.region === undefined ? undefined : { region: input.region },
578
- );
579
+ const core = traeCoreForAccount(input.realm, (input.region ?? "").toLowerCase());
579
580
 
580
581
  if (input.realm === "intl") {
581
582
  // The agent picker: the only surface that lists the intl roster's configs.
@@ -649,21 +650,3 @@ export async function discoverTraeModels(input: {
649
650
  }
650
651
 
651
652
  export { TRAE_CN_MODELS, TRAE_INTL_MODELS };
652
-
653
- /**
654
- * Core host for one account.
655
- *
656
- * An intl account is provisioned into a region and its token only authenticates
657
- * against that region's host, so the account's own `region` wins over the
658
- * realm default. Falling back to the realm keeps older accounts (stored before
659
- * the region was captured) working exactly as before.
660
- */
661
- function traeCoreForAccount(
662
- realm: TraeRealm,
663
- authState: Readonly<Record<string, unknown>> | undefined,
664
- ): string {
665
- const region = typeof authState?.["region"] === "string" ? authState["region"].toLowerCase() : "";
666
- const regional = TRAE_REGION_CORE_HOSTS[region];
667
- if (regional !== undefined) return regional;
668
- return traeCoreCandidates(realm)[0] ?? "";
669
- }
@@ -0,0 +1,117 @@
1
+ // Vercel AI Gateway provider — TypeScript, Vercel AI Gateway, Bun gateway.
2
+ //
3
+ // Vercel AI Gateway is an OpenAI-compatible routing front for 500+ models
4
+ // across providers. The API key (`vck_...`) is bearer-authenticated against
5
+ // `https://ai-gateway.vercel.sh/v1`.
6
+ //
7
+ // Verified live 2026-11-11 with a free-tier key:
8
+ // - GET /v1/models → 200, 506 rows (OpenRouter-shaped list)
9
+ // - POST /v1/chat/completions → 200 for nvidia/nemotron-3.5-lightning-free
10
+ // - POST /v1/chat/completions → 403 `no_providers_available` for the other
11
+ // zero-priced rows (reflection/beam*, humansand/ampersand-v0.1,
12
+ // stealth/glyph-cluster) — listed at $0 but gated behind a paid/Pro plan.
13
+ // - GET /v1/credits → { balance, token_pass, total_used }
14
+ //
15
+ // The static roster below carries the five $0-priced rows, so the free section
16
+ // is populated before any fetch. Four of them are marked `proOnly` because the
17
+ // upstream refuses them on a free plan (verified live); discovery still finds
18
+ // them and the badge tracks the upstream's own listing.
19
+
20
+ import type { ApiKeyProviderSpec } from "../../configured-provider";
21
+ import { fetchOpenAICompatibleModels } from "../../discovery/openai-model-discovery";
22
+ import { defineModel, type ModelDefinition } from "../../provider-model-definition";
23
+
24
+ const VERCEL_PROVIDER_ID = "vercel" as const;
25
+
26
+ /** Chat path below the `/v1` base root. */
27
+ const VERCEL_CHAT_PATH = "/chat/completions";
28
+
29
+ /**
30
+ * Vercel AI Gateway adapter spec: plain bearer, OpenAI-compatible chat.
31
+ *
32
+ * The gateway accepts standard OpenAI fields (`max_tokens`, `temperature`,
33
+ * `stop`, `tools`, `tool_choice`, `reasoning`, `response_format`, …) and
34
+ * reports usage/cost in extended `provider_metadata`. No bespoke headers are
35
+ * required beyond the bearer, so the generic API-key adapter serves it.
36
+ */
37
+ export const VERCEL_SPEC: ApiKeyProviderSpec = {
38
+ provider_id: VERCEL_PROVIDER_ID,
39
+ endpoint_paths_by_wire_family: {},
40
+ };
41
+
42
+ /**
43
+ * Default rows for the Vercel AI Gateway provider (2026-11-11).
44
+ *
45
+ * Verified live with a free-tier key: only
46
+ * `nvidia/nemotron-3.5-lightning-free` is served on a free plan; the other
47
+ * zero-priced listing rows are refused with 403 `no_providers_available`, so
48
+ * they are not seeded. The two billed rows (`meituan/longcat-2.5`,
49
+ * `inception/mercury-3`) are the operator's picks and carry the capabilities
50
+ * the upstream itself declares — LongCat is the vision-capable coding row,
51
+ * Mercury 3 is the diffusion row. Everything else comes from live discovery.
52
+ */
53
+ export const VERCEL_DEFAULT_MODELS: readonly ModelDefinition[] = [
54
+ defineModel({
55
+ id: "nvidia/nemotron-3.5-lightning-free",
56
+ wireFamily: "chat",
57
+ endpoint: VERCEL_CHAT_PATH,
58
+ ctx: 1_000_000,
59
+ out: 65_000,
60
+ reasoning: true,
61
+ toolCall: true,
62
+ free: true,
63
+ freeTier: true,
64
+ }),
65
+ defineModel({
66
+ id: "meituan/longcat-2.5",
67
+ wireFamily: "chat",
68
+ endpoint: VERCEL_CHAT_PATH,
69
+ ctx: 1_048_576,
70
+ out: 131_072,
71
+ vision: true,
72
+ reasoning: true,
73
+ toolCall: true,
74
+ }),
75
+ defineModel({
76
+ id: "inception/mercury-3",
77
+ wireFamily: "chat",
78
+ endpoint: VERCEL_CHAT_PATH,
79
+ ctx: 1_000_000,
80
+ out: 131_072,
81
+ reasoning: true,
82
+ toolCall: true,
83
+ }),
84
+ ];
85
+
86
+ /**
87
+ * Live `/v1/models` listing.
88
+ *
89
+ * The listing requires the API key, so the credential is forwarded when
90
+ * present; an unauthenticated fetch fails cleanly and the static roster stays
91
+ * authoritative. Every fetched row is kept as the shared reader resolved it
92
+ * (limits/modalities/reasoning/tool flags come from the upstream's own JSON).
93
+ *
94
+ * A fetched id the static roster already knows keeps the roster row whole, not
95
+ * the listing's fields: the upstream does not publish `reasoning`/`tool_call`
96
+ * keys or tier flags, so the shared reader would overwrite the roster's
97
+ * reasoning/tool/freeTier/proOnly statements with its conservative defaults.
98
+ * The roster is the canonical metadata for the models we know; the listing only
99
+ * adds genuinely new ids.
100
+ */
101
+ export async function discoverVercelModels(options: {
102
+ readonly baseUrl: string;
103
+ readonly credential?: string;
104
+ readonly fetcher?: typeof fetch;
105
+ }): Promise<readonly ModelDefinition[] | null> {
106
+ const listed = await fetchOpenAICompatibleModels({
107
+ baseUrl: options.baseUrl,
108
+ providerId: "vercel",
109
+ ...(typeof options.credential === "string" && options.credential.trim().length > 0
110
+ ? { headers: { authorization: `Bearer ${options.credential}` } }
111
+ : {}),
112
+ ...(options.fetcher === undefined ? {} : { fetcher: options.fetcher }),
113
+ });
114
+ if (listed === null) return null;
115
+ const known = new Map(VERCEL_DEFAULT_MODELS.map((model) => [model.modelId, model]));
116
+ return listed.map((model) => known.get(model.modelId) ?? model);
117
+ }
@@ -0,0 +1,96 @@
1
+ // Vercel AI Gateway quota collector.
2
+ //
3
+ // Reads `GET /v1/credits` (Bearer) — the gateway's credit surface. It returns
4
+ // a remaining balance (`balance`, USD string), an optional `token_pass`, and
5
+ // the total spend (`total_used`, USD string). No ceiling is reported (balance
6
+ // is remaining, not a limit), so the window carries absolute remaining/used
7
+ // values and no percent — the pooled credit sum still works. Never throws: a
8
+ // failed read returns a null-result so the router treats it as unknown rather
9
+ // than dead.
10
+
11
+ import type {
12
+ FetchLike,
13
+ ProviderQuotaResult,
14
+ ProviderQuotaWindow,
15
+ } from "../../quota/quota-contracts";
16
+ import {
17
+ cleanError,
18
+ number,
19
+ unsupportedQuota,
20
+ } from "../../quota/quota-contracts";
21
+ import type { QuotaCollectionContext } from "../../quota/quota-support";
22
+
23
+ const VERCEL_CREDITS_URL = "https://ai-gateway.vercel.sh/v1/credits";
24
+
25
+ function readCredits(
26
+ credential: string,
27
+ fetcher: FetchLike,
28
+ ): Promise<Record<string, unknown> | null> {
29
+ return fetcher(VERCEL_CREDITS_URL, {
30
+ method: "GET",
31
+ headers: {
32
+ authorization: `Bearer ${credential}`,
33
+ accept: "application/json",
34
+ },
35
+ signal: AbortSignal.timeout(15_000),
36
+ })
37
+ .then(async (res) => {
38
+ if (!res.ok) return null;
39
+ try {
40
+ return (await res.json()) as Record<string, unknown>;
41
+ } catch {
42
+ return null;
43
+ }
44
+ })
45
+ .catch(() => null);
46
+ }
47
+
48
+ /** Reads the Vercel AI Gateway credit balance and returns a normalized result. */
49
+ export async function fetchVercelQuota(
50
+ credential: string,
51
+ fetcher: FetchLike,
52
+ _context?: QuotaCollectionContext,
53
+ ): Promise<ProviderQuotaResult> {
54
+ try {
55
+ const credits = await readCredits(credential, fetcher);
56
+ if (!credits) {
57
+ return unsupportedQuota("vercel");
58
+ }
59
+ const balance = number(credits["balance"]);
60
+ const totalUsed = number(credits["total_used"]);
61
+ if (balance === null && totalUsed === null) {
62
+ return {
63
+ source: "vercel",
64
+ plan: null,
65
+ windows: [],
66
+ error: "Vercel AI Gateway /v1/credits returned no usable numbers",
67
+ };
68
+ }
69
+ const windows: ProviderQuotaWindow[] = [
70
+ {
71
+ kind: "credits",
72
+ label: "AI Gateway credits",
73
+ accounting: "pooled",
74
+ usedPercent: null,
75
+ remainingPercent: null,
76
+ resetsAt: null,
77
+ used: totalUsed,
78
+ limit: null,
79
+ remaining: balance,
80
+ },
81
+ ];
82
+ return {
83
+ source: "vercel",
84
+ plan: null,
85
+ windows,
86
+ error: null,
87
+ };
88
+ } catch (error) {
89
+ return {
90
+ source: "vercel",
91
+ plan: null,
92
+ windows: [],
93
+ error: cleanError(error),
94
+ };
95
+ }
96
+ }
@@ -135,8 +135,8 @@ export class XaiOAuthClient extends OAuthDeviceFlow {
135
135
  protected override readonly tokenUrl = XAI_TOKEN_URL;
136
136
  protected override readonly scopes = XAI_SCOPE;
137
137
 
138
- override async startDeviceAuth(_context?: OAuthDeviceFlowContext): Promise<OAuthDeviceStartResult> {
139
- const response = await this.fetchFn(XAI_DEVICE_URL, {
138
+ override async startDeviceAuth(context?: OAuthDeviceFlowContext): Promise<OAuthDeviceStartResult> {
139
+ const response = await this.fetchFor(context)(XAI_DEVICE_URL, {
140
140
  method: "POST",
141
141
  headers: formHeaders(),
142
142
  body: new URLSearchParams({ client_id: XAI_CLIENT_ID, scope: XAI_SCOPE }),
@@ -157,9 +157,9 @@ export class XaiOAuthClient extends OAuthDeviceFlow {
157
157
 
158
158
  override async pollDeviceAuth(
159
159
  deviceAuthId: string,
160
- _context?: OAuthDeviceFlowContext,
160
+ context?: OAuthDeviceFlowContext,
161
161
  ): Promise<OAuthDevicePollResult> {
162
- const response = await this.fetchFn(XAI_TOKEN_URL, {
162
+ const response = await this.fetchFor(context)(XAI_TOKEN_URL, {
163
163
  method: "POST",
164
164
  headers: formHeaders(),
165
165
  body: new URLSearchParams({
@@ -203,7 +203,7 @@ export class XaiOAuthClient extends OAuthDeviceFlow {
203
203
  };
204
204
  }
205
205
  const result = this.parseTokenResponse(payload);
206
- const identity = await fetchXaiIdentity(result.access, this.fetchFn);
206
+ const identity = await fetchXaiIdentity(result.access, this.fetchFor(context));
207
207
  const authState = xaiAuthState(identity);
208
208
  return {
209
209
  status: "complete",
@@ -22,7 +22,7 @@
22
22
  import { createHash } from "node:crypto";
23
23
  import { sleep } from "../../../shared/sleep";
24
24
  import { asRecord, asTrimmedString } from "../../../shared/coerce";
25
- import type { ValidatedFetch } from "../../../network/outbound-fetch";
25
+ import type { ValidatedFetch } from "../../../network/egress/fetch";
26
26
 
27
27
  const ZCODE = "https://zcode.z.ai";
28
28
  const BILLING = `${ZCODE}/api/v1/zcode-plan/billing`;
@@ -62,10 +62,10 @@ export class ZcodeStartPlanOAuthClient extends OAuthDeviceFlow {
62
62
  }
63
63
 
64
64
  override async startDeviceAuth(
65
- _context?: OAuthDeviceFlowContext,
65
+ context?: OAuthDeviceFlowContext,
66
66
  ): Promise<OAuthDeviceStartResult & { providerState: string }> {
67
67
  const pollToken = crypto.randomBytes(32).toString("hex");
68
- const response = await this.fetchFn(INIT_URL, {
68
+ const response = await this.fetchFor(context)(INIT_URL, {
69
69
  method: "POST",
70
70
  headers: { ...JSON_HEADERS, authorization: `Bearer ${pollToken}` },
71
71
  body: JSON.stringify({ provider: "zai" }),
@@ -95,7 +95,7 @@ export class ZcodeStartPlanOAuthClient extends OAuthDeviceFlow {
95
95
  if (!pollToken) {
96
96
  return { status: "failed", reason: "sign-in session lost its poll token" };
97
97
  }
98
- const response = await this.fetchFn(`${POLL_BASE}/${encodeURIComponent(deviceAuthId)}`, {
98
+ const response = await this.fetchFor(context)(`${POLL_BASE}/${encodeURIComponent(deviceAuthId)}`, {
99
99
  headers: { ...JSON_HEADERS, authorization: `Bearer ${pollToken}` },
100
100
  });
101
101
  if (response.status === 404) {