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
@@ -25,10 +25,14 @@ import type {
25
25
  OAuthDeviceStartResult,
26
26
  OAuthExchangeResult,
27
27
  } from "../../authentication/oauth-flow-store";
28
- import type { OAuthTokenRefreshResult } from "../../authentication/oauth-refresh-service";
28
+ import type {
29
+ OAuthRefreshContext,
30
+ OAuthTokenRefresher,
31
+ OAuthTokenRefreshResult,
32
+ } from "../../authentication/oauth-refresh-service";
29
33
  import { OAuthDeviceFlow } from "../../authentication/oauth-device-flow";
30
34
  import type { FetchLike } from "../../authentication/oauth-client";
31
- import { resolveGithubEnterpriseDomain } from "../../../config/providers";
35
+ import { resolveGithubCopilotMode, resolveGithubEnterpriseDomain } from "../../../config/providers";
32
36
  import { randomUUID, createHash } from "node:crypto";
33
37
  import { getOrCreateInstallId } from "../shared/install-id";
34
38
 
@@ -55,6 +59,26 @@ export const GITHUB_OPENCODE_API_HOST = "api.githubcopilot.com" as const;
55
59
  */
56
60
  const GITHUB_OPENCODE_TOKEN_LIFETIME_MS = 365 * 24 * 60 * 60 * 1000;
57
61
 
62
+ export type GithubCopilotCredentialProfile = "opencode-copilot" | "vscode-client-real";
63
+
64
+ export function githubCopilotProfileForClientId(clientId: string): GithubCopilotCredentialProfile {
65
+ return clientId === GITHUB_OPENCODE_CLIENT_ID ? "opencode-copilot" : "vscode-client-real";
66
+ }
67
+
68
+ export function githubCopilotClientIdForProfile(profile: GithubCopilotCredentialProfile): string {
69
+ return profile === "opencode-copilot" ? GITHUB_OPENCODE_CLIENT_ID : GITHUB_CLIENT_ID;
70
+ }
71
+
72
+ export function githubCopilotApiHostForProfile(
73
+ profile: GithubCopilotCredentialProfile,
74
+ domain: string,
75
+ ): string {
76
+ if (profile === "opencode-copilot") {
77
+ return domain === "github.com" ? GITHUB_OPENCODE_API_HOST : `copilot-api.${domain}`;
78
+ }
79
+ return GITHUB_DEFAULT_API_HOST;
80
+ }
81
+
58
82
  /**
59
83
  * Headers every Copilot inference and policy request must carry.
60
84
  *
@@ -129,15 +153,18 @@ export function copilotGitHubDomain(): string {
129
153
  return resolveGithubEnterpriseDomain() || "github.com";
130
154
  }
131
155
 
132
- /** Persisted credential shape: the Copilot token plus the host it was minted for. */
133
- export function encodeGithubCredential(access: string, apiHost: string, githubToken?: string): string {
156
+ /** Persisted credential shape: token, upstream host, and OAuth app origin. */
157
+ export function encodeGithubCredential(
158
+ access: string,
159
+ apiHost: string,
160
+ githubToken: string | undefined,
161
+ profile: GithubCopilotCredentialProfile,
162
+ ): string {
134
163
  return JSON.stringify({
135
164
  access,
136
165
  apiHost,
137
- // The GitHub token is carried in the envelope as well as the refresh
138
- // column: the quota collector is handed only the access value, and the
139
- // usage endpoint takes the GitHub token, not the minted one. It is optional
140
- // so a credential written before this field existed still parses.
166
+ profile,
167
+ // The GitHub token is carried beside the minted token for quota reads.
141
168
  ...(githubToken === undefined || githubToken.length === 0 ? {} : { github: githubToken }),
142
169
  });
143
170
  }
@@ -145,10 +172,93 @@ export function encodeGithubCredential(access: string, apiHost: string, githubTo
145
172
  export interface GithubCredential {
146
173
  readonly access: string;
147
174
  readonly apiHost: string;
175
+ readonly profile: GithubCopilotCredentialProfile;
148
176
  /** The long-lived GitHub token, when the envelope carries it. */
149
177
  readonly github?: string;
150
178
  }
151
179
 
180
+ export interface ImportedGithubCredential {
181
+ readonly credential: string;
182
+ readonly profile: GithubCopilotCredentialProfile;
183
+ readonly refreshToken?: string;
184
+ /** True when a raw GitHub token should be minted on first use in VS Code mode. */
185
+ readonly expiresAt: Date;
186
+ readonly requiresRefresh: boolean;
187
+ /** Non-fatal facts an importer must surface: the credential is usable, but its origin is not fully verified. */
188
+ readonly warnings: readonly string[];
189
+ }
190
+
191
+ /**
192
+ * Binds an exported/imported GitHub credential to the currently selected wire
193
+ * profile. This is a local normalization only; it never contacts GitHub.
194
+ */
195
+ export function bindImportedGithubCredential(
196
+ value: string,
197
+ refreshValue: string | null | undefined,
198
+ profile: GithubCopilotCredentialProfile,
199
+ domain: string,
200
+ expiresAt?: Date,
201
+ ): ImportedGithubCredential {
202
+ const parsed = (() => {
203
+ try {
204
+ return parseGithubCredential(value);
205
+ } catch {
206
+ return undefined;
207
+ }
208
+ })();
209
+ const access = parsed?.access ?? nonEmptyTrimmedString(value);
210
+ if (access === undefined) throw new Error("GitHub Copilot import credential is empty");
211
+ const refresh = nonEmptyTrimmedString(refreshValue);
212
+ const rawToken = parsed?.github ?? refresh;
213
+ const isMinted =
214
+ access.includes("proxy-ep=") ||
215
+ (parsed !== undefined && rawToken !== undefined && access !== rawToken);
216
+ const githubToken = rawToken ?? (isMinted ? undefined : access);
217
+ if (profile === "opencode-copilot") {
218
+ if (isMinted && githubToken === undefined) {
219
+ throw new Error("a VS Code Copilot export needs its raw GitHub refresh token to import as opencode-copilot");
220
+ }
221
+ const token = githubToken ?? access;
222
+ return {
223
+ credential: encodeGithubCredential(
224
+ token,
225
+ githubCopilotApiHostForProfile(profile, domain),
226
+ token,
227
+ profile,
228
+ ),
229
+ profile,
230
+ refreshToken: token,
231
+ expiresAt: new Date(Date.now() + GITHUB_OPENCODE_TOKEN_LIFETIME_MS),
232
+ requiresRefresh: false,
233
+ warnings: [],
234
+ };
235
+ }
236
+ if (isMinted) {
237
+ const apiHost = parsed?.apiHost ?? apiHostFromGithubToken(access);
238
+ return {
239
+ credential: encodeGithubCredential(access, apiHost, githubToken, profile),
240
+ profile,
241
+ ...(githubToken === undefined ? {} : { refreshToken: githubToken }),
242
+ expiresAt: expiresAt ?? new Date(Date.now() + COPILOT_TOKEN_FALLBACK_MS),
243
+ requiresRefresh: false,
244
+ warnings: githubToken === undefined
245
+ ? ["the minted Copilot token has no raw GitHub refresh token; it will expire without a way to mint a replacement"]
246
+ : [],
247
+ };
248
+ }
249
+ const token = githubToken ?? access;
250
+ return {
251
+ credential: encodeGithubCredential(token, GITHUB_DEFAULT_API_HOST, token, profile),
252
+ profile,
253
+ refreshToken: token,
254
+ expiresAt: new Date(0),
255
+ requiresRefresh: true,
256
+ warnings: [
257
+ "the imported token has no Copilot mint metadata; its GitHub OAuth app origin could not be verified",
258
+ ],
259
+ };
260
+ }
261
+
152
262
  /**
153
263
  * Parses a stored Copilot credential.
154
264
  *
@@ -172,9 +282,17 @@ export function parseGithubCredential(value: string): GithubCredential {
172
282
  }
173
283
  const apiHost = nonEmptyTrimmedString(root.apiHost) ?? GITHUB_DEFAULT_API_HOST;
174
284
  const github = nonEmptyTrimmedString(root.github);
285
+ const rawProfile = nonEmptyTrimmedString(root.profile);
286
+ const profile: GithubCopilotCredentialProfile =
287
+ rawProfile === "opencode-copilot" || rawProfile === "vscode-client-real"
288
+ ? rawProfile
289
+ : github !== undefined && access === github && apiHost === GITHUB_OPENCODE_API_HOST
290
+ ? "opencode-copilot"
291
+ : "vscode-client-real";
175
292
  return {
176
293
  access,
177
294
  apiHost,
295
+ profile,
178
296
  ...(github === undefined ? {} : { github }),
179
297
  };
180
298
  }
@@ -236,6 +354,7 @@ export async function mintGithubToken(
236
354
  async function fetchGitHubLogin(
237
355
  githubAccessToken: string,
238
356
  domain: string,
357
+ profile: GithubCopilotCredentialProfile,
239
358
  fetchFn: FetchLike,
240
359
  signal: AbortSignal,
241
360
  ): Promise<string | undefined> {
@@ -244,10 +363,8 @@ async function fetchGitHubLogin(
244
363
  headers: {
245
364
  accept: "application/json",
246
365
  authorization: `Bearer ${githubAccessToken}`,
247
- // The Copilot client headers belong on the Copilot API. Sent here they
248
- // draw a 400, the login is never read, and every account then shares
249
- // the dialog's label — so the second one overwrites the first.
250
- "user-agent": GITHUB_REQUEST_HEADERS["user-agent"]!,
366
+ // The GitHub account API only needs the selected client's user-agent.
367
+ "user-agent": githubCopilotUserAgent(profile),
251
368
  },
252
369
  signal,
253
370
  });
@@ -277,6 +394,7 @@ interface CopilotUserProfile {
277
394
  async function fetchGitHubCopilotEntitlement(
278
395
  githubAccessToken: string,
279
396
  domain: string,
397
+ profile: GithubCopilotCredentialProfile,
280
398
  fetchFn: FetchLike,
281
399
  signal: AbortSignal,
282
400
  ): Promise<{ denied: string } | { denied?: undefined }> {
@@ -289,18 +407,18 @@ async function fetchGitHubCopilotEntitlement(
289
407
  headers: {
290
408
  accept: "application/json",
291
409
  authorization: `Bearer ${githubAccessToken}`,
292
- "user-agent": GITHUB_REQUEST_HEADERS["user-agent"]!,
410
+ "user-agent": githubCopilotUserAgent(profile),
293
411
  "x-github-api-version": "2025-04-01",
294
412
  },
295
413
  signal,
296
414
  });
297
415
  if (!response.ok) return { denied: undefined };
298
- const profile = record(await response.json()) as CopilotUserProfile | undefined;
299
- if (profile?.chat_enabled === false) {
300
- if (profile.can_signup_for_limited) {
416
+ const userProfile = record(await response.json()) as CopilotUserProfile | undefined;
417
+ if (userProfile?.chat_enabled === false) {
418
+ if (userProfile.can_signup_for_limited) {
301
419
  return {
302
420
  denied:
303
- "This GitHub account is not signed up for GitHub Copilot. Sign up for Copilot Free at https://github.com/features/copilot/plans and connect again.",
421
+ "This GitHub account is not assigned to use GitHub Copilot. Activate Copilot Free at https://github.com/features/copilot and connect again.",
304
422
  };
305
423
  }
306
424
  return {
@@ -316,7 +434,43 @@ async function fetchGitHubCopilotEntitlement(
316
434
  }
317
435
  }
318
436
 
319
- export class GithubOAuthClient extends OAuthDeviceFlow {
437
+ function parseGithubDeviceProviderState(value: string | undefined): { domain?: string; clientId?: string; profile?: GithubCopilotCredentialProfile } | undefined {
438
+ if (value === undefined || value.length === 0) return undefined;
439
+ try {
440
+ const parsed: unknown = JSON.parse(value);
441
+ const state = record(parsed);
442
+ const domain = nonEmptyTrimmedString(state?.domain);
443
+ const clientId = nonEmptyTrimmedString(state?.clientId);
444
+ const rawProfile = nonEmptyTrimmedString(state?.profile);
445
+ const profile = rawProfile === "opencode-copilot" || rawProfile === "vscode-client-real"
446
+ ? rawProfile
447
+ : undefined;
448
+ if (domain !== undefined) {
449
+ return {
450
+ domain,
451
+ ...(clientId === undefined ? {} : { clientId }),
452
+ ...(profile === undefined ? {} : { profile }),
453
+ };
454
+ }
455
+ } catch {
456
+ // Older in-flight logins persisted the bare GitHub domain as providerState.
457
+ }
458
+ return { domain: value };
459
+ }
460
+
461
+ function githubCopilotUserAgent(profile: GithubCopilotCredentialProfile): string {
462
+ return profile === "opencode-copilot" ? "opencode/0.1.0" : GITHUB_REQUEST_HEADERS["user-agent"]!;
463
+ }
464
+
465
+ function readGithubCopilotProfile(
466
+ authState: Readonly<Record<string, unknown>> | undefined,
467
+ ): GithubCopilotCredentialProfile | undefined {
468
+ const value = authState?.["github_copilot_profile"];
469
+ return value === "opencode-copilot" || value === "vscode-client-real" ? value : undefined;
470
+ }
471
+
472
+ export class GithubOAuthClient extends OAuthDeviceFlow implements OAuthTokenRefresher {
473
+ readonly requiresAccessToken = true;
320
474
  override readonly supportsDeviceCode = true;
321
475
  override readonly supportsBrowserCode = false;
322
476
 
@@ -330,20 +484,20 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
330
484
 
331
485
  constructor(
332
486
  fetchFn: FetchLike = globalThis.fetch,
333
- clientId: string = GITHUB_CLIENT_ID,
487
+ clientId: string = githubCopilotClientIdForProfile(resolveGithubCopilotMode()),
334
488
  ) {
335
489
  super(fetchFn);
336
490
  this.clientId = clientId;
337
491
  }
338
492
 
339
- override async startDeviceAuth(_context?: OAuthDeviceFlowContext): Promise<OAuthDeviceStartResult> {
493
+ override async startDeviceAuth(context?: OAuthDeviceFlowContext): Promise<OAuthDeviceStartResult> {
340
494
  const domain = copilotGitHubDomain();
341
- const response = await this.fetchFn(`https://${domain}/login/device/code`, {
495
+ const response = await this.fetchFor(context)(`https://${domain}/login/device/code`, {
342
496
  method: "POST",
343
497
  headers: {
344
498
  accept: "application/json",
345
499
  "content-type": "application/x-www-form-urlencoded",
346
- "user-agent": GITHUB_REQUEST_HEADERS["user-agent"]!,
500
+ "user-agent": githubCopilotUserAgent(githubCopilotProfileForClientId(this.clientId)),
347
501
  },
348
502
  body: new URLSearchParams({ client_id: this.clientId, scope: "read:user" }),
349
503
  });
@@ -380,9 +534,13 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
380
534
  deviceAuthId: deviceCode,
381
535
  intervalSeconds: interval,
382
536
  expiresInSeconds: expiresIn,
383
- // The host is captured at start so the poll mints against the same GitHub
384
- // domain the device code was issued by.
385
- providerState: domain,
537
+ // Pin the OAuth app and host across the complete device flow, even if
538
+ // process configuration changes before the user finishes authorization.
539
+ providerState: JSON.stringify({
540
+ domain,
541
+ clientId: this.clientId,
542
+ profile: githubCopilotProfileForClientId(this.clientId),
543
+ }),
386
544
  };
387
545
  }
388
546
 
@@ -390,16 +548,19 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
390
548
  deviceAuthId: string,
391
549
  context?: OAuthDeviceFlowContext,
392
550
  ): Promise<OAuthDevicePollResult> {
393
- const domain = context?.providerState ?? copilotGitHubDomain();
394
- const response = await this.fetchFn(`https://${domain}/login/oauth/access_token`, {
551
+ const providerState = parseGithubDeviceProviderState(context?.providerState);
552
+ const domain = providerState?.domain ?? copilotGitHubDomain();
553
+ const clientId = providerState?.clientId ?? this.clientId;
554
+ const profile = providerState?.profile ?? githubCopilotProfileForClientId(clientId);
555
+ const response = await this.fetchFor(context)(`https://${domain}/login/oauth/access_token`, {
395
556
  method: "POST",
396
557
  headers: {
397
558
  accept: "application/json",
398
559
  "content-type": "application/x-www-form-urlencoded",
399
- "user-agent": GITHUB_REQUEST_HEADERS["user-agent"]!,
560
+ "user-agent": githubCopilotUserAgent(profile),
400
561
  },
401
562
  body: new URLSearchParams({
402
- client_id: this.clientId,
563
+ client_id: clientId,
403
564
  device_code: deviceAuthId,
404
565
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
405
566
  }),
@@ -451,6 +612,7 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
451
612
  const entitlement = await fetchGitHubCopilotEntitlement(
452
613
  githubToken,
453
614
  domain,
615
+ profile,
454
616
  this.fetchFn,
455
617
  AbortSignal.timeout(10_000),
456
618
  );
@@ -462,7 +624,7 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
462
624
  // no minted Copilot token. The VS Code mint endpoint
463
625
  // (`/copilot_internal/v2/token`) rejects non-VS-Code client ids with 403
464
626
  // (GitHub's anti-scraping gate), so an OpenCode login must not mint.
465
- if (this.clientId !== GITHUB_OPENCODE_CLIENT_ID) {
627
+ if (profile === "vscode-client-real") {
466
628
  minted = await mintGithubToken(githubToken, domain, this.fetchFn);
467
629
  }
468
630
  } catch (error) {
@@ -470,40 +632,50 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
470
632
  // not an exception that escapes the dashboard's poll request.
471
633
  return { status: "failed", reason: error instanceof Error ? error.message : "GitHub Copilot token mint failed" };
472
634
  }
473
- const label = await fetchGitHubLogin(githubToken, domain, this.fetchFn, AbortSignal.timeout(10_000));
635
+ const label = await fetchGitHubLogin(githubToken, domain, profile, this.fetchFn, AbortSignal.timeout(10_000));
474
636
  const access = minted === undefined ? githubToken : minted.access;
475
- const apiHost = minted === undefined ? GITHUB_OPENCODE_API_HOST : minted.apiHost;
637
+ const apiHost = minted === undefined ? githubCopilotApiHostForProfile(profile, domain) : minted.apiHost;
476
638
  const expiresAt =
477
639
  minted === undefined
478
640
  ? new Date(Date.now() + GITHUB_OPENCODE_TOKEN_LIFETIME_MS)
479
641
  : minted.expiresAt;
480
642
  const result: OAuthExchangeResult = {
481
- access: encodeGithubCredential(access, apiHost, githubToken),
482
- // The GitHub token is the refresh value: it is what re-mints the Copilot
483
- // token (VS Code app) or is the raw credential itself (OpenCode app),
484
- // depending on which app the flow presented.
643
+ access: encodeGithubCredential(access, apiHost, githubToken, profile),
644
+ // The raw GitHub token is the refresh value for the selected profile.
485
645
  refresh: githubToken,
486
646
  expiresAt,
647
+ auth_state: { github_copilot_profile: profile },
487
648
  ...(label === undefined ? {} : { accountLabel: label }),
488
649
  };
489
650
  return { status: "complete", result };
490
651
  }
491
652
 
492
653
 
493
- override async refresh(refreshToken: string, signal?: AbortSignal): Promise<OAuthTokenRefreshResult> {
494
- // OpenCode's app uses the raw GitHub token directly — there is no minted
495
- // Copilot token to re-mint, and the VS Code mint endpoint rejects the
496
- // OpenCode client id. Refresh is then a no-op that keeps the credential
497
- // valid on its far-future lifetime.
498
- if (this.clientId === GITHUB_OPENCODE_CLIENT_ID) {
654
+ override async refresh(
655
+ refreshToken: string,
656
+ signal?: AbortSignal,
657
+ context?: OAuthRefreshContext,
658
+ ): Promise<OAuthTokenRefreshResult> {
659
+ const profile = githubCopilotProfileForClientId(this.clientId);
660
+ const storedProfile =
661
+ readGithubCopilotProfile(context?.auth_state) ??
662
+ (context?.access_token === undefined
663
+ ? undefined
664
+ : parseGithubCredential(context.access_token).profile);
665
+ if (storedProfile !== undefined && storedProfile !== profile) {
666
+ throw new Error("github_copilot_profile_mismatch: reconnect this GitHub account under the selected CARTETHYIA_GITHUB_COPILOT_MODE");
667
+ }
668
+ if (profile === "opencode-copilot") {
499
669
  return {
500
670
  access: encodeGithubCredential(
501
671
  refreshToken,
502
- GITHUB_OPENCODE_API_HOST,
672
+ githubCopilotApiHostForProfile(profile, copilotGitHubDomain()),
503
673
  refreshToken,
674
+ profile,
504
675
  ),
505
676
  refresh: refreshToken,
506
677
  expiresAt: new Date(Date.now() + GITHUB_OPENCODE_TOKEN_LIFETIME_MS),
678
+ auth_state: { github_copilot_profile: profile },
507
679
  };
508
680
  }
509
681
  const minted = await mintGithubToken(
@@ -513,10 +685,11 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
513
685
  signal,
514
686
  );
515
687
  return {
516
- access: encodeGithubCredential(minted.access, minted.apiHost, refreshToken),
688
+ access: encodeGithubCredential(minted.access, minted.apiHost, refreshToken, profile),
517
689
  // The GitHub token is unchanged by a re-mint, so it stays the refresh value.
518
690
  refresh: refreshToken,
519
691
  expiresAt: minted.expiresAt,
692
+ auth_state: { github_copilot_profile: profile },
520
693
  };
521
694
  }
522
695
  }
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import type { FetchLike, ProviderQuotaResult, ProviderQuotaWindow } from "../../quota/quota-contracts";
14
14
  import { getJson, isoDate, number, percentWindow, quotaRecord, text } from "../../quota/quota-contracts";
15
- import { resolveGithubEnterpriseDomain } from "../../../config/providers";
15
+ import { resolveGithubCopilotMode, resolveGithubEnterpriseDomain } from "../../../config/providers";
16
16
  import { GITHUB_REQUEST_HEADERS, parseGithubCredential } from "./github-oauth";
17
17
 
18
18
  /**
@@ -113,14 +113,31 @@ export async function fetchGithubQuota(
113
113
  // predates that field, the minted token is sent instead and the upstream
114
114
  // answers with an auth error that surfaces honestly rather than as a
115
115
  // fabricated window.
116
+ const mode = resolveGithubCopilotMode();
117
+ if (parsed.profile !== mode) {
118
+ return {
119
+ source: "github",
120
+ plan: null,
121
+ windows: [],
122
+ error: `GitHub Copilot account is bound to ${parsed.profile}; reconnect it after selecting ${mode}.`,
123
+ };
124
+ }
116
125
  const token = parsed.github ?? parsed.access;
117
126
  const domain = resolveGithubEnterpriseDomain() || "github.com";
127
+ const headers = mode === "opencode-copilot"
128
+ ? {
129
+ authorization: `Bearer ${token}`,
130
+ "user-agent": "opencode/0.1.0",
131
+ "x-github-api-version": "2025-04-01",
132
+ }
133
+ : {
134
+ authorization: `Bearer ${token}`,
135
+ ...GITHUB_REQUEST_HEADERS,
136
+ "x-github-api-version": "2025-04-01",
137
+ };
118
138
  const body = await getJson(
119
139
  `https://api.${domain}/copilot_internal/user`,
120
- {
121
- authorization: `Bearer ${token}`,
122
- ...GITHUB_REQUEST_HEADERS,
123
- },
140
+ headers,
124
141
  fetcher,
125
142
  );
126
143
  return parseGithubQuota(body);
@@ -387,6 +387,18 @@ export const GITHUB_MODELS: readonly ModelDefinition[] = [
387
387
  * pipeline, so only the two Copilot-specific facts (the host and the client
388
388
  * identity) are supplied here.
389
389
  */
390
+ function assertGithubProfileMatchesMode(credential: GithubCredential): void {
391
+ const mode = resolveGithubCopilotMode();
392
+ if (credential.profile !== mode) {
393
+ throw new GatewayError(
394
+ "authentication_failed",
395
+ 401,
396
+ `GitHub Copilot account is bound to ${credential.profile}; reconnect it after selecting ${mode}.`,
397
+ { credentialEvidence: true, accountScope: true },
398
+ );
399
+ }
400
+ }
401
+
390
402
  class GithubAdapter extends OpenAICompatibleAdapter {
391
403
  readonly #fallbackBaseUrl: string;
392
404
  /**
@@ -433,11 +445,14 @@ class GithubAdapter extends OpenAICompatibleAdapter {
433
445
  throw new GatewayError("authentication_failed", 401, "GitHub Copilot credential is missing (request {requestId}). Wrong key, right door — check the credential 🔑");
434
446
  }
435
447
  const credential = new TextDecoder().decode(secret);
448
+ const parsedCredential = parseGithubCredential(credential);
449
+ assertGithubProfileMatchesMode(parsedCredential);
436
450
  const autoInput = autoPrompt(request);
437
451
  const sessionId = resolveInboundSessionId(context, request);
452
+ const profile = resolveGithubCopilotMode();
438
453
  const cacheKey = sessionId === undefined || sessionId.startsWith("aff_")
439
454
  ? undefined
440
- : this.#autoDecisionCacheKey(credential, sessionId, tier);
455
+ : this.#autoDecisionCacheKey(credential, sessionId, tier, profile);
441
456
  let autoDecision = cacheKey === undefined
442
457
  ? undefined
443
458
  : this.#getCachedAutoDecision(cacheKey, autoInput.hasImage);
@@ -548,18 +563,10 @@ class GithubAdapter extends OpenAICompatibleAdapter {
548
563
  if (credential === undefined) {
549
564
  throw new GatewayError("authentication_failed", 401, "GitHub Copilot credential is missing (request {requestId}). Wrong key, right door — check the credential 🔑");
550
565
  }
551
- // `opencode-copilot` mode (default): raw GitHub token + minimal headers,
552
- // proven the most stable (single token, no mint expiry). The reference
553
- // mode (`vscode-client-real`) uses the minted Copilot token + full VS Code
554
- // headers — but a stale/expired minted token fails with 403 and the
555
- // reference headers on /auto can answer 400 "invalid apiVersion" unless the
556
- // minted token is fresh, so the default stays opencode.
557
- const headers =
558
- resolveGithubCopilotMode() === "opencode-copilot" &&
559
- credential.github !== undefined &&
560
- credential.github.length > 0
561
- ? await this.#opencodeStyleHeaders(context, request, credential)
562
- : await this.prepareHeaders(context, request, candidate);
566
+ assertGithubProfileMatchesMode(credential);
567
+ // /auto remains Cartethyia's Auto selection feature in both wire profiles;
568
+ // only prepareHeaders chooses the OAuth app's matching identity.
569
+ const headers = await this.prepareHeaders(context, request, candidate);
563
570
  const payload: Record<string, unknown> = { prompt, tier };
564
571
  if (hasImage) payload.has_image = true;
565
572
  const outboundFetch = context.outbound_fetch ?? this.#fetch ?? globalThis.fetch;
@@ -630,12 +637,13 @@ class GithubAdapter extends OpenAICompatibleAdapter {
630
637
  credentialValue: string,
631
638
  sessionId: string,
632
639
  tier: "efficiency" | "balance" | "intelligence",
640
+ profile: ReturnType<typeof resolveGithubCopilotMode>,
633
641
  ): string {
634
642
  const credential = parseGithubCredential(credentialValue);
635
643
  const account = createHash("sha256")
636
644
  .update(`${credential.apiHost}:${credential.access}`)
637
645
  .digest("hex");
638
- return `${account}:${tier}:${sessionId}`;
646
+ return `${account}:${profile}:${tier}:${sessionId}`;
639
647
  }
640
648
 
641
649
  #getCachedAutoDecision(
@@ -684,6 +692,17 @@ class GithubAdapter extends OpenAICompatibleAdapter {
684
692
  request: CanonicalRequest,
685
693
  candidate: ProviderDispatchTarget,
686
694
  ): Promise<Record<string, string>> {
695
+ const credentialSecret = context.credential.secret;
696
+ if (credentialSecret !== undefined && credentialSecret.length > 0) {
697
+ const credential = parseGithubCredential(new TextDecoder().decode(credentialSecret));
698
+ assertGithubProfileMatchesMode(credential);
699
+ if (credential.profile === "opencode-copilot") {
700
+ const opencode = await this.#opencodeStyleHeaders(context, request, credential);
701
+ const token = this.#autoSessionTokens.get(request);
702
+ if (token !== undefined) opencode["Copilot-Session-Token"] = token;
703
+ return opencode;
704
+ }
705
+ }
687
706
  const headers = await super.prepareHeaders(context, request, candidate);
688
707
  // The Copilot API rejects a request that does not identify a Copilot
689
708
  // client, so these are a protocol requirement. `applyRouteUserAgent` has
@@ -691,31 +710,20 @@ class GithubAdapter extends OpenAICompatibleAdapter {
691
710
  // it and wins — the route-level User-Agent is for generic OpenAI-compatible
692
711
  // hosts and would be rejected here.
693
712
  Object.assign(headers, GITHUB_REQUEST_HEADERS);
694
- const secret = context.credential.secret;
713
+ const referenceSecret = context.credential.secret;
695
714
  let parsedCredential: ReturnType<typeof parseGithubCredential> | undefined;
696
- if (secret !== undefined && secret.length > 0) {
715
+ if (referenceSecret !== undefined && referenceSecret.length > 0) {
697
716
  try {
698
- parsedCredential = parseGithubCredential(new TextDecoder().decode(secret));
717
+ parsedCredential = parseGithubCredential(new TextDecoder().decode(referenceSecret));
699
718
  } catch {
700
719
  // Unparseable credential; fall back to the reference identity below.
701
720
  }
702
721
  }
722
+ if (parsedCredential !== undefined) assertGithubProfileMatchesMode(parsedCredential);
703
723
  const autoSessionToken = this.#autoSessionTokens.get(request);
704
- // Auto-selected requests stream behind the session token. In the default
705
- // `opencode-copilot` mode (raw GitHub token + minimal headers, proven 200
706
- // on /auto and /responses), the whole request uses the OpenCode shape. The
707
- // reference mode (`vscode-client-real`) keeps the reference identity and
708
- // the minted Copilot token.
709
- if (autoSessionToken !== undefined && parsedCredential?.github !== undefined) {
710
- if (resolveGithubCopilotMode() === "opencode-copilot") {
711
- const opencode = await this.#opencodeStyleHeaders(context, request, parsedCredential);
712
- for (const key of Object.keys(headers)) delete headers[key];
713
- Object.assign(headers, opencode);
714
- headers["Copilot-Session-Token"] = autoSessionToken;
715
- return headers;
716
- }
717
- headers["Copilot-Session-Token"] = autoSessionToken;
718
- } else if (autoSessionToken !== undefined) {
724
+ // Auto-selected requests in either profile keep the session token returned
725
+ // by Cartethyia's /auto selection step.
726
+ if (autoSessionToken !== undefined) {
719
727
  headers["Copilot-Session-Token"] = autoSessionToken;
720
728
  }
721
729
  const requestId = context.request_id ?? randomUUID();