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
@@ -131,25 +131,25 @@ export class MuseCodeOAuthClient extends OAuthDeviceFlow {
131
131
  protected override readonly tokenUrl = MUSE_CODE_DEVICE_TOKEN_URL;
132
132
  protected override readonly scopes = "";
133
133
 
134
- override async startDeviceAuth(_context?: OAuthDeviceFlowContext): Promise<OAuthDeviceStartResult> {
134
+ override async startDeviceAuth(context?: OAuthDeviceFlowContext): Promise<OAuthDeviceStartResult> {
135
135
  return this.startGenericDeviceAuth({
136
136
  tokenUrl: MUSE_CODE_DEVICE_TOKEN_URL,
137
137
  deviceAuthUrl: MUSE_CODE_DEVICE_AUTHORIZE_URL,
138
138
  clientId: MUSE_CODE_CLIENT_ID,
139
139
  formHeaders: metaHeaders,
140
- });
140
+ }, context);
141
141
  }
142
142
 
143
143
  override async pollDeviceAuth(
144
144
  deviceAuthId: string,
145
- _context?: OAuthDeviceFlowContext,
145
+ context?: OAuthDeviceFlowContext,
146
146
  ): Promise<OAuthDevicePollResult> {
147
147
  const result = await this.pollGenericDeviceAuth(deviceAuthId, {
148
148
  tokenUrl: MUSE_CODE_DEVICE_TOKEN_URL,
149
149
  deviceAuthUrl: MUSE_CODE_DEVICE_AUTHORIZE_URL,
150
150
  clientId: MUSE_CODE_CLIENT_ID,
151
151
  formHeaders: metaHeaders,
152
- });
152
+ }, context);
153
153
  if (result.status !== "complete") return result;
154
154
  const accessToken = result.result.access;
155
155
  // The key mint runs after approval: a 429, a payment gate, or a transient
@@ -157,7 +157,7 @@ export class MuseCodeOAuthClient extends OAuthDeviceFlow {
157
157
  // exception that escapes the dashboard's poll request.
158
158
  let keyPayload;
159
159
  try {
160
- keyPayload = await requestMuseCodeKey(accessToken, this.fetchFn);
160
+ keyPayload = await requestMuseCodeKey(accessToken, this.fetchFor(context));
161
161
  } catch (error) {
162
162
  return { status: "failed", reason: error instanceof Error ? error.message : "Muse Code key exchange failed" };
163
163
  }
@@ -0,0 +1,2 @@
1
+ export * from "./models";
2
+ export * from "./quota";
@@ -16,8 +16,8 @@
16
16
  * All six serve the chat wire (none is a Responses-native id), and all report
17
17
  * `supported_in_api: true` on the live roster.
18
18
  */
19
- import { defineModel } from "../provider-model-definition";
20
- import type { ModelDefinition } from "../provider-model-definition";
19
+ import { defineModel } from "../../provider-model-definition";
20
+ import type { ModelDefinition } from "../../provider-model-definition";
21
21
 
22
22
  export const OLLAMACLOUD_PROVIDER_ID = "ollamacloud" as const;
23
23
 
@@ -1,6 +1,6 @@
1
- import type { FetchLike, ProviderQuotaResult } from "../quota/quota-contracts";
2
- import { getJson, record, text } from "../quota/quota-contracts";
3
- import { parseQuotaWindows } from "../quota/quota-window-parser";
1
+ import type { FetchLike, ProviderQuotaResult } from "../../quota/quota-contracts";
2
+ import { getJson, record, text } from "../../quota/quota-contracts";
3
+ import { parseQuotaWindows } from "../../quota/quota-window-parser";
4
4
 
5
5
  async function fetchPlan(fetcher: FetchLike, headers: Record<string, string>): Promise<string> {
6
6
  try {
@@ -9,7 +9,7 @@ import { createHash, randomBytes } from "node:crypto";
9
9
  * can pin the exact identity sent before discovery resolves. Keep both in
10
10
  * lockstep when the CLI releases.
11
11
  */
12
- export const OPENCODE_CLIENT_VERSION = "2.2.6";
12
+ export const OPENCODE_CLIENT_VERSION = "2.2.7";
13
13
 
14
14
  let lastTimestamp = 0;
15
15
  let sequenceCounter = 0;
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  getJson,
3
+ isoDate,
3
4
  type FetchLike,
4
5
  type ProviderQuotaResult,
5
6
  type ProviderQuotaWindow,
@@ -42,13 +43,6 @@ function labelFor(kind: string): string {
42
43
  return kind.charAt(0).toUpperCase() + kind.slice(1);
43
44
  }
44
45
 
45
- function resetIso(value: string | null | undefined): string | null {
46
- if (typeof value !== "string" || value.trim() === "") return null;
47
- const parsed = Date.parse(value);
48
- if (!Number.isFinite(parsed)) return null;
49
- return new Date(parsed).toISOString();
50
- }
51
-
52
46
  /**
53
47
  * One window, or `undefined` when the upstream omitted what makes it usable.
54
48
  *
@@ -75,7 +69,7 @@ function windowFor(
75
69
  accounting: "independent",
76
70
  usedPercent: percent,
77
71
  remainingPercent: 100 - percent,
78
- resetsAt: resetIso(raw.resetsAt),
72
+ resetsAt: isoDate(raw.resetsAt),
79
73
  };
80
74
  }
81
75
 
@@ -5,7 +5,7 @@ import { providerBaseUrl } from "../../provider-metadata";
5
5
  import type { DiscoveryInput } from "../../discovery/discovery-types";
6
6
  import { defineModel } from "../../provider-model-definition";
7
7
  import { openrouterFreeRoster } from "../../discovery/openrouter-free-roster";
8
- import { clineReasoningReplayPrepares } from "../buddy/buddy-chat-shared";
8
+ import { clineReasoningReplayPrepares } from "../buddy/chat";
9
9
 
10
10
  const OPENROUTER_PROVIDER_ID = "openrouter" as const;
11
11
 
@@ -0,0 +1,25 @@
1
+ // Perch credential-envelope parsing.
2
+ //
3
+ // Perch accounts authenticate via Supabase OAuth (PKCE), and the stored secret
4
+ // is the OAuth envelope JSON with `accessToken` (or `access_token`). Both the
5
+ // dispatch adapter and the quota collector need the access token out of that
6
+ // envelope; the parse is shared here and each surface keeps its own fallback
7
+ // for a secret that is not an envelope.
8
+
9
+ /**
10
+ * Reads the access token out of a stored Perch OAuth envelope.
11
+ *
12
+ * Returns the token when the secret parses as JSON and carries `accessToken`
13
+ * (or `access_token`) as a string; `null` when it is not such an envelope
14
+ * (not JSON, or JSON without a token field). The caller decides what a
15
+ * `null` means for its surface.
16
+ */
17
+ export function parsePerchCredentialToken(secret: string): string | null {
18
+ try {
19
+ const parsed = JSON.parse(secret) as Record<string, unknown>;
20
+ const found = parsed["accessToken"] ?? parsed["access_token"];
21
+ return typeof found === "string" ? found : null;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
@@ -27,12 +27,6 @@ import { PERCH_STARTER_PINS } from "./perch-realm";
27
27
  /** Default hosted model id when the account endpoint is unreachable. */
28
28
  export const PERCH_DEFAULT_CHAT_MODEL = "wandb-kimi-k2-6";
29
29
 
30
- /** App origin (model-call + ticket + account + plan endpoints). */
31
- export const PERCH_APP_URL = "https://app.perchai.app";
32
-
33
- /** The CLI user-agent the upstream recognizes as a CLI surface. */
34
- export const PERCH_CLI_USER_AGENT = "perchai-cli/unknown";
35
-
36
30
  export interface PerchTurnContext {
37
31
  readonly userId: string | null;
38
32
  readonly workspaceId: string | null;
@@ -11,21 +11,7 @@ import type { FetchLike, ProviderQuotaResult, ProviderQuotaWindow } from "../../
11
11
  import { cleanError, percentWindow, text } from "../../quota/quota-contracts";
12
12
  import type { QuotaCollectionContext } from "../../quota/quota-support";
13
13
  import { PERCH_APP_URL } from "./perch-realm";
14
-
15
- interface PerchSessionCredential {
16
- readonly accessToken?: string;
17
- readonly access_token?: string;
18
- }
19
-
20
- function credentialToken(credential: string): string {
21
- try {
22
- const parsed = JSON.parse(credential) as PerchSessionCredential;
23
- const found = parsed.accessToken ?? parsed.access_token;
24
- return typeof found === "string" ? found : "";
25
- } catch {
26
- return credential.trim();
27
- }
28
- }
14
+ import { parsePerchCredentialToken } from "./perch-credential";
29
15
 
30
16
  async function readAccount(
31
17
  baseUrl: string,
@@ -61,7 +47,7 @@ export async function fetchPerchQuota(
61
47
  _context?: QuotaCollectionContext,
62
48
  ): Promise<ProviderQuotaResult> {
63
49
  try {
64
- const token = credentialToken(credential);
50
+ const token = parsePerchCredentialToken(credential) ?? "";
65
51
  const account = await readAccount(PERCH_APP_URL, token, fetcher);
66
52
  if (!account) {
67
53
  return { source: "perch", plan: null, windows: [], error: "Perch account endpoint unavailable" };
@@ -27,9 +27,8 @@ import {
27
27
  type ProviderId,
28
28
  } from "../../provider-contracts";
29
29
  import { usageFromProvider } from "../../provider-usage";
30
+ import { getPerchVersion } from "../../operations/client-versions";
30
31
  import {
31
- PERCH_APP_URL,
32
- PERCH_CLI_USER_AGENT,
33
32
  PERCH_SAFFRON_SYSTEM_PROMPT,
34
33
  buildPerchModelCallBody,
35
34
  parsePerchSse,
@@ -39,7 +38,8 @@ import {
39
38
  PERCH_MODEL_PINS,
40
39
  PERCH_MODEL_ROOST_TIERS,
41
40
  } from "./perch-catalog";
42
- import { PERCH_PROVIDER_ID } from "./perch-realm";
41
+ import { PERCH_APP_URL, PERCH_PROVIDER_ID } from "./perch-realm";
42
+ import { parsePerchCredentialToken } from "./perch-credential";
43
43
 
44
44
  interface PerchAdapterOptions {
45
45
  readonly providerId?: string;
@@ -96,7 +96,7 @@ export class PerchAdapter implements ProviderAdapter {
96
96
  headers: {
97
97
  authorization: `Bearer ${access}`,
98
98
  "content-type": "application/json",
99
- "user-agent": PERCH_CLI_USER_AGENT,
99
+ "user-agent": `perchai-cli/${getPerchVersion()}`,
100
100
  "x-perch-locale": "en",
101
101
  },
102
102
  body: JSON.stringify({ surface: "cli", profile: "standard" }),
@@ -126,7 +126,7 @@ export class PerchAdapter implements ProviderAdapter {
126
126
  }
127
127
  const secret = readCredentialSecret(context.credential, "Perch credential is required");
128
128
  const modelId = candidate.model_id || request.model || "standard";
129
- const access = secret.startsWith("{") ? parseAccessToken(secret) : secret.trim();
129
+ const access = parsePerchCredentialToken(secret) ?? secret.trim();
130
130
  // Probes pass a validated outbound fetch (with capture wrapped around it);
131
131
  // live dispatch passes one too. `globalThis.fetch` here silently split the
132
132
  // probe from the route's egress policy and left Request Detail empty.
@@ -164,7 +164,7 @@ export class PerchAdapter implements ProviderAdapter {
164
164
  authorization: `Bearer ${access}`,
165
165
  "content-type": "application/json",
166
166
  accept: "text/event-stream",
167
- "user-agent": PERCH_CLI_USER_AGENT,
167
+ "user-agent": `perchai-cli/${getPerchVersion()}`,
168
168
  "x-perch-locale": "en",
169
169
  "x-perch-call-delta": "1",
170
170
  "x-perch-turn-ticket": ticket.ticket,
@@ -281,16 +281,6 @@ function normalizePerchUsage(usage: Record<string, unknown>): Record<string, unk
281
281
  return out;
282
282
  }
283
283
 
284
- function parseAccessToken(secret: string): string {
285
- try {
286
- const parsed = JSON.parse(secret) as Record<string, unknown>;
287
- const found = parsed["accessToken"] ?? parsed["access_token"];
288
- return typeof found === "string" ? found : secret.trim();
289
- } catch {
290
- return secret.trim();
291
- }
292
- }
293
-
294
284
  /**
295
285
  * Builds an upstream verdict for the Perch surface. `origin: "upstream"` is
296
286
  * what lets the account health machine see the failure: `attempt-finalize`
@@ -15,11 +15,15 @@
15
15
  * 401; verified live 2026-11-04).
16
16
  */
17
17
  import type { CartethyiaDatabase } from "../../../persistence/db-handle";
18
- import type { ValidatedFetch } from "../../../network/outbound-fetch";
19
- import { getQoderVersion } from "../../operations/client-versions";
18
+ import type { ValidatedFetch } from "../../../network/egress/fetch";
20
19
  import { qoderRealmForProvider, QODER_REALMS } from "./qoder-realm";
20
+ import { exchangeQoderPatForHost } from "./qoder-auth";
21
21
  import {
22
+ asNumber,
23
+ asRecord,
22
24
  buildCampaignHeaders,
25
+ normalizeCampaign,
26
+ safeParse,
23
27
  } from "./qoder-checkin";
24
28
  import { qoderMachineHeaders } from "./qoder-umid";
25
29
  import {
@@ -38,31 +42,6 @@ export const QODER_CREDIT_PROVIDER_IDS = ["qoder", "qodercn"] as const;
38
42
 
39
43
  const PAT_PREFIX = "pt-";
40
44
 
41
- function asRecord(value: unknown): Record<string, unknown> | null {
42
- if (typeof value !== "object" || value === null) return null;
43
- return value as Record<string, unknown>;
44
- }
45
-
46
- function asNumber(value: unknown): number | null {
47
- if (typeof value === "number" && Number.isFinite(value)) return value;
48
- if (typeof value === "string" && value.trim() !== "") {
49
- const parsed = Number(value);
50
- return Number.isFinite(parsed) ? parsed : null;
51
- }
52
- return null;
53
- }
54
-
55
- function normalizeCampaign(raw: Record<string, unknown>): Record<string, unknown> {
56
- const benefit = asRecord(raw["benefit"]);
57
- return {
58
- campaign_id: String(raw["campaignId"] ?? raw["campaign_id"] ?? ""),
59
- campaign_key: String(raw["campaignKey"] ?? raw["campaign_key"] ?? ""),
60
- action_type: String(raw["actionType"] ?? raw["action_type"] ?? ""),
61
- claim_status: String(raw["claimStatus"] ?? raw["claim_status"] ?? ""),
62
- ...(benefit !== null ? { benefit } : {}),
63
- };
64
- }
65
-
66
45
  /**
67
46
  * Exchanges a PAT (`pt-…`) for a job token (`jt-…`) on the realm's openapi
68
47
  * host. Returns the job token, or `null` when the exchange fails.
@@ -70,7 +49,8 @@ function normalizeCampaign(raw: Record<string, unknown>): Record<string, unknown
70
49
  * The real client binds the machine identity at exchange time
71
50
  * (`getMachineIdentityRequestFields`), so `machine_id` + `machine_token` are
72
51
  * sent when available — the job token is then bound to that machine. The
73
- * User-Agent and Cosy version mirror the real client (`qoder/<version>`).
52
+ * request is the shared `exchangeQoderPatForHost` (same v1 body and client
53
+ * identity headers as the auth path), parameterized by this site's own host.
74
54
  */
75
55
  async function exchangePatToJobToken(
76
56
  base: string,
@@ -78,30 +58,17 @@ async function exchangePatToJobToken(
78
58
  fetcher: ValidatedFetch,
79
59
  machine?: { readonly id?: string; readonly token?: string },
80
60
  ): Promise<string | null> {
81
- try {
82
- const body: Record<string, unknown> = { personal_token: pat };
83
- if (machine?.id) body.machine_id = machine.id;
84
- if (machine?.token) body.machine_token = machine.token;
85
- const version = getQoderVersion();
86
- const res = await fetcher(`${base}/api/v1/jobToken/exchange`, {
87
- method: "POST",
88
- headers: {
89
- "Content-Type": "application/json",
90
- "User-Agent": `qoder/${version}`,
91
- "Cosy-ClientType": "5",
92
- "Cosy-Version": version,
93
- },
94
- body: JSON.stringify(body),
95
- signal: AbortSignal.timeout(25_000),
96
- });
97
- if (!res.ok) return null;
98
- const payload: unknown = await res.json().catch(() => null);
99
- const rec = asRecord(payload);
100
- const token = String(rec?.["token"] ?? rec?.["device_token"] ?? rec?.["access_token"] ?? "");
101
- return token.length > 0 ? token : null;
102
- } catch {
103
- return null;
104
- }
61
+ const exchanged = await exchangeQoderPatForHost({
62
+ pat,
63
+ url: `${base}/api/v1/jobToken/exchange`,
64
+ ...(machine === undefined ? {} : { machine }),
65
+ fetcher,
66
+ signal: AbortSignal.timeout(25_000),
67
+ tokenKeys: ["token", "device_token", "access_token"],
68
+ });
69
+ if (exchanged === null) return null;
70
+ if (!exchanged.response.ok) return null;
71
+ return exchanged.result?.token ?? null;
105
72
  }
106
73
 
107
74
  /**
@@ -248,11 +215,3 @@ export async function claimQoderCredits(args: {
248
215
  return { state: "error", amount: 0, detail: "Claim request failed." };
249
216
  }
250
217
  }
251
-
252
- function safeParse(raw: string): unknown {
253
- try {
254
- return JSON.parse(raw);
255
- } catch {
256
- return null;
257
- }
258
- }
@@ -1,21 +1,17 @@
1
1
  /**
2
2
  * Qoder adapter — transport selection, credential resolution, dispatch.
3
3
  *
4
- * Surface contract (2026-11-07):
4
+ * Surface contract (2026-11-07, verified live 2026-11-11):
5
5
  * - `cosy` (default, production): `/algo/.../agent_chat_generation` on
6
- * `api1` → `api2` → `api3.qoder.sh` (legacy COSY) — the only surface
7
- * verified alive. Accepts free-tier rows (`lite`).
8
- * - `grpc` (`model.chat.ChatService/ChatCompletionStream`): **not the
9
- * default anymore** — its host (`api2-v2.qoder.sh`) is NXDOMAIN since
10
- * 2026-11-07. Kept as an explicit override for probes.
11
- * - `ide` (`/model/v1/chat/completions`): **probe/testing only — never
12
- * production.** It is the only surface that echoes strategy metadata
13
- * (`raw_usage` → provider_model/target) and it streams immediately, but its
14
- * ~256 KiB body cap cannot carry the 1M window the roster declares. Dispatch
15
- * reaches it only when `probe_surface: "ide"` is set (discovery probes) or
16
- * the option `surface: "ide"` is forced explicitly for local testing.
6
+ * the realm's token-kind-specific gateway set (legacy COSY) — the only
7
+ * surface verified alive. A PAT is exchanged to a job token first; job
8
+ * tokens use `api2` → `api1` only, since `api3` rejects them with
9
+ * `105 Login expired` (403). Accepts free-tier rows (`lite`).
10
+ * - The gRPC surface (`model.chat.ChatService/ChatCompletionStream`) and the
11
+ * IDE surface (`/model/v1/chat/completions`) are dead on every host that
12
+ * resolves (gRPC: UNIMPLEMENTED; IDE: 404) since 2026-11-07 and are not
13
+ * dispatched anymore.
17
14
  */
18
- import { randomUUID } from "node:crypto";
19
15
  import { GatewayError } from "../../../protocol/gateway-error";
20
16
  import type { CanonicalEvent, CanonicalRequest } from "../../../protocol/canonical-model";
21
17
  import {
@@ -28,19 +24,16 @@ import {
28
24
  } from "../../provider-contracts";
29
25
  import { abortGatewayError, createUpstreamDeadlineLifecycle } from "../../operations/upstream-deadline";
30
26
  import { getQoderVersion } from "../../operations/client-versions";
31
- import { qoderMachineHostname, qoderMachineOs } from "../../operations/cli-platform";
32
27
  import { mapUpstreamHttpError } from "../../../transport/failure-policy";
33
- import { QODER_CN_MODEL_CONFIGS, QODER_INTL_MODEL_CONFIGS, qoderDiscoveredMaxContexts } from "./qoder-catalog";
28
+ import { QODER_CN_MODEL_CONFIGS, QODER_INTL_MODEL_CONFIGS } from "./qoder-catalog";
34
29
  import { isQoderVirtualModel, qoderDispatchModelId } from "./qoder-virtual";
35
30
  import { isQoderPat, qoderOAuthAuth, qoderSessionId, resolveQoderAuth } from "./qoder-auth";
36
31
  import { buildQoderRequest, callQoder } from "./qoder-chat";
37
- import { buildQoderGrpcRequest, qoderGrpcMetadata, qoderGrpcToCanonicalEvents } from "./qoder-grpc";
38
- import { buildQoderIdeRequest, qoderBodyToCanonicalEvents } from "./qoder-ide";
32
+ import { qoderBodyToCanonicalEvents } from "./qoder-sse";
39
33
  import { estimateQoderSessionCache } from "./qoder-cache";
40
34
  import {
41
35
  QODER_CHAT_PATH,
42
36
  QODER_PROVIDER_ID,
43
- qoderIdeChatUrl,
44
37
  qoderInferenceCandidates,
45
38
  type QoderRealm,
46
39
  } from "./qoder-realm";
@@ -56,48 +49,23 @@ interface QoderAdapterOptions {
56
49
  * Which chat transport to dispatch on.
57
50
  *
58
51
  * `cosy` (default) = legacy COSY (`/algo/.../agent_chat_generation`) on
59
- * `api1` → `api2` → `api3.qoder.sh` — the only surface verified alive
60
- * (2026-11-07; the `api2-v2` model-server host is NXDOMAIN). This path also
61
- * accepts free-tier rows (`lite`).
62
- *
63
- * `grpc` forces the gRPC surface (`model.chat.ChatService/ChatCompletionStream`)
64
- * — **no longer the default**; its host is not verified alive.
65
- *
66
- * `ide` forces `/model/v1` — **probe/testing only, never production**
67
- * (see module doc).
52
+ * the realm's gateway candidates (device tokens: `api1` → `api2` → `api3`;
53
+ * job tokens minted from a PAT: `api2` → `api1` only — `api3` answers
54
+ * `105 Login expired`). The `api2-v2` model-server host is NXDOMAIN, and
55
+ * the gRPC service is UNIMPLEMENTED on every host that resolves. This path
56
+ * also accepts free-tier rows (`lite`).
68
57
  */
69
- readonly surface?: "grpc" | "cosy" | "ide";
58
+ readonly surface?: "cosy";
70
59
  }
71
60
 
72
61
  /** Resolves the transport for one dispatch. Exported for tests. */
73
- export function resolveQoderTransport(
74
- surface: "grpc" | "cosy" | "ide" | undefined,
75
- modelConfig: QoderModelConfig,
76
- ): "grpc" | "cosy" | "ide" {
77
- const chosen = surface ?? "cosy";
78
- // `ide` is an explicit testing override — never auto-selected, and it does
79
- // not apply the free-tier rule (a forced IDE call is a test call).
80
- if (chosen === "ide") return "ide";
81
- // Free-tier rows (`lite`) are refused on gRPC with `quota exceeded` (116);
82
- // they must use the legacy COSY surface.
83
- if (chosen === "grpc" && modelConfig.is_free === true) return "cosy";
84
- return chosen;
85
- }
86
-
87
- /**
88
- * The widest selectable context window for one model key (from the live list's
89
- * `context_config`; falls back to the static roster's stated window).
90
- */
91
- function qoderMaxContextFor(modelId: string, modelConfig: QoderModelConfig, realm: QoderRealm): number {
92
- const discovered = qoderDiscoveredMaxContexts(realm).get(modelId);
93
- if (discovered !== undefined) return discovered;
94
- return modelConfig.max_input_tokens;
62
+ export function resolveQoderTransport(): "cosy" {
63
+ return "cosy";
95
64
  }
96
65
 
97
66
  class QoderAdapter implements ProviderAdapter {
98
67
  readonly provider_id: ProviderId;
99
68
  private readonly realm: QoderRealm;
100
- private readonly surface: "grpc" | "cosy" | "ide";
101
69
  private readonly fetchFn: typeof fetch;
102
70
 
103
71
  constructor(options: QoderAdapterOptions = {}) {
@@ -110,7 +78,6 @@ class QoderAdapter implements ProviderAdapter {
110
78
  // mismatch that made `deepseek-v4.1-flash` fail while `qwen3.8-flash`
111
79
  // passed. This is the same default `resolveQoderTransport(undefined)`
112
80
  // already returns.
113
- this.surface = options.surface ?? "cosy";
114
81
  this.fetchFn = options.fetch ?? globalThis.fetch;
115
82
  }
116
83
 
@@ -190,7 +157,7 @@ class QoderAdapter implements ProviderAdapter {
190
157
 
191
158
  const lifecycle = createUpstreamDeadlineLifecycle(context);
192
159
 
193
- // Outbound fetch respects SSRF binding when provided
160
+ // Outbound fetch respects SSRF binding when provided.
194
161
  const outboundFetch: typeof fetch = (context.outbound_fetch as unknown as typeof fetch) ?? this.fetchFn;
195
162
 
196
163
  const fetcher: (url: string, init: RequestInit) => Promise<Response> = (url, init) =>
@@ -206,78 +173,6 @@ class QoderAdapter implements ProviderAdapter {
206
173
  : qoderOAuthAuth(secret, context.credential.auth_state, context.credential.account_id);
207
174
 
208
175
  // Transport selection — see module doc for the surface contract.
209
- const forceIde = this.surface === "ide" || candidate.probe_surface === "ide";
210
- const useGrpc = !forceIde && resolveQoderTransport(this.surface, modelConfig) === "grpc";
211
-
212
- if (forceIde) {
213
- const ideUrl = qoderIdeChatUrl(this.realm);
214
- const ideBody = buildQoderIdeRequest(modelId, request, modelConfig, auth, this.realm, version);
215
- // Header fingerprint mirrors the desktop client's IDE surface (`PFn` /
216
- // `Hc`): X-Request-ID normalized to uppercase, Cosy-Version, real
217
- // machine OS, and the sanitized hostname on infer SSE requests. The
218
- // User-Agent is the real IDE client's (`Bun/1.4.4`, live capture) and
219
- // `X-Session-ID` matches the body's session id (sha256 userId:model).
220
- const ideHostname = qoderMachineHostname();
221
- const res = await fetcher(ideUrl, {
222
- method: "POST",
223
- headers: {
224
- Authorization: `Bearer ${auth.securityOauthToken}`,
225
- "Content-Type": "application/json",
226
- Accept: "text/event-stream",
227
- "User-Agent": "Bun/1.4.4",
228
- "X-Request-ID": randomUUID().toUpperCase(),
229
- "X-Session-ID": qoderSessionId(auth.userId, modelId),
230
- "Cosy-Version": version,
231
- "Cosy-ClientType": "5",
232
- "Cosy-MachineOS": qoderMachineOs(),
233
- ...(ideHostname.length > 0 ? { "Cosy-MachineHostname": ideHostname } : {}),
234
- },
235
- body: JSON.stringify(ideBody),
236
- });
237
- if (!res.ok) throw await mapUpstreamHttpError(res, "qoder");
238
- if (!res.body) throw new GatewayError("platform_unavailable", 502, "Qoder returned empty body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
239
- lifecycle.release();
240
- yield* this.#withSessionCacheEstimate(
241
- qoderBodyToCanonicalEvents(res.body as ReadableStream<Uint8Array>, lifecycle.signal, modelId),
242
- request,
243
- qoderSessionId(auth.userId, modelId),
244
- );
245
- return;
246
- }
247
-
248
- if (useGrpc) {
249
- // The gRPC request mirrors the HTTP surfaces' OpenAI-shaped body, so
250
- // the shared builder supplies the messages, tool declarations, and
251
- // tool-choice knobs both surfaces expect. Without the `tools` array
252
- // the upstream sees no toolset and answers with a DSML text block (or
253
- // claims no tool exists) instead of a structured `tool_calls`.
254
- const legacy = buildQoderRequest(modelId, request, modelConfig, auth, this.realm);
255
- const grpcRequest = buildQoderGrpcRequest(
256
- modelId,
257
- request,
258
- auth.userId,
259
- qoderMaxContextFor(modelId, modelConfig, this.realm),
260
- {
261
- messages: legacy.messages as Array<Record<string, unknown>>,
262
- tools: legacy.tools as Array<Record<string, unknown>>,
263
- ...(legacy.parameters === undefined
264
- ? {}
265
- : { parameters: legacy.parameters as Record<string, unknown> }),
266
- },
267
- );
268
- const grpcMetadata = qoderGrpcMetadata(auth);
269
- lifecycle.release();
270
- yield* this.#withSessionCacheEstimate(
271
- qoderGrpcToCanonicalEvents(grpcRequest, grpcMetadata, this.realm, lifecycle.signal, context.provider_capture),
272
- request,
273
- qoderSessionId(auth.userId, modelId),
274
- );
275
- return;
276
- }
277
-
278
- // Legacy COSY hosts are tried in order (api1 → api2 → api3) with the
279
- // endpoint path from discovery when present. A network-level failure on
280
- // one host falls through to the next; an HTTP error response surfaces as-is.
281
176
  const cosyCandidates = candidate.endpoint_path?.startsWith("http")
282
177
  ? [candidate.endpoint_path]
283
178
  : qoderInferenceCandidates(this.realm, auth.securityOauthToken).map(