cartethyia 2.4.5 → 2.4.6

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 (303) hide show
  1. package/.env.example +4 -1
  2. package/README.md +18 -0
  3. package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
  4. package/dist/dashboard/assets/{BannedPage-DUrXpp-X.js → BannedPage-QhvbJB9o.js} +1 -1
  5. package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolDetailPage-BzGevuql.js → CliToolDetailPage-Bcy150Jo.js} +1 -1
  7. package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
  8. package/dist/dashboard/assets/{CliToolsPage-BaRM_7We.js → CliToolsPage-DyNXefph.js} +1 -1
  9. package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
  10. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +1 -0
  11. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js +1 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
  14. package/dist/dashboard/assets/{CustomizationPage-DqUGcoH0.js → CustomizationPage-D6jEU7nL.js} +1 -1
  15. package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
  16. package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js +1 -0
  17. package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-DbhTGfhb.js → LoginPage-BJlMVN4U.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
  20. package/dist/dashboard/assets/ModelPicker-Br3CXEau.js +1 -0
  21. package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-C3NIzsDx.js → OverviewPage-CQxXbo2g.js} +1 -1
  23. package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +3 -0
  25. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
  26. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
  27. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +1 -0
  28. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
  29. package/dist/dashboard/assets/ProxyPage-CXiumA53.js +7 -0
  30. package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
  31. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +1 -0
  32. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
  33. package/dist/dashboard/assets/{SettingsPage-YxRml8AT.js → SettingsPage-BHW4Zt06.js} +1 -1
  34. package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
  35. package/dist/dashboard/assets/SetupPage-VOYC_lIa.js +1 -0
  36. package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
  37. package/dist/dashboard/assets/SortableList-DpOTnGuH.js +1 -0
  38. package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
  39. package/dist/dashboard/assets/StudioPage-BNIDLiU6.js +31 -0
  40. package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
  41. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
  42. package/dist/dashboard/assets/{UsagePage-Djes-Epy.js → UsagePage-BAFaSDQV.js} +14 -14
  43. package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
  44. package/dist/dashboard/assets/api-keys-BETfaMYZ.js +1 -0
  45. package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
  46. package/dist/dashboard/assets/app-BLCvGdzb.css +2 -0
  47. package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
  48. package/dist/dashboard/assets/{app-BDJWMThu.js → app-Cjq7nrwu.js} +3 -3
  49. package/dist/dashboard/assets/app-Cjq7nrwu.js.gz +0 -0
  50. package/dist/dashboard/assets/arrow-left-CpBxkoxI.js.gz +0 -0
  51. package/dist/dashboard/assets/arrow-right-Dl5yKnp8.js.gz +0 -0
  52. package/dist/dashboard/assets/{assert-Bwh6DJTK.js → assert-7J6_LVD3.js} +1 -1
  53. package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
  54. package/dist/dashboard/assets/backup-file-gdKIlg8U.js.gz +0 -0
  55. package/dist/dashboard/assets/{badge-BwmwEOgA.js → badge-BTyGk_DL.js} +1 -1
  56. package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
  57. package/dist/dashboard/assets/{banner-BKBfKTqn.js → banner-bujTWYM4.js} +1 -1
  58. package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
  59. package/dist/dashboard/assets/brain-Cwed_72z.js.gz +0 -0
  60. package/dist/dashboard/assets/{card-BTdbRFFg.js → card-B0Bnx5CA.js} +1 -1
  61. package/dist/dashboard/assets/card-B0Bnx5CA.js.gz +0 -0
  62. package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
  63. package/dist/dashboard/assets/chevron-right-BXIy5l3h.js.gz +0 -0
  64. package/dist/dashboard/assets/circle-alert-CntYHV5v.js.gz +0 -0
  65. package/dist/dashboard/assets/circle-check-BU3v_mFW.js.gz +0 -0
  66. package/dist/dashboard/assets/cli-tools-DE2ug812.js +1 -0
  67. package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
  68. package/dist/dashboard/assets/clipboard-button-R_rArH8S.js +1 -0
  69. package/dist/dashboard/assets/clipboard-button-R_rArH8S.js.gz +0 -0
  70. package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
  71. package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
  72. package/dist/dashboard/assets/{dialog-iocKA3jr.js → dialog-BWIP_EvW.js} +1 -1
  73. package/dist/dashboard/assets/dialog-BWIP_EvW.js.gz +0 -0
  74. package/dist/dashboard/assets/dialog-actions-B1axpEg7.js.gz +0 -0
  75. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  76. package/dist/dashboard/assets/download-dRFo5_Wu.js.gz +0 -0
  77. package/dist/dashboard/assets/{drawer-CDKOTZdY.js → drawer-BKcgVugF.js} +1 -1
  78. package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
  79. package/dist/dashboard/assets/entry-7dpgvpJ3.js +4 -0
  80. package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
  81. package/dist/dashboard/assets/entry-CcWkGR4a.js +1 -0
  82. package/dist/dashboard/assets/entry-CcWkGR4a.js.gz +0 -0
  83. package/dist/dashboard/assets/entry-kTiTFrvh.js +1 -0
  84. package/dist/dashboard/assets/entry-kTiTFrvh.js.gz +0 -0
  85. package/dist/dashboard/assets/eye-C_hawfH4.js.gz +0 -0
  86. package/dist/dashboard/assets/eye-off-Bk5g2luF.js.gz +0 -0
  87. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  88. package/dist/dashboard/assets/gauge-BbIMy7MB.js.gz +0 -0
  89. package/dist/dashboard/assets/github-badge-Booc5mNe.js +1 -0
  90. package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
  91. package/dist/dashboard/assets/image-BPaouViG.js.gz +0 -0
  92. package/dist/dashboard/assets/{index-C4hXfAMs.js → index-D70pChfn.js} +2 -2
  93. package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
  94. package/dist/dashboard/assets/{inline-DCejSLlA.js → inline-D1TCqM2D.js} +1 -1
  95. package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
  96. package/dist/dashboard/assets/{input-Bn4_kNav.js → input-B9oyU93y.js} +1 -1
  97. package/dist/dashboard/assets/input-B9oyU93y.js.gz +0 -0
  98. package/dist/dashboard/assets/jsx-runtime-CXUMngI_.js.gz +0 -0
  99. package/dist/dashboard/assets/key-round-UkavsGxp.js.gz +0 -0
  100. package/dist/dashboard/assets/layers-2-glBbjl.js.gz +0 -0
  101. package/dist/dashboard/assets/{layout-Bv-NxqZ5.js → layout-BArzXirQ.js} +1 -1
  102. package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
  103. package/dist/dashboard/assets/{list-row-BLA0sldD.js → list-row-BVZzaoHF.js} +1 -1
  104. package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
  105. package/dist/dashboard/assets/{live-YlifPYox.js → live-EmuIayqp.js} +1 -1
  106. package/dist/dashboard/assets/live-EmuIayqp.js.gz +0 -0
  107. package/dist/dashboard/assets/log-in-BmRjy60c.js.gz +0 -0
  108. package/dist/dashboard/assets/message-circle-CCxGwRvv.js.gz +0 -0
  109. package/dist/dashboard/assets/{model-bans-CVvHZ-qz.js → model-bans-CSPQ9xB0.js} +1 -1
  110. package/dist/dashboard/assets/model-bans-CSPQ9xB0.js.gz +0 -0
  111. package/dist/dashboard/assets/mutation-BkoLM5Ba.js +1 -0
  112. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  113. package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
  114. package/dist/dashboard/assets/pencil-768ysRAr.js.gz +0 -0
  115. package/dist/dashboard/assets/plus-DIdX_g-x.js.gz +0 -0
  116. package/dist/dashboard/assets/power-off-Yjw9GMJh.js +1 -0
  117. package/dist/dashboard/assets/power-off-Yjw9GMJh.js.gz +0 -0
  118. package/dist/dashboard/assets/privacy-mode-GM8bej85.js +1 -0
  119. package/dist/dashboard/assets/privacy-mode-GM8bej85.js.gz +0 -0
  120. package/dist/dashboard/assets/providers/agentrouter.svg.gz +0 -0
  121. package/dist/dashboard/assets/providers/aihubmix.svg.gz +0 -0
  122. package/dist/dashboard/assets/providers/alibaba-coding-plan.svg.gz +0 -0
  123. package/dist/dashboard/assets/providers/alibaba.svg.gz +0 -0
  124. package/dist/dashboard/assets/providers/antigravity.svg.gz +0 -0
  125. package/dist/dashboard/assets/providers/bai.svg.gz +0 -0
  126. package/dist/dashboard/assets/providers/blackbox.svg.gz +0 -0
  127. package/dist/dashboard/assets/providers/claude-code.svg.gz +0 -0
  128. package/dist/dashboard/assets/providers/exa.svg.gz +0 -0
  129. package/dist/dashboard/assets/providers/gmi-cloud.svg.gz +0 -0
  130. package/dist/dashboard/assets/providers/inferhub.svg.gz +0 -0
  131. package/dist/dashboard/assets/providers/muse.svg.gz +0 -0
  132. package/dist/dashboard/assets/providers/nous-research.svg.gz +0 -0
  133. package/dist/dashboard/assets/providers/openai-light.svg.gz +0 -0
  134. package/dist/dashboard/assets/providers/parallel.svg.gz +0 -0
  135. package/dist/dashboard/assets/providers/tokenharbor.svg.gz +0 -0
  136. package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
  137. package/dist/dashboard/assets/{query-keys-ROf-g1Wi.js → query-keys-D7wX2vM5.js} +1 -1
  138. package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
  139. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  140. package/dist/dashboard/assets/refresh-cw-BqJFk4sU.js.gz +0 -0
  141. package/dist/dashboard/assets/rotate-ccw-CufcqITi.js.gz +0 -0
  142. package/dist/dashboard/assets/route-CNSNj9Zm.js.gz +0 -0
  143. package/dist/dashboard/assets/{routing-jntw9-Ui.js → routing-BExgxxBU.js} +1 -1
  144. package/dist/dashboard/assets/routing-BExgxxBU.js.gz +0 -0
  145. package/dist/dashboard/assets/scaling-nWE4Cx8f.js.gz +0 -0
  146. package/dist/dashboard/assets/search-JOKus9rb.js.gz +0 -0
  147. package/dist/dashboard/assets/{select-CGUuTQmj.js → select-C9mMoEpJ.js} +1 -1
  148. package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
  149. package/dist/dashboard/assets/settings-C4_Gn_wq.js +1 -0
  150. package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
  151. package/dist/dashboard/assets/shield-check-DVS0Foqh.js.gz +0 -0
  152. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
  153. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  154. package/dist/dashboard/assets/{stack-C1kTcOmP.js → stack-Ca5HxI5i.js} +1 -1
  155. package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
  156. package/dist/dashboard/assets/{state-DHKdg-pm.js → state-CzUPfLXo.js} +1 -1
  157. package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
  158. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  159. package/dist/dashboard/assets/{switch-DIDeAiz9.js → switch-DOUIky_i.js} +1 -1
  160. package/dist/dashboard/assets/switch-DOUIky_i.js.gz +0 -0
  161. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +1 -0
  162. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  163. package/dist/dashboard/assets/toast-C76WrMhc.js +2 -0
  164. package/dist/dashboard/assets/toast-C76WrMhc.js.gz +0 -0
  165. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  166. package/dist/dashboard/assets/trash-Cppn5c3R.js.gz +0 -0
  167. package/dist/dashboard/assets/use-clipboard-B0Sy03fE.js.gz +0 -0
  168. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  169. package/dist/dashboard/assets/use-reduced-motion-C4B0R6Sz.js.gz +0 -0
  170. package/dist/dashboard/assets/use-timeout-BwcBBRrT.js.gz +0 -0
  171. package/dist/dashboard/assets/{useMutation-DVmwOKU-.js → useMutation-D87FZwH0.js} +1 -1
  172. package/dist/dashboard/assets/useMutation-D87FZwH0.js.gz +0 -0
  173. package/dist/dashboard/assets/wrench-B6E1oEuY.js.gz +0 -0
  174. package/dist/dashboard/index.html +1 -1
  175. package/dist/dashboard/index.html.gz +0 -0
  176. package/dist/dashboard/precompressed-assets.json +123 -0
  177. package/migrations/full-postgres/0013_add_providers_tenant_id_index.sql +22 -0
  178. package/migrations/full-postgres/0014_add_provider_accounts_tenant_id_index.sql +21 -0
  179. package/migrations/lite-sqlite3/0000_baseline.sql +4 -2
  180. package/migrations/lite-sqlite3/0013_add_providers_tenant_id_index.sql +4 -0
  181. package/migrations/lite-sqlite3/0014_add_provider_accounts_tenant_id_index.sql +4 -0
  182. package/package.json +25 -30
  183. package/scripts/cli/{carte-launcher.cjs → bin/carte.cjs} +8 -8
  184. package/scripts/cli/{cli-launcher.cjs → bin/cartethyia.cjs} +167 -134
  185. package/scripts/cli/command/admin.ts +65 -0
  186. package/scripts/cli/command/console-menu.ts +227 -0
  187. package/scripts/cli/{doctor.ts → command/doctor.ts} +257 -258
  188. package/scripts/cli/{setup.ts → command/setup.ts} +8 -12
  189. package/scripts/cli/command/start.ts +13 -0
  190. package/scripts/cli/command/tray.ts +165 -0
  191. package/scripts/cli/command/update.ts +23 -0
  192. package/scripts/cli/{runtime-state.ts → gateway/drain-token.ts} +78 -78
  193. package/scripts/cli/gateway/spawn.ts +101 -0
  194. package/scripts/cli/gateway/stop.ts +51 -0
  195. package/scripts/cli/main.ts +94 -0
  196. package/scripts/cli/support/bun.ts +27 -0
  197. package/scripts/cli/support/env.ts +99 -0
  198. package/scripts/cli/support/platform.ts +64 -0
  199. package/scripts/cli/support/services.ts +88 -0
  200. package/scripts/cli/support/startup-signal.ts +23 -0
  201. package/scripts/cli/{version.ts → support/version.ts} +1 -1
  202. package/scripts/cli/tray/hidden-window.vbs +28 -0
  203. package/scripts/cli/{tray.ts → tray/index.ts} +3 -3
  204. package/scripts/cli/{tray.ps1 → tray/windows.ps1} +107 -98
  205. package/scripts/cli/ui/background.ts +49 -0
  206. package/src/boot/app.ts +3 -1
  207. package/src/boot/dependencies.ts +36 -13
  208. package/src/config/providers.ts +27 -1
  209. package/src/config/spec.ts +11 -0
  210. package/src/console/backup/routes.ts +8 -0
  211. package/src/console/cli-tools/contracts.ts +6 -6
  212. package/src/console/console-router.ts +5 -3
  213. package/src/console/dashboard-assets.ts +81 -11
  214. package/src/console/observability/contracts.ts +54 -22
  215. package/src/console/observability/routes.ts +31 -6
  216. package/src/console/observability/store.ts +68 -28
  217. package/src/console/providers/catalog/public-model-store.ts +91 -60
  218. package/src/console/providers/catalog/store.ts +5 -1
  219. package/src/console/quota/account-quota-tenant.ts +9 -6
  220. package/src/console/response-compression.ts +69 -2
  221. package/src/console/routing/pools/probe-verdict.ts +72 -0
  222. package/src/console/share/share-router.ts +7 -4
  223. package/src/console/share/share-stats.ts +12 -5
  224. package/src/console/shared/read-through-cache.ts +133 -7
  225. package/src/console/updates/repository-updates.ts +39 -34
  226. package/src/network/outbound-fetch.ts +113 -25
  227. package/src/network/tls/bun-client-hello.ts +112 -0
  228. package/src/observability/event-log.ts +29 -8
  229. package/src/observability/metrics.ts +40 -13
  230. package/src/observability/runtime-metrics.ts +7 -0
  231. package/src/persistence/schema.ts +40 -2
  232. package/src/protocol/response/codex.ts +9 -75
  233. package/src/protocol/tool-identity.ts +43 -9
  234. package/src/providers/authentication/jwt-validator.ts +130 -187
  235. package/src/providers/authentication/oauth-flow-store.ts +16 -9
  236. package/src/providers/default-registry.ts +4 -0
  237. package/src/providers/discovery/catalog/catalog-format.ts +20 -0
  238. package/src/providers/discovery/models-dev-catalog.ts +39 -22
  239. package/src/providers/discovery/probe-wire.ts +29 -5
  240. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +3 -2
  241. package/src/providers/integrations/buddy/buddy-cli-headers.ts +88 -0
  242. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +4 -0
  243. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +33 -1
  244. package/src/providers/integrations/buddy/buddy-identity.ts +46 -0
  245. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +37 -1
  246. package/src/providers/integrations/buddy/buddy-usage-store.ts +40 -5
  247. package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -2
  248. package/src/providers/integrations/buddy/codebuddy/shared.ts +106 -73
  249. package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +5 -2
  250. package/src/providers/integrations/buddy/workbuddy/quota.ts +172 -159
  251. package/src/providers/integrations/buddy/workbuddy/shared.ts +200 -171
  252. package/src/providers/integrations/codex/codex.ts +5 -8
  253. package/src/providers/integrations/factory/factory-oauth.ts +7 -11
  254. package/src/providers/integrations/opencode/opencode-go-quota.ts +130 -0
  255. package/src/providers/integrations/opencode/provider.ts +42 -3
  256. package/src/providers/integrations/qoder/qoder-oauth.ts +3 -9
  257. package/src/providers/integrations/shared/install-id.ts +29 -2
  258. package/src/providers/operations/client-versions.ts +11 -1
  259. package/src/providers/operations/model-catalog-cache.ts +18 -3
  260. package/src/providers/operations/provider-catalog-service.ts +76 -0
  261. package/src/transport/dispatch/attempt-finalize.ts +7 -1
  262. package/src/transport/dispatch/attempt-loop.ts +9 -7
  263. package/src/transport/dispatch/empty-stream.ts +17 -26
  264. package/src/transport/failure-policy.ts +20 -0
  265. package/src/transport/routing/route-catalog.ts +42 -3
  266. package/src/transport/surface/chat/encode.ts +18 -31
  267. package/src/transport/surface/messages/stream.ts +3 -2
  268. package/src/transport/translation/tool-repair.ts +37 -16
  269. package/src/workers/account-identity-sweep.ts +14 -12
  270. package/src/workers/daily-checkin.ts +62 -0
  271. package/src/workers/quota-refresh-sweep.ts +27 -2
  272. package/dist/dashboard/assets/CombosPage-B7ETkfVH.js +0 -1
  273. package/dist/dashboard/assets/ConsoleLogPage-AisQzCI1.js +0 -1
  274. package/dist/dashboard/assets/HealthEventsModal-BNflDm_f.js +0 -1
  275. package/dist/dashboard/assets/ModelPicker-CAiPR5Vd.js +0 -1
  276. package/dist/dashboard/assets/ProviderDetailPage-BK8i5B5X.js +0 -3
  277. package/dist/dashboard/assets/ProvidersPage-CvCVgm1e.js +0 -1
  278. package/dist/dashboard/assets/ProxyPage-DjabkuzP.js +0 -7
  279. package/dist/dashboard/assets/QuotaPage-Dxe0B0iu.js +0 -1
  280. package/dist/dashboard/assets/SetupPage-BaD4P_jW.js +0 -1
  281. package/dist/dashboard/assets/SortableList-Bol1ASmI.js +0 -1
  282. package/dist/dashboard/assets/StudioPage-CLP4le3U.js +0 -31
  283. package/dist/dashboard/assets/api-keys-CjIGQhe7.js +0 -1
  284. package/dist/dashboard/assets/app-Cjgo4-Lf.css +0 -2
  285. package/dist/dashboard/assets/cli-tools-DccaOfhX.js +0 -1
  286. package/dist/dashboard/assets/clipboard-button-DQmMikNQ.js +0 -1
  287. package/dist/dashboard/assets/entry-Cpi0rph1.js +0 -4
  288. package/dist/dashboard/assets/entry-Dm3PpDCO.js +0 -1
  289. package/dist/dashboard/assets/entry-keNDZ_Wz.js +0 -1
  290. package/dist/dashboard/assets/github-badge-BuAJr8oU.js +0 -1
  291. package/dist/dashboard/assets/mutation-BIizFZav.js +0 -1
  292. package/dist/dashboard/assets/privacy-mode-CQjbZ3xk.js +0 -1
  293. package/dist/dashboard/assets/settings-BAS6QbmJ.js +0 -1
  294. package/dist/dashboard/assets/terminal-C9mZ4Ugm.js +0 -1
  295. package/dist/dashboard/assets/toast-DKAdP2mo.js +0 -2
  296. package/scripts/cli/cli.ts +0 -771
  297. package/scripts/operations/probe-egress.ts +0 -335
  298. package/scripts/operations/restart.ts +0 -120
  299. package/scripts/shared/env.ts +0 -269
  300. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.ico +0 -0
  301. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.png +0 -0
  302. /package/scripts/cli/{menu.ts → ui/prompt.ts} +0 -0
  303. /package/scripts/cli/{wordmark.ts → ui/wordmark.ts} +0 -0
@@ -73,8 +73,9 @@ const BUDDY_IMAGE_MODEL_IDS: readonly { id: string; endpoint: string; ctx: numbe
73
73
  * `seedance-2.5` is the text-to-video model the buddy facade exposes. It is a
74
74
  * distinct service kind (`videoGeneration`), not a chat or image row, so it is
75
75
  * built by {@link makeBuddyVideoModels} and reached through
76
- * `/v1/videos/generations` rather than a chat wire. CodeBuddy CN is
77
- * deliberately absent — it publishes a different roster.
76
+ * `/videos/generations` (relative to each variant's own base, which is why the
77
+ * WorkBuddy rows carry the `/v2` segment themselves) rather than a chat wire.
78
+ * CodeBuddy CN is deliberately absent — it publishes a different roster.
78
79
  */
79
80
  const BUDDY_VIDEO_MODEL_IDS: readonly { id: string; endpoint: string; ctx: number; out: number }[] = [
80
81
  { id: "seedance-2.5", endpoint: "/videos/generations", ctx: 1_000_000, out: 128_000 },
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The header family only the CLI identity sends.
3
+ *
4
+ * Everything here comes from the two reference gateways, which agree: the
5
+ * desktop product sends conversation/tracing headers and browser transport,
6
+ * while the CLI sends the agent envelope plus the OpenAI Node SDK fingerprint
7
+ * and no browser headers at all. A request carrying both halves is a hybrid no
8
+ * real client produces, which is what risk control keys on.
9
+ *
10
+ * Two references, two fingerprints: the hub derives OS/arch and a Node version
11
+ * from its own host, and codebuddy2api pins a fixed Linux/x64/v24.21.0 set.
12
+ * Neither can be right for every deployment, so the values here follow the
13
+ * host this gateway actually runs on — they are a fingerprint of the process,
14
+ * not a claim about a remote machine.
15
+ */
16
+
17
+ /** OpenAI Node SDK 6.25.0 `x-stainless-*` fingerprint; both references agree. */
18
+ const STAINLESS_PACKAGE_VERSION = "6.25.0";
19
+
20
+ function hostOs(): string {
21
+ return process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "MacOS" : "Linux";
22
+ }
23
+
24
+ function hostArch(): string {
25
+ return process.arch === "arm64" || process.arch === "ia32" ? process.arch : "x64";
26
+ }
27
+
28
+ /**
29
+ * The SDK fingerprint headers.
30
+ *
31
+ * `x-stainless-timeout` is in the hub only and `x-stainless-retry-count` is in
32
+ * both; the SDK sends both, so both go out.
33
+ */
34
+ export function buddyStainlessHeaders(): Record<string, string> {
35
+ return {
36
+ "x-stainless-lang": "js",
37
+ "x-stainless-package-version": STAINLESS_PACKAGE_VERSION,
38
+ "x-stainless-os": hostOs(),
39
+ "x-stainless-arch": hostArch(),
40
+ "x-stainless-runtime": "node",
41
+ "x-stainless-runtime-version": process.version,
42
+ "x-stainless-retry-count": "0",
43
+ "x-stainless-timeout": "1200",
44
+ };
45
+ }
46
+
47
+ /**
48
+ * The trace family the CLI carries as a child span.
49
+ *
50
+ * The desktop product omits the parent (`with_parent=False` in the reference);
51
+ * the CLI conversation request is a child span, so it also sends `b3`'s fourth
52
+ * field and `X-B3-ParentSpanId`. `buddyConversationHeaders` already produces
53
+ * `traceparent`, `b3`, and the `X-B3-*` set, so only the parent is added here.
54
+ */
55
+ export function buddyCliTraceHeaders(
56
+ conversation: Readonly<Record<string, string>>,
57
+ ): Record<string, string> {
58
+ const traceId = conversation["x-trace-id"] ?? conversation["x-b3-traceid"];
59
+ const spanId = conversation["x-b3-spanid"];
60
+ if (traceId === undefined) return {};
61
+ const b3 = `${traceId}-${spanId ?? traceId.slice(0, 16)}-1-${traceId.slice(0, 16)}`;
62
+ return { ...(spanId === undefined ? {} : { "X-B3-ParentSpanId": spanId }), b3 };
63
+ }
64
+
65
+ /**
66
+ * The full CLI identity envelope, minus what each caller already supplies.
67
+ *
68
+ * The caller keeps ownership of `User-Agent`, `X-IDE-*`, `X-Domain`, and the
69
+ * conversation family, because those vary by provider and realm: only the
70
+ * headers that are the same for every CLI dispatch are built here.
71
+ */
72
+ export function buddyCliHeaders(args: { readonly userId: string }): Record<string, string> {
73
+ return {
74
+ "X-Agent-Intent": "craft",
75
+ "X-Agent-Type": "main",
76
+ // The reference enum is local-agent / cloud-agent / agent-agent, and its
77
+ // parser returns "other" for anything unknown or absent. A gateway is none
78
+ // of the three, so "other" is the honest value rather than a guess.
79
+ "X-Agent-Runtime": "other",
80
+ "X-User-Id": args.userId,
81
+ // Personal accounts are not an enterprise and the reference sends the flag
82
+ // either way (`true` only for an enterprise id), so the non-enterprise
83
+ // default goes out here. Step 3 replaces this with the account's real
84
+ // enterprise id once discovery carries one.
85
+ "X-Private-Data": "false",
86
+ ...buddyStainlessHeaders(),
87
+ };
88
+ }
@@ -81,6 +81,10 @@ export function buddyConversationHeaders(
81
81
  "x-request-id": messageId,
82
82
  "x-root-request-id": conversationRequestId,
83
83
  "x-trace-id": traceId,
84
+ // W3C trace context alongside the B3 set below: the upstream reads both,
85
+ // and every reference request carries `traceparent`, so sending only the
86
+ // B3 half leaves a header the channel check expects absent.
87
+ traceparent: `00-${b3TraceId}-${messageId.slice(0, 16)}-01`,
84
88
  "x-b3-traceid": b3TraceId,
85
89
  "x-b3-spanid": messageId.slice(0, 16),
86
90
  "x-b3-sampled": "1",
@@ -37,6 +37,18 @@ export const BUDDY_CN_MODELS_PATH = "/console/enterprises/personal/models" as co
37
37
  */
38
38
  export const BUDDY_INTL_MODELS_PATH = "/v2/enterprises/personal/models" as const;
39
39
 
40
+ /**
41
+ * Remote config path, answered on every realm's chat host.
42
+ *
43
+ * Both reference gateways treat this as the primary model catalog and the
44
+ * personal-models paths above as the narrower fallback: `/v3/config` carries
45
+ * the same `{ code, msg, data: { models, agents } }` envelope plus the
46
+ * per-product rosters the personal path does not always publish. It is tried
47
+ * first, and a failure falls through to the path the caller already passes, so
48
+ * a host that does not answer it behaves exactly as it does today.
49
+ */
50
+ export const BUDDY_CONFIG_PATH = "/v3/config" as const;
51
+
40
52
  /** Envelope code the console directory answers on success. */
41
53
  const BUDDY_OK_CODE = 0;
42
54
 
@@ -92,13 +104,33 @@ export async function fetchBuddyDirectoryModels(args: {
92
104
  readonly fetcher?: typeof fetch | undefined;
93
105
  }): Promise<readonly ModelDefinition[] | null> {
94
106
  const fetcher = args.fetcher ?? globalThis.fetch;
107
+ const primary = await fetchBuddyDirectoryOnce(args, fetcher, BUDDY_CONFIG_PATH);
108
+ if (primary !== null) return primary;
109
+ // Both references rank `/v3/config` above the personal-models path, but this
110
+ // repo has only ever called the latter, so the fallback keeps today's
111
+ // behaviour intact for a host that answers one and not the other.
112
+ return await fetchBuddyDirectoryOnce(args, fetcher, args.modelsPath);
113
+ }
114
+
115
+ /** Reads one directory path; `null` on any transport, status, or shape miss. */
116
+ async function fetchBuddyDirectoryOnce(
117
+ args: {
118
+ readonly siteUrl: string;
119
+ readonly providerId: string;
120
+ readonly credential: string;
121
+ readonly endpoint: string | undefined;
122
+ readonly signal?: AbortSignal | undefined;
123
+ },
124
+ fetcher: typeof fetch,
125
+ path: string,
126
+ ): Promise<readonly ModelDefinition[] | null> {
95
127
  // The directory scores by account: an access token's `sub` is the uid. An
96
128
  // opaque API key carries no identity, and the endpoint still answers for it,
97
129
  // so the header is declared absent rather than guessed.
98
130
  const uid = buddyAccountUid(args.credential);
99
131
  let response: Response;
100
132
  try {
101
- response = await fetcher(`${siteOrigin(args.siteUrl)}${args.modelsPath}`, {
133
+ response = await fetcher(`${siteOrigin(args.siteUrl)}${path}`, {
102
134
  method: "GET",
103
135
  headers: {
104
136
  accept: "application/json",
@@ -0,0 +1,46 @@
1
+ import type { ProviderDispatchContext } from "../../provider-contracts";
2
+
3
+ /**
4
+ * Which upstream client a buddy provider presents itself as.
5
+ *
6
+ * `desktop` is what every buddy provider sends today and stays the default for
7
+ * all four. `cli` is the identity the reference gateways use for CLI traffic: a
8
+ * different header set, a different User-Agent formula, and a different CN
9
+ * upstream.
10
+ *
11
+ * Nothing flips to `cli` implicitly. Being wrong here is what gets an account
12
+ * blocked upstream, so a provider changes identity only when an operator or an
13
+ * account says so.
14
+ */
15
+ export type BuddyIdentity = "desktop" | "cli";
16
+
17
+ /**
18
+ * Resolves the identity one buddy dispatch presents.
19
+ *
20
+ * Precedence is account then provider: an account authorized as a CLI keeps
21
+ * that identity even when the provider default changes, because the upstream
22
+ * recorded it at login. The provider-level flag is
23
+ * `compatibilityProfile.cli_identity`, already declared, validated, and
24
+ * defaulted — it just never reached these adapters before.
25
+ */
26
+ export function resolveBuddyIdentity(
27
+ context: ProviderDispatchContext | undefined,
28
+ profileIdentity?: boolean | undefined,
29
+ ): BuddyIdentity {
30
+ const accountRaw = context?.credential.auth_state?.["cliIdentity"];
31
+ if (accountRaw === true) return "cli";
32
+ if (accountRaw === false) return "desktop";
33
+ return profileIdentity === true ? "cli" : "desktop";
34
+ }
35
+
36
+ /**
37
+ * The upstream's `X-User-Id` for a buddy dispatch.
38
+ *
39
+ * The reference CLI sends the literal `anonymous` when it has no uid rather
40
+ * than omitting the header, so an unauthenticated dispatch still looks like a
41
+ * client rather than like a caller that forgot a header.
42
+ */
43
+ export function buddyUserId(context?: ProviderDispatchContext): string {
44
+ const accountId = context?.credential.account_id;
45
+ return accountId !== undefined && accountId.length > 0 ? accountId : "anonymous";
46
+ }
@@ -280,7 +280,43 @@ export class BuddyOAuthClient extends OAuthDeviceFlow {
280
280
  reason: `${this.#variant.providerId} token response omitted accessToken`,
281
281
  };
282
282
  }
283
- return { status: "complete", result: tokenResult(this.#variant, payload) };
283
+ // The profile nickname is a second, authorized request the reference makes
284
+ // once the token lands — the JWT carries the email but not always a
285
+ // display name. Best-effort by design: a login must not fail because the
286
+ // profile endpoint was unreachable, so every miss falls back to the label
287
+ // the token itself yields.
288
+ const nickname = await this.fetchAccountNickname(deviceAuthId, access);
289
+ if (nickname === undefined) return { status: "complete", result: tokenResult(this.#variant, payload) };
290
+ const result = tokenResult(this.#variant, payload);
291
+ return {
292
+ status: "complete",
293
+ result: { ...result, accountLabel: result.accountLabel ?? nickname },
294
+ };
295
+ }
296
+
297
+ /** Reads the account profile nickname, or `undefined` on any miss. */
298
+ private async fetchAccountNickname(
299
+ deviceAuthId: string,
300
+ accessToken: string,
301
+ ): Promise<string | undefined> {
302
+ try {
303
+ // Both references call `/v2/plugin/login/account` with the token after
304
+ // the poll succeeds; the poll URL is the same versioned plugin root, so
305
+ // the profile path is derived from it rather than declared per variant.
306
+ const url = `${this.#variant.devicePollUrl.replace("/plugin/auth/token", "/plugin/login/account")}?state=${encodeURIComponent(deviceAuthId)}`;
307
+ const response = await this.fetchFn(url, {
308
+ method: "GET",
309
+ headers: {
310
+ ...(await devicePollHeaders(this.#variant)),
311
+ authorization: `Bearer ${accessToken}`,
312
+ },
313
+ });
314
+ if (!response.ok) return undefined;
315
+ const profile = buddyResponseData((await response.json()) as unknown);
316
+ return nonEmptyTrimmedString(profile?.nickname);
317
+ } catch {
318
+ return undefined;
319
+ }
284
320
  }
285
321
 
286
322
  override async refresh(refreshToken: string, signal?: AbortSignal): Promise<OAuthTokenRefreshResult> {
@@ -10,7 +10,7 @@
10
10
  * Deliberately not a request-path store: this is written by a maintenance pass
11
11
  * and read by the console. Nothing here can fail a dispatch.
12
12
  */
13
- import { and, desc, eq, inArray, sql } from "drizzle-orm";
13
+ import { and, desc, eq, gte, inArray, sql } from "drizzle-orm";
14
14
  import type { CartethyiaDatabase } from "../../../persistence/db-handle";
15
15
  import { buddyUsageSnapshots } from "../../../persistence/schema";
16
16
  import type { BuddyUsageRow } from "./buddy-usage-shared";
@@ -23,6 +23,11 @@ export interface BuddyUsageSnapshotRow extends BuddyUsageRow {
23
23
  /** Days of usage retained. The upstream's own window is 30. */
24
24
  const RETENTION_DAYS = 30;
25
25
 
26
+ /** Oldest day a stored bucket can hold, as the `YYYY-MM-DD` key the table uses. */
27
+ function retentionCutoff(now: Date = new Date()): string {
28
+ return new Date(now.getTime() - RETENTION_DAYS * 86_400_000).toISOString().slice(0, 10);
29
+ }
30
+
26
31
  /**
27
32
  * Upserts one account's usage buckets.
28
33
  *
@@ -72,7 +77,31 @@ export async function saveBuddyUsageSnapshots(
72
77
  }
73
78
 
74
79
  /**
75
- * Reads one account's stored usage, newest day first.
80
+ * Most (day, model) buckets one day can hold, used only to bound the read.
81
+ *
82
+ * The renderer keeps {@link DEFAULT_LIMIT_PER_ACCOUNT} entries per account plus
83
+ * every bucket of the newest day, so this is a ceiling on a pathological table
84
+ * rather than a number the page depends on. Set well above any real model count
85
+ * so the cap can only ever trigger on a table that grew without pruning.
86
+ */
87
+ const MODELS_PER_DAY_CEILING = 64;
88
+
89
+ /** Buckets per account the console renders. */
90
+ const DEFAULT_LIMIT_PER_ACCOUNT = 30;
91
+
92
+ /**
93
+ * Reads each account's stored usage, newest day first.
94
+ *
95
+ * Bounded on both axes, because it used to be bounded on neither: with no day
96
+ * filter and no `LIMIT`, an account fetched every bucket it had ever stored
97
+ * (~1500 rows for a 30-day window) to render 30 of them, on the route the
98
+ * dashboard polls.
99
+ *
100
+ * - The day window is semantic, not a truncation: {@link pruneBuddyUsageSnapshots}
101
+ * already drops everything older, so no row it excludes could have been
102
+ * rendered anyway.
103
+ * - The row ceiling is the backstop. It is sized per account, so one large
104
+ * account cannot starve the others in a shared `LIMIT`.
76
105
  *
77
106
  * `numeric` comes back from the driver as a string, so the credit is parsed
78
107
  * rather than trusted; an unparseable row is reported as 0 instead of being
@@ -81,7 +110,7 @@ export async function saveBuddyUsageSnapshots(
81
110
  export async function readBuddyUsageSnapshots(
82
111
  db: CartethyiaDatabase,
83
112
  accountIds: readonly string[],
84
- limitPerAccount = 30,
113
+ limitPerAccount = DEFAULT_LIMIT_PER_ACCOUNT,
85
114
  ): Promise<Map<string, readonly BuddyUsageSnapshotRow[]>> {
86
115
  const result = new Map<string, BuddyUsageSnapshotRow[]>();
87
116
  if (accountIds.length === 0) return result;
@@ -102,8 +131,14 @@ export async function readBuddyUsageSnapshots(
102
131
  syncedAt: buddyUsageSnapshots.syncedAt,
103
132
  })
104
133
  .from(buddyUsageSnapshots)
105
- .where(inArray(buddyUsageSnapshots.accountId, [...accountIds]))
106
- .orderBy(desc(buddyUsageSnapshots.dayKey));
134
+ .where(
135
+ and(
136
+ inArray(buddyUsageSnapshots.accountId, [...accountIds]),
137
+ gte(buddyUsageSnapshots.dayKey, retentionCutoff()),
138
+ ),
139
+ )
140
+ .orderBy(desc(buddyUsageSnapshots.dayKey))
141
+ .limit(accountIds.length * limitPerAccount * MODELS_PER_DAY_CEILING);
107
142
  } catch {
108
143
  return result;
109
144
  }
@@ -109,8 +109,8 @@ export const CODEBUDDY_IMAGE_MODELS: readonly ModelDefinition[] = makeBuddyImage
109
109
  /**
110
110
  * CodeBuddy's video-generation rows (`seedance-2.5`).
111
111
  *
112
- * Reached through `/v1/videos/generations`, so the endpoint is the upstream
113
- * video path rather than a chat-completions path.
112
+ * Reached through `/videos/generations` on the `/v2` base, so the endpoint is
113
+ * the upstream video path rather than a chat-completions path.
114
114
  */
115
115
  export const CODEBUDDY_VIDEO_MODELS: readonly ModelDefinition[] = makeBuddyVideoModels(
116
116
  "cb",
@@ -1,73 +1,106 @@
1
- import { withBearerAuthentication, type OpenAICompatibleAdapterConfig } from "../../../adapters/compatible-adapter";
2
- import type { CanonicalRequest } from "../../../../protocol/canonical-model";
3
- import type { ProviderDispatchContext, ProviderDispatchTarget, ProviderId } from "../../../provider-contracts";
4
- import { resolveInboundSessionId } from "../../../operations/session-resolution";
5
- import { buddyConversationHeaders } from "../buddy-conversation-headers";
6
- import { buildCodeBuddyUserAgent } from "../../../operations/client-versions";
7
- import { readBuddyDeviceToken } from "../workbuddy/shared";
8
-
9
- /** Client identity variant behind the per-request CodeBuddy headers. */
10
- export type CodeBuddyVariant = "IDE" | "CLI";
11
-
12
- export function codebuddyDomain(variant: CodeBuddyVariant): string {
13
- return variant === "IDE" ? "www.codebuddy.ai" : "copilot.tencent.com";
14
- }
15
-
16
-
17
- /**
18
- * Per-request CodeBuddy headers — exact provider contract shared by the
19
- * international (IDE identity) and CN (CLI identity). The stable conversation
20
- * header is preserved from the client when available; request IDs rotate.
21
- */
22
- export async function codebuddyHeaders(
23
- variant: CodeBuddyVariant,
24
- context?: ProviderDispatchContext,
25
- request?: CanonicalRequest,
26
- ): Promise<Record<string, string>> {
27
- const identity = variant === "IDE" ? "IDE" : "CLI";
28
- const ua = buildCodeBuddyUserAgent(identity);
29
- const deviceToken = readBuddyDeviceToken(context);
30
- const conversationId = resolveInboundSessionId(context, request);
31
- return {
32
- accept: "text/event-stream",
33
- "User-Agent": ua,
34
- "X-Product": "SaaS",
35
- "X-IDE-Type": identity,
36
- "X-IDE-Name": identity,
37
- "X-Domain": codebuddyDomain(variant),
38
- "x-requested-with": "XMLHttpRequest",
39
- "x-codebuddy-request": "1",
40
- // Same family WorkBuddy sends: the upstream's channel check reads the
41
- // turn-level aggregation key, and a chat request without it is refused with
42
- // `400 · 11128 "conversation id missing"`.
43
- ...buddyConversationHeaders({
44
- ...(conversationId === undefined ? {} : { conversationId }),
45
- }),
46
- ...(deviceToken === undefined ? {} : { "X-Device-Token": deviceToken }),
47
- };
48
- }
49
-
50
- /**
51
- * Shared OpenAI-compatible chat-adapter config for both CodeBuddy variants:
52
- * bearer auth, chat-only wire contract, per-request identity headers, and the
53
- * variant's payload hook.
54
- */
55
- export function codebuddyAdapterConfig(args: {
56
- providerId: ProviderId;
57
- baseUrl: string;
58
- variant: CodeBuddyVariant;
59
- prePayload: (
60
- payload: Record<string, unknown>,
61
- request: CanonicalRequest,
62
- candidate: ProviderDispatchTarget,
63
- ) => void;
64
- fetchImpl?: typeof fetch;
65
- }): OpenAICompatibleAdapterConfig {
66
- return withBearerAuthentication({
67
- provider_id: args.providerId,
68
- base_url: args.baseUrl,
69
- buildExtraHeaders: (context, request) => codebuddyHeaders(args.variant, context, request),
70
- prePayload: args.prePayload,
71
- ...(args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}),
72
- });
73
- }
1
+ import { withBearerAuthentication, type OpenAICompatibleAdapterConfig } from "../../../adapters/compatible-adapter";
2
+ import type { CanonicalRequest } from "../../../../protocol/canonical-model";
3
+ import type { ProviderDispatchContext, ProviderDispatchTarget, ProviderId } from "../../../provider-contracts";
4
+ import { resolveInboundSessionId } from "../../../operations/session-resolution";
5
+ import { buddyConversationHeaders } from "../buddy-conversation-headers";
6
+ import { buddyUserId, resolveBuddyIdentity } from "../buddy-identity";
7
+ import { buildCodeBuddyUserAgent, getCodeBuddyCnVersion, getCodeBuddyVersion } from "../../../operations/client-versions";
8
+ import { readBuddyDeviceToken } from "../workbuddy/shared";
9
+ import { buddyCliHeaders, buddyCliTraceHeaders } from "../buddy-cli-headers";
10
+
11
+ /** Client identity variant behind the per-request CodeBuddy headers. */
12
+ export type CodeBuddyVariant = "IDE" | "CLI";
13
+
14
+ export function codebuddyDomain(variant: CodeBuddyVariant): string {
15
+ return variant === "IDE" ? "www.codebuddy.ai" : "copilot.tencent.com";
16
+ }
17
+
18
+
19
+ /**
20
+ * Per-request CodeBuddy headers — exact provider contract shared by the
21
+ * international (IDE identity) and CN (CLI identity). The stable conversation
22
+ * header is preserved from the client when available; request IDs rotate.
23
+ *
24
+ * `cliIdentity` selects the real CLI contract rather than today's hybrid: the
25
+ * reference CLI is issued by the OpenAI Node SDK, so it carries no browser
26
+ * headers and no device token, and it adds the agent envelope. Left unset it
27
+ * keeps the current behaviour for every provider.
28
+ */
29
+ export async function codebuddyHeaders(
30
+ variant: CodeBuddyVariant,
31
+ context?: ProviderDispatchContext,
32
+ request?: CanonicalRequest,
33
+ cliIdentity?: boolean,
34
+ ): Promise<Record<string, string>> {
35
+ const identity = variant === "IDE" ? "IDE" : "CLI";
36
+ const ua = buildCodeBuddyUserAgent(identity);
37
+ const deviceToken = readBuddyDeviceToken(context);
38
+ const conversationId = resolveInboundSessionId(context, request);
39
+ const conversation = buddyConversationHeaders({
40
+ ...(conversationId === undefined ? {} : { conversationId }),
41
+ });
42
+ if (resolveBuddyIdentity(context, cliIdentity) === "cli") {
43
+ return {
44
+ // The reference is explicit: the official CLI sends `Accept:
45
+ // application/json` and no browser header at all — no
46
+ // `X-Requested-With`, no `x-codebuddy-request`, no device token. A
47
+ // request carrying both halves is a hybrid no real client produces.
48
+ accept: "application/json",
49
+ "User-Agent": `CLI/${getCodeBuddyVersion()} CodeBuddy/${getCodeBuddyVersion()}`,
50
+ "X-Product": "SaaS",
51
+ "X-IDE-Type": "CLI",
52
+ "X-IDE-Name": "CLI",
53
+ "X-IDE-Version": getCodeBuddyVersion(),
54
+ "X-Domain": codebuddyDomain(variant),
55
+ ...conversation,
56
+ ...buddyCliTraceHeaders(conversation),
57
+ ...buddyCliHeaders({ userId: buddyUserId(context) }),
58
+ };
59
+ }
60
+ return {
61
+ accept: "text/event-stream",
62
+ "User-Agent": ua,
63
+ "X-Product": "SaaS",
64
+ "X-IDE-Type": identity,
65
+ "X-IDE-Name": identity,
66
+ // Every reference branch sends this, in both the desktop and the CLI
67
+ // product; its absence here is what made a CodeBuddy dispatch the only
68
+ // buddy caller with no client version on the wire.
69
+ "X-IDE-Version": identity === "CLI" ? getCodeBuddyCnVersion() : getCodeBuddyVersion(),
70
+ "X-Domain": codebuddyDomain(variant),
71
+ "x-requested-with": "XMLHttpRequest",
72
+ "x-codebuddy-request": "1",
73
+ // Same family WorkBuddy sends: the upstream's channel check reads the
74
+ // turn-level aggregation key, and a chat request without it is refused with
75
+ // `400 · 11-128 "conversation id missing"`.
76
+ ...conversation,
77
+ ...(deviceToken === undefined ? {} : { "X-Device-Token": deviceToken }),
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Shared OpenAI-compatible chat-adapter config for both CodeBuddy variants:
83
+ * bearer auth, chat-only wire contract, per-request identity headers, and the
84
+ * variant's payload hook.
85
+ */
86
+ export function codebuddyAdapterConfig(args: {
87
+ providerId: ProviderId;
88
+ baseUrl: string;
89
+ variant: CodeBuddyVariant;
90
+ prePayload: (
91
+ payload: Record<string, unknown>,
92
+ request: CanonicalRequest,
93
+ candidate: ProviderDispatchTarget,
94
+ ) => void;
95
+ fetchImpl?: typeof fetch;
96
+ cliIdentity?: boolean;
97
+ }): OpenAICompatibleAdapterConfig {
98
+ return withBearerAuthentication({
99
+ provider_id: args.providerId,
100
+ base_url: args.baseUrl,
101
+ buildExtraHeaders: (context, request) =>
102
+ codebuddyHeaders(args.variant, context, request, args.cliIdentity),
103
+ prePayload: args.prePayload,
104
+ ...(args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}),
105
+ });
106
+ }
@@ -5,7 +5,7 @@ import type { DiscoveryInput } from "../../../discovery/discovery-types";
5
5
  import type { ModelDefinition } from "../../../provider-model-definition";
6
6
  import { providerBaseUrl } from "../../../provider-metadata";
7
7
  import { getWorkBuddyCnClientVersion } from "../../../operations/client-versions";
8
- import { BUDDY_INTL_MODELS_PATH, fetchBuddyDirectoryModels } from "../buddy-discovery-shared";
8
+ import { BUDDY_CN_MODELS_PATH, fetchBuddyDirectoryModels } from "../buddy-discovery-shared";
9
9
  import { CODEBUDDY_CN_MODELS } from "../codebuddy/cn-provider";
10
10
  import { workbuddyPrePayload, WORKBUDDY_SYSTEM_PROMPT } from "./provider";
11
11
  import { workbuddyAdapterConfig, workbuddyHeaders, WORKBUDDY_CHAT_PATH } from "./shared";
@@ -28,7 +28,10 @@ export async function discoverWorkBuddyCnModels(
28
28
  siteUrl: WORKBUDDY_CN_BASE_URL,
29
29
  providerId: WORKBUDDY_CN_PROVIDER_ID,
30
30
  credential: input.credential,
31
- modelsPath: BUDDY_INTL_MODELS_PATH,
31
+ // The CN host answers the `/console/…` directory, exactly like the CodeBuddy
32
+ // CN host does; the international path was inherited from the intl provider
33
+ // and the CN edge answers it with a 500 rather than a 404.
34
+ modelsPath: BUDDY_CN_MODELS_PATH,
32
35
  endpoint: WORKBUDDY_CHAT_PATH,
33
36
  ...(input.signal ? { signal: input.signal } : {}),
34
37
  ...(input.fetcher ? { fetcher: input.fetcher } : {}),