cartethyia 2.4.5 → 2.5.0

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 (415) hide show
  1. package/.env.example +4 -1
  2. package/README.md +63 -13
  3. package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
  4. package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
  5. package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolDetailPage-BzGevuql.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
  7. package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
  8. package/dist/dashboard/assets/{CliToolsPage-BaRM_7We.js → CliToolsPage-D3xqZbN-.js} +1 -1
  9. package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
  10. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +1 -0
  11. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js +1 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
  14. package/dist/dashboard/assets/{CustomizationPage-DqUGcoH0.js → CustomizationPage-CTTpCb25.js} +1 -1
  15. package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
  16. package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js +1 -0
  17. package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-DbhTGfhb.js → LoginPage-DHoB3Agn.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
  20. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js +1 -0
  21. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
  22. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +1 -0
  23. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +3 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
  26. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
  27. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
  30. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +7 -0
  31. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-YxRml8AT.js → SettingsPage-CvjZxHE4.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
  36. package/dist/dashboard/assets/SetupPage-D3nza7LH.js +1 -0
  37. package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
  38. package/dist/dashboard/assets/{SortableList-Bol1ASmI.js → SortableList-Dtt12WT1.js} +1 -1
  39. package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
  40. package/dist/dashboard/assets/StudioPage-CbK3hXvU.js +31 -0
  41. package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
  42. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
  43. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +56 -0
  44. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
  45. package/dist/dashboard/assets/api-keys-BmDbsior.js +1 -0
  46. package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
  47. package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
  48. package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
  49. package/dist/dashboard/assets/{app-BDJWMThu.js → app-C-Y4oZT0.js} +3 -3
  50. package/dist/dashboard/assets/app-C-Y4oZT0.js.gz +0 -0
  51. package/dist/dashboard/assets/arrow-left-CpBxkoxI.js.gz +0 -0
  52. package/dist/dashboard/assets/arrow-right-Dl5yKnp8.js.gz +0 -0
  53. package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
  54. package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
  55. package/dist/dashboard/assets/backup-file-gdKIlg8U.js.gz +0 -0
  56. package/dist/dashboard/assets/{badge-BwmwEOgA.js → badge-DpXT4Ixt.js} +1 -1
  57. package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
  58. package/dist/dashboard/assets/{banner-BKBfKTqn.js → banner-CSAAWLJv.js} +1 -1
  59. package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
  60. package/dist/dashboard/assets/brain-Cwed_72z.js.gz +0 -0
  61. package/dist/dashboard/assets/{card-BTdbRFFg.js → card-DEhRXXw4.js} +1 -1
  62. package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
  63. package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
  64. package/dist/dashboard/assets/chevron-right-BXIy5l3h.js.gz +0 -0
  65. package/dist/dashboard/assets/circle-alert-CntYHV5v.js.gz +0 -0
  66. package/dist/dashboard/assets/circle-check-BU3v_mFW.js.gz +0 -0
  67. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js +1 -0
  68. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
  69. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js +1 -0
  70. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
  71. package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
  72. package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
  73. package/dist/dashboard/assets/{dialog-iocKA3jr.js → dialog-BvQeBMfP.js} +1 -1
  74. package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
  75. package/dist/dashboard/assets/dialog-actions-B1axpEg7.js.gz +0 -0
  76. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  77. package/dist/dashboard/assets/download-dRFo5_Wu.js.gz +0 -0
  78. package/dist/dashboard/assets/{drawer-CDKOTZdY.js → drawer-CMwPXwQB.js} +1 -1
  79. package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
  80. package/dist/dashboard/assets/entry-D4O2229X.js +1 -0
  81. package/dist/dashboard/assets/entry-D4O2229X.js.gz +0 -0
  82. package/dist/dashboard/assets/entry-DKvr5Q9K.js +1 -0
  83. package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
  84. package/dist/dashboard/assets/entry-FQCejoDY.js +4 -0
  85. package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
  86. package/dist/dashboard/assets/eye-C_hawfH4.js.gz +0 -0
  87. package/dist/dashboard/assets/eye-off-Bk5g2luF.js.gz +0 -0
  88. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  89. package/dist/dashboard/assets/gauge-BbIMy7MB.js.gz +0 -0
  90. package/dist/dashboard/assets/github-badge-zliLvBwr.js +1 -0
  91. package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
  92. package/dist/dashboard/assets/image-BPaouViG.js.gz +0 -0
  93. package/dist/dashboard/assets/{index-C4hXfAMs.js → index-CXm3FIyt.js} +2 -2
  94. package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
  95. package/dist/dashboard/assets/{inline-DCejSLlA.js → inline-D1IlZZ8H.js} +1 -1
  96. package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
  97. package/dist/dashboard/assets/{input-Bn4_kNav.js → input-BpVZdrbc.js} +1 -1
  98. package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
  99. package/dist/dashboard/assets/jsx-runtime-CXUMngI_.js.gz +0 -0
  100. package/dist/dashboard/assets/key-round-UkavsGxp.js.gz +0 -0
  101. package/dist/dashboard/assets/layers-2-glBbjl.js.gz +0 -0
  102. package/dist/dashboard/assets/{layout-Bv-NxqZ5.js → layout-BioLW7gN.js} +1 -1
  103. package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
  104. package/dist/dashboard/assets/{list-row-BLA0sldD.js → list-row-CwVcGAIc.js} +1 -1
  105. package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
  106. package/dist/dashboard/assets/{live-YlifPYox.js → live-yfEJFF9T.js} +1 -1
  107. package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
  108. package/dist/dashboard/assets/log-in-BmRjy60c.js.gz +0 -0
  109. package/dist/dashboard/assets/message-circle-CCxGwRvv.js.gz +0 -0
  110. package/dist/dashboard/assets/model-bans-CT-TxSNX.js +1 -0
  111. package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
  112. package/dist/dashboard/assets/mutation-BkoLM5Ba.js +1 -0
  113. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  114. package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
  115. package/dist/dashboard/assets/pencil-768ysRAr.js.gz +0 -0
  116. package/dist/dashboard/assets/plus-DIdX_g-x.js.gz +0 -0
  117. package/dist/dashboard/assets/power-off-Yjw9GMJh.js +1 -0
  118. package/dist/dashboard/assets/power-off-Yjw9GMJh.js.gz +0 -0
  119. package/dist/dashboard/assets/privacy-mode-GM8bej85.js +1 -0
  120. package/dist/dashboard/assets/privacy-mode-GM8bej85.js.gz +0 -0
  121. package/dist/dashboard/assets/providers/agentrouter.svg.gz +0 -0
  122. package/dist/dashboard/assets/providers/aihubmix.svg.gz +0 -0
  123. package/dist/dashboard/assets/providers/alibaba-coding-plan.svg.gz +0 -0
  124. package/dist/dashboard/assets/providers/alibaba.svg.gz +0 -0
  125. package/dist/dashboard/assets/providers/antigravity.svg.gz +0 -0
  126. package/dist/dashboard/assets/providers/bai.svg.gz +0 -0
  127. package/dist/dashboard/assets/providers/blackbox.svg.gz +0 -0
  128. package/dist/dashboard/assets/providers/claude-code.svg.gz +0 -0
  129. package/dist/dashboard/assets/providers/cursor.webp +0 -0
  130. package/dist/dashboard/assets/providers/exa.svg.gz +0 -0
  131. package/dist/dashboard/assets/providers/gmi-cloud.svg.gz +0 -0
  132. package/dist/dashboard/assets/providers/inferhub.svg.gz +0 -0
  133. package/dist/dashboard/assets/providers/muse.svg.gz +0 -0
  134. package/dist/dashboard/assets/providers/nous-research.svg.gz +0 -0
  135. package/dist/dashboard/assets/providers/openai-light.svg.gz +0 -0
  136. package/dist/dashboard/assets/providers/parallel.svg.gz +0 -0
  137. package/dist/dashboard/assets/providers/tokenharbor.svg.gz +0 -0
  138. package/dist/dashboard/assets/providers/trae.webp +0 -0
  139. package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
  140. package/dist/dashboard/assets/{query-keys-ROf-g1Wi.js → query-keys-D7wX2vM5.js} +1 -1
  141. package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
  142. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  143. package/dist/dashboard/assets/refresh-cw-BqJFk4sU.js.gz +0 -0
  144. package/dist/dashboard/assets/rotate-ccw-CufcqITi.js.gz +0 -0
  145. package/dist/dashboard/assets/route-CNSNj9Zm.js.gz +0 -0
  146. package/dist/dashboard/assets/routing-Cs6jl49L.js +1 -0
  147. package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
  148. package/dist/dashboard/assets/scaling-nWE4Cx8f.js.gz +0 -0
  149. package/dist/dashboard/assets/search-JOKus9rb.js.gz +0 -0
  150. package/dist/dashboard/assets/select-C0Rer3gF.js +41 -0
  151. package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
  152. package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
  153. package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
  154. package/dist/dashboard/assets/shield-check-DVS0Foqh.js.gz +0 -0
  155. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  156. package/dist/dashboard/assets/{stack-C1kTcOmP.js → stack-C9_uUjaA.js} +1 -1
  157. package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
  158. package/dist/dashboard/assets/{state-DHKdg-pm.js → state-DA0B95UQ.js} +1 -1
  159. package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
  160. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  161. package/dist/dashboard/assets/{switch-DIDeAiz9.js → switch-Cittxy-x.js} +1 -1
  162. package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
  163. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +1 -0
  164. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  165. package/dist/dashboard/assets/toast-CNt_jzO4.js +2 -0
  166. package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
  167. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  168. package/dist/dashboard/assets/trash-Cppn5c3R.js.gz +0 -0
  169. package/dist/dashboard/assets/use-clipboard-B0Sy03fE.js.gz +0 -0
  170. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  171. package/dist/dashboard/assets/use-reduced-motion-C4B0R6Sz.js.gz +0 -0
  172. package/dist/dashboard/assets/use-timeout-BwcBBRrT.js.gz +0 -0
  173. package/dist/dashboard/assets/{useMutation-DVmwOKU-.js → useMutation-BQKMcJ1H.js} +1 -1
  174. package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
  175. package/dist/dashboard/assets/wrench-B6E1oEuY.js.gz +0 -0
  176. package/dist/dashboard/index.html +1 -1
  177. package/dist/dashboard/index.html.gz +0 -0
  178. package/dist/dashboard/precompressed-assets.json +122 -0
  179. package/migrations/full-postgres/0013_add_providers_tenant_id_index.sql +22 -0
  180. package/migrations/full-postgres/0014_add_provider_accounts_tenant_id_index.sql +21 -0
  181. package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
  182. package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
  183. package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
  184. package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
  185. package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
  186. package/migrations/lite-sqlite3/0000_baseline.sql +4 -2
  187. package/migrations/lite-sqlite3/0013_add_providers_tenant_id_index.sql +4 -0
  188. package/migrations/lite-sqlite3/0014_add_provider_accounts_tenant_id_index.sql +4 -0
  189. package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
  190. package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
  191. package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
  192. package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
  193. package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
  194. package/package.json +26 -30
  195. package/scripts/cli/{carte-launcher.cjs → bin/carte.cjs} +8 -8
  196. package/scripts/cli/{cli-launcher.cjs → bin/cartethyia.cjs} +167 -134
  197. package/scripts/cli/command/admin.ts +65 -0
  198. package/scripts/cli/command/console-menu.ts +246 -0
  199. package/scripts/cli/{doctor.ts → command/doctor.ts} +257 -258
  200. package/scripts/cli/{setup.ts → command/setup.ts} +8 -12
  201. package/scripts/cli/command/start.ts +77 -0
  202. package/scripts/cli/command/tray.ts +165 -0
  203. package/scripts/cli/command/update.ts +23 -0
  204. package/scripts/cli/{runtime-state.ts → gateway/drain-token.ts} +78 -78
  205. package/scripts/cli/gateway/instance-lock.ts +105 -0
  206. package/scripts/cli/gateway/spawn.ts +173 -0
  207. package/scripts/cli/gateway/stop.ts +51 -0
  208. package/scripts/cli/main.ts +94 -0
  209. package/scripts/cli/support/bun.ts +27 -0
  210. package/scripts/cli/support/env.ts +99 -0
  211. package/scripts/cli/support/platform.ts +64 -0
  212. package/scripts/cli/support/services.ts +88 -0
  213. package/scripts/cli/support/startup-signal.ts +23 -0
  214. package/scripts/cli/{version.ts → support/version.ts} +1 -1
  215. package/scripts/cli/tray/hidden-window.vbs +28 -0
  216. package/scripts/cli/{tray.ts → tray/index.ts} +3 -3
  217. package/scripts/cli/{tray.ps1 → tray/windows.ps1} +107 -98
  218. package/scripts/cli/ui/background.ts +110 -0
  219. package/src/boot/app.ts +23 -7
  220. package/src/boot/dependencies.ts +55 -13
  221. package/src/boot/elysia-typebox-bridge.ts +37 -0
  222. package/src/boot/lifecycle.ts +2 -0
  223. package/src/config/cooldowns.ts +138 -138
  224. package/src/config/providers.ts +31 -5
  225. package/src/config/runtime.ts +19 -3
  226. package/src/config/spec.ts +20 -0
  227. package/src/console/api-keys/contracts.ts +414 -391
  228. package/src/console/api-keys/routes.ts +880 -870
  229. package/src/console/auth/account-reset-store.ts +14 -0
  230. package/src/console/auth/account-reset.ts +3 -2
  231. package/src/console/auth/setup-service.ts +6 -3
  232. package/src/console/backup/routes.ts +8 -0
  233. package/src/console/cli-tools/contracts.ts +6 -6
  234. package/src/console/console-router.ts +5 -3
  235. package/src/console/dashboard-assets.ts +81 -11
  236. package/src/console/domain-registration.ts +9 -2
  237. package/src/console/observability/contracts.ts +475 -423
  238. package/src/console/observability/routes.ts +31 -6
  239. package/src/console/observability/store.ts +1015 -912
  240. package/src/console/providers/catalog/contracts.ts +31 -0
  241. package/src/console/providers/catalog/provider-operations.ts +8 -0
  242. package/src/console/providers/catalog/public-model-store.ts +91 -60
  243. package/src/console/providers/catalog/store.ts +1532 -1521
  244. package/src/console/providers/oauth/callback-listener.ts +18 -3
  245. package/src/console/providers/oauth/routes.ts +38 -15
  246. package/src/console/quota/account-quota-tenant.ts +9 -6
  247. package/src/console/quota/refresh.ts +19 -0
  248. package/src/console/response-compression.ts +69 -2
  249. package/src/console/routing/pools/probe-verdict.ts +72 -0
  250. package/src/console/share/share-router.ts +7 -4
  251. package/src/console/share/share-stats.ts +12 -5
  252. package/src/console/shared/read-through-cache.ts +133 -7
  253. package/src/console/updates/repository-updates.ts +39 -34
  254. package/src/console/updates/routes.ts +162 -4
  255. package/src/main.ts +3 -0
  256. package/src/network/http2-bidi.ts +85 -0
  257. package/src/network/outbound-fetch.ts +87 -24
  258. package/src/network/pool/agent.ts +478 -460
  259. package/src/network/pool/resolver.ts +1104 -544
  260. package/src/network/pool-health-machine.ts +12 -5
  261. package/src/observability/event-log.ts +29 -8
  262. package/src/observability/metrics.ts +40 -13
  263. package/src/observability/payload-capture.ts +344 -324
  264. package/src/observability/runtime-metrics.ts +7 -0
  265. package/src/persistence/db-pg.ts +9 -3
  266. package/src/persistence/db-sqlite.ts +497 -495
  267. package/src/persistence/health-event-flush.ts +137 -0
  268. package/src/persistence/migrate.ts +181 -129
  269. package/src/persistence/schema.ts +1360 -1257
  270. package/src/persistence/settings-revision.ts +25 -0
  271. package/src/persistence/shared-cache.ts +217 -0
  272. package/src/persistence/stores/share-store.ts +556 -528
  273. package/src/persistence/usage-flush.ts +61 -1
  274. package/src/protocol/canonical-model.ts +23 -1
  275. package/src/protocol/response/chat.ts +143 -10
  276. package/src/protocol/response/codex.ts +9 -75
  277. package/src/protocol/response/omp-tool-call-markup.ts +221 -0
  278. package/src/protocol/tool-identity.ts +43 -9
  279. package/src/providers/authentication/jwt-validator.ts +130 -187
  280. package/src/providers/authentication/oauth-flow-store.ts +16 -9
  281. package/src/providers/default-registry.ts +177 -1
  282. package/src/providers/discovery/catalog/catalog-format.ts +20 -0
  283. package/src/providers/discovery/models-dev-catalog.ts +39 -22
  284. package/src/providers/discovery/openai-model-discovery.ts +39 -3
  285. package/src/providers/discovery/openrouter-free-roster.ts +98 -0
  286. package/src/providers/discovery/probe-wire.ts +29 -5
  287. package/src/providers/discovery/probing-service.ts +1022 -1001
  288. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +42 -2
  289. package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
  290. package/src/providers/integrations/buddy/buddy-cli-headers.ts +88 -0
  291. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +22 -5
  292. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +33 -1
  293. package/src/providers/integrations/buddy/buddy-identity.ts +46 -0
  294. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +37 -1
  295. package/src/providers/integrations/buddy/buddy-usage-store.ts +40 -5
  296. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
  297. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
  298. package/src/providers/integrations/buddy/codebuddy/shared.ts +115 -73
  299. package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +5 -2
  300. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
  301. package/src/providers/integrations/buddy/workbuddy/quota.ts +172 -159
  302. package/src/providers/integrations/buddy/workbuddy/shared.ts +200 -171
  303. package/src/providers/integrations/claude/claude.ts +606 -582
  304. package/src/providers/integrations/cline/cline-quota.ts +18 -16
  305. package/src/providers/integrations/cline/cline.ts +539 -468
  306. package/src/providers/integrations/codex/codex.ts +5 -8
  307. package/src/providers/integrations/commandcode/commandcode.ts +675 -541
  308. package/src/providers/integrations/commandcode/discovery.ts +116 -0
  309. package/src/providers/integrations/commandcode/quota.ts +160 -0
  310. package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
  311. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
  312. package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
  313. package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
  314. package/src/providers/integrations/cursor/cursor-request.ts +471 -0
  315. package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
  316. package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
  317. package/src/providers/integrations/cursor/cursor.ts +960 -0
  318. package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
  319. package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
  320. package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
  321. package/src/providers/integrations/factory/factory-oauth.ts +7 -11
  322. package/src/providers/integrations/github/github.ts +4 -4
  323. package/src/providers/integrations/hermes.ts +79 -8
  324. package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
  325. package/src/providers/integrations/kilo/kilo.ts +153 -146
  326. package/src/providers/integrations/opencode/opencode-go-quota.ts +130 -0
  327. package/src/providers/integrations/opencode/provider.ts +532 -499
  328. package/src/providers/integrations/openrouter/provider.ts +139 -281
  329. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
  330. package/src/providers/integrations/qoder/qoder-oauth.ts +310 -263
  331. package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
  332. package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
  333. package/src/providers/integrations/qoder/qoder.ts +865 -730
  334. package/src/providers/integrations/shared/install-id.ts +29 -2
  335. package/src/providers/integrations/trae/trae-catalog.ts +190 -0
  336. package/src/providers/integrations/trae/trae-headers.ts +149 -0
  337. package/src/providers/integrations/trae/trae-oauth.ts +773 -0
  338. package/src/providers/integrations/trae/trae-protocol.ts +400 -0
  339. package/src/providers/integrations/trae/trae-realm.ts +220 -0
  340. package/src/providers/integrations/trae/trae.ts +458 -0
  341. package/src/providers/operations/account-health-service.ts +107 -10
  342. package/src/providers/operations/client-versions.ts +143 -6
  343. package/src/providers/operations/model-catalog-cache.ts +18 -3
  344. package/src/providers/operations/provider-catalog-seeder.ts +95 -86
  345. package/src/providers/operations/provider-catalog-service.ts +76 -0
  346. package/src/providers/operations/provider-credential-service.ts +46 -7
  347. package/src/providers/provider-contracts.ts +76 -0
  348. package/src/providers/provider-metadata.ts +78 -6
  349. package/src/providers/provider-model-definition.ts +373 -349
  350. package/src/security/api-key-auth.ts +126 -8
  351. package/src/transport/contracts.ts +3 -0
  352. package/src/transport/dispatch/attempt-finalize.ts +39 -2
  353. package/src/transport/dispatch/attempt-loop.ts +530 -513
  354. package/src/transport/dispatch/empty-stream.ts +17 -26
  355. package/src/transport/dispatch/generation.ts +88 -21
  356. package/src/transport/dispatch/leases.ts +99 -2
  357. package/src/transport/dispatch/proxy-request.ts +60 -3
  358. package/src/transport/dispatch/streaming/finalize.ts +16 -1
  359. package/src/transport/dispatch/streaming/stream.ts +22 -2
  360. package/src/transport/dispatch/upstream.ts +19 -1
  361. package/src/transport/failure-policy.ts +20 -0
  362. package/src/transport/guards/auth-guard.ts +26 -1
  363. package/src/transport/guards/body-policy.ts +5 -4
  364. package/src/transport/request/preparer.ts +103 -0
  365. package/src/transport/request/state.ts +14 -0
  366. package/src/transport/routing/plan-gate.ts +45 -0
  367. package/src/transport/routing/route-catalog.ts +799 -738
  368. package/src/transport/routing/route-model.ts +52 -1
  369. package/src/transport/routing/router.ts +51 -0
  370. package/src/transport/surface/chat/encode.ts +18 -31
  371. package/src/transport/surface/messages/stream.ts +3 -2
  372. package/src/transport/translation/tool-repair.ts +37 -16
  373. package/src/workers/account-identity-sweep.ts +14 -12
  374. package/src/workers/daily-checkin.ts +62 -0
  375. package/src/workers/quota-refresh-sweep.ts +27 -2
  376. package/dist/dashboard/assets/BannedPage-DUrXpp-X.js +0 -1
  377. package/dist/dashboard/assets/CombosPage-B7ETkfVH.js +0 -1
  378. package/dist/dashboard/assets/ConsoleLogPage-AisQzCI1.js +0 -1
  379. package/dist/dashboard/assets/HealthEventsModal-BNflDm_f.js +0 -1
  380. package/dist/dashboard/assets/ModelPicker-CAiPR5Vd.js +0 -1
  381. package/dist/dashboard/assets/OverviewPage-C3NIzsDx.js +0 -1
  382. package/dist/dashboard/assets/ProviderDetailPage-BK8i5B5X.js +0 -3
  383. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
  384. package/dist/dashboard/assets/ProvidersPage-CvCVgm1e.js +0 -1
  385. package/dist/dashboard/assets/ProxyPage-DjabkuzP.js +0 -7
  386. package/dist/dashboard/assets/QuotaPage-Dxe0B0iu.js +0 -1
  387. package/dist/dashboard/assets/SetupPage-BaD4P_jW.js +0 -1
  388. package/dist/dashboard/assets/StudioPage-CLP4le3U.js +0 -31
  389. package/dist/dashboard/assets/UsagePage-Djes-Epy.js +0 -56
  390. package/dist/dashboard/assets/api-keys-CjIGQhe7.js +0 -1
  391. package/dist/dashboard/assets/app-Cjgo4-Lf.css +0 -2
  392. package/dist/dashboard/assets/assert-Bwh6DJTK.js +0 -1
  393. package/dist/dashboard/assets/cli-tools-DccaOfhX.js +0 -1
  394. package/dist/dashboard/assets/clipboard-button-DQmMikNQ.js +0 -1
  395. package/dist/dashboard/assets/entry-Cpi0rph1.js +0 -4
  396. package/dist/dashboard/assets/entry-Dm3PpDCO.js +0 -1
  397. package/dist/dashboard/assets/entry-keNDZ_Wz.js +0 -1
  398. package/dist/dashboard/assets/github-badge-BuAJr8oU.js +0 -1
  399. package/dist/dashboard/assets/model-bans-CVvHZ-qz.js +0 -1
  400. package/dist/dashboard/assets/mutation-BIizFZav.js +0 -1
  401. package/dist/dashboard/assets/privacy-mode-CQjbZ3xk.js +0 -1
  402. package/dist/dashboard/assets/routing-jntw9-Ui.js +0 -1
  403. package/dist/dashboard/assets/select-CGUuTQmj.js +0 -41
  404. package/dist/dashboard/assets/settings-BAS6QbmJ.js +0 -1
  405. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
  406. package/dist/dashboard/assets/terminal-C9mZ4Ugm.js +0 -1
  407. package/dist/dashboard/assets/toast-DKAdP2mo.js +0 -2
  408. package/scripts/cli/cli.ts +0 -771
  409. package/scripts/operations/probe-egress.ts +0 -335
  410. package/scripts/operations/restart.ts +0 -120
  411. package/scripts/shared/env.ts +0 -269
  412. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.ico +0 -0
  413. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.png +0 -0
  414. /package/scripts/cli/{menu.ts → ui/prompt.ts} +0 -0
  415. /package/scripts/cli/{wordmark.ts → ui/wordmark.ts} +0 -0
@@ -1,171 +1,200 @@
1
- import { createHash } from "node:crypto";
2
- import { readFileSync } from "node:fs";
3
-
4
- import type { OpenAICompatibleAdapterConfig } from "../../../adapters/compatible-adapter";
5
- import { withBearerAuthentication } from "../../../adapters/compatible-adapter";
6
- import type { CanonicalRequest } from "../../../../protocol/canonical-model";
7
- import type { ProviderDispatchContext, ProviderDispatchTarget, ProviderId } from "../../../provider-contracts";
8
- import { resolveInboundSessionId } from "../../../operations/session-resolution";
9
- import { buddyConversationHeaders } from "../buddy-conversation-headers";
10
- import {
11
- buildWorkBuddyUserAgent,
12
- getWorkBuddyClientVersion,
13
- getWorkBuddyCliVersion,
14
- } from "../../../operations/client-versions";
15
-
16
- /**
17
- * WorkBuddy serves chat from a versioned path on a base URL that carries no
18
- * version segment (billing uses the unversioned `/billing/…` path), so both the
19
- * adapter config and the static catalog rows must state it. The generic
20
- * `/chat/completions` default would join into
21
- * `https://www.workbuddy.ai/chat/completions`, which that upstream answers with
22
- * a 405 HTML error page.
23
- */
24
- export const WORKBUDDY_CHAT_PATH = "/v2/chat/completions" as const;
25
-
26
- export {
27
- buildWorkBuddyUserAgent,
28
- getWorkBuddyClientVersion,
29
- getWorkBuddyCliVersion,
30
- };
31
-
32
- /** WorkBuddy international gateway domain (Origin/Referer/X-Domain). */
33
- export const WORKBUDDY_DOMAIN = "www.workbuddy.ai";
34
-
35
- /**
36
- * Stable per-account device identifiers.
37
- *
38
- * WorkBuddy's desktop client presents one fixed virtual device per account.
39
- * Deriving `X-Machine-ID` / `X-Session-ID` from the account id (sha256, 36 hex)
40
- * keeps them constant across restarts while staying distinct per account, so
41
- * multiple pooled accounts are not correlated by a missing or drifting device
42
- * fingerprint. Mirrors the reference gateway's `wb2a:<purpose>:<uid>` scheme.
43
- */
44
- function deriveAccountStableId(accountId: string, purpose: string): string {
45
- const sum = createHash("sha256").update(`wb2a:${purpose}:${accountId}`).digest("hex");
46
- return sum.slice(0, 36);
47
- }
48
-
49
- /**
50
- * Per-request WorkBuddy chat headers — the exact desktop-client contract.
51
- *
52
- * The international gateway gates chat on the desktop `User-Agent` (platform
53
- * brand `WorkBuddy AI`; the wrong brand trips 403 code 11140), plus the
54
- * `X-CodeBuddy-Request` risk gate and the account-scoped device headers.
55
- * The background client-version monitor keeps this value fresh; dispatch only
56
- * reads the cached value and never waits on a version endpoint.
57
- */
58
- export async function workbuddyHeaders(
59
- context?: ProviderDispatchContext,
60
- request?: CanonicalRequest,
61
- domain = WORKBUDDY_DOMAIN,
62
- clientVersion = getWorkBuddyClientVersion(),
63
- ): Promise<Record<string, string>> {
64
- const headers: Record<string, string> = {
65
- accept: "application/json, text/event-stream",
66
- "user-agent": buildWorkBuddyUserAgent(clientVersion, getWorkBuddyCliVersion()),
67
- origin: `https://${domain}`,
68
- referer: `https://${domain}/`,
69
- "accept-language": "en-US",
70
- "x-requested-with": "XMLHttpRequest",
71
- "x-codebuddy-request": "1",
72
- "x-domain": domain,
73
- "x-agent-purpose": "conversation",
74
- "x-ide-name": "WorkBuddy",
75
- "x-ide-type": "WorkBuddy",
76
- "x-ide-version": clientVersion,
77
- "x-product": "WorkBuddy",
78
- // Personal international accounts carry no enterprise id: declare it
79
- // explicitly so upstream risk control does not treat the omission as
80
- // suspicious.
81
- "x-no-enterprise-id": "1",
82
- };
83
- // The conversation/tracing family the upstream's channel check looks for. A
84
- // live request answered `400 · 11128 "conversation id missing"` while carrying
85
- // only `x-session-id`; the aggregation key below is the one the reference
86
- // marks mandatory.
87
- Object.assign(
88
- headers,
89
- buddyConversationHeaders({
90
- ...(resolveInboundSessionId(context, request) === undefined
91
- ? {}
92
- : { conversationId: resolveInboundSessionId(context, request) }),
93
- }),
94
- );
95
- const accountId = context?.credential.account_id;
96
- if (accountId) {
97
- headers["x-user-id"] = accountId;
98
- headers["x-machine-id"] = deriveAccountStableId(accountId, "machine");
99
- headers["x-session-id"] = deriveAccountStableId(accountId, "session");
100
- }
101
- const deviceToken = readBuddyDeviceToken(context);
102
- if (deviceToken !== undefined) headers["x-device-token"] = deviceToken;
103
- return headers;
104
- }
105
-
106
- /**
107
- * Desktop risk token, sent only when the account actually has one.
108
- *
109
- * The account's own `auth_state` wins. When it carries none, a file the
110
- * desktop client writes (`CARTETHYIA_DEVICE_TOKEN_FILE`, default
111
- * `/app/data/device_token`) is read instead — cached for five minutes, and
112
- * ignored when it is larger than 1 KB, because a token file that size is not
113
- * a token. A missing file is the normal case and reads as "no token".
114
- */
115
- const DEVICE_TOKEN_FILE_MAX_BYTES = 1024;
116
- const DEVICE_TOKEN_FILE_TTL_MS = 5 * 60_000;
117
- let deviceTokenFileCache: { readonly value: string | undefined; readonly readAt: number } | undefined;
118
-
119
- export function readBuddyDeviceToken(context?: ProviderDispatchContext): string | undefined {
120
- const state = context?.credential.auth_state;
121
- const value = state?.["deviceToken"] ?? state?.["device_token"];
122
- if (typeof value === "string" && value.trim().length > 0) return value.trim();
123
- return readDeviceTokenFile();
124
- }
125
-
126
- function readDeviceTokenFile(): string | undefined {
127
- const now = Date.now();
128
- if (deviceTokenFileCache !== undefined && now - deviceTokenFileCache.readAt < DEVICE_TOKEN_FILE_TTL_MS) {
129
- return deviceTokenFileCache.value;
130
- }
131
- const path = process.env["CARTETHYIA_DEVICE_TOKEN_FILE"] ?? "/app/data/device_token";
132
- let value: string | undefined;
133
- try {
134
- const raw = readFileSync(path);
135
- if (raw.byteLength > 0 && raw.byteLength <= DEVICE_TOKEN_FILE_MAX_BYTES) {
136
- const text = raw.toString("utf8").trim();
137
- if (text.length > 0) value = text;
138
- }
139
- } catch {
140
- value = undefined;
141
- }
142
- deviceTokenFileCache = { value, readAt: now };
143
- return value;
144
- }
145
-
146
- /**
147
- * Shared OpenAI-compatible chat-adapter config for WorkBuddy: bearer auth
148
- * (api_key and oauth both use `Authorization: Bearer`), `/v2/chat/completions`
149
- * endpoint, per-request desktop-client headers, and the payload hook.
150
- */
151
- export function workbuddyAdapterConfig(args: {
152
- providerId: ProviderId;
153
- baseUrl: string;
154
- domain?: string;
155
- prePayload: (
156
- payload: Record<string, unknown>,
157
- request: CanonicalRequest,
158
- candidate: ProviderDispatchTarget,
159
- ) => void;
160
- fetchImpl?: typeof fetch;
161
- }): OpenAICompatibleAdapterConfig {
162
- return withBearerAuthentication({
163
- provider_id: args.providerId,
164
- base_url: args.baseUrl,
165
- endpoint_paths_by_wire_family: { chat: WORKBUDDY_CHAT_PATH },
166
- buildExtraHeaders: (context, request) =>
167
- workbuddyHeaders(context, request, args.domain ?? WORKBUDDY_DOMAIN),
168
- prePayload: args.prePayload,
169
- ...(args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}),
170
- });
171
- }
1
+ import { createHash } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+
4
+ import type { OpenAICompatibleAdapterConfig } from "../../../adapters/compatible-adapter";
5
+ import { withBearerAuthentication } from "../../../adapters/compatible-adapter";
6
+ import type { CanonicalRequest } from "../../../../protocol/canonical-model";
7
+ import type { ProviderDispatchContext, ProviderDispatchTarget, ProviderId } from "../../../provider-contracts";
8
+ import { resolveInboundSessionId } from "../../../operations/session-resolution";
9
+ import { buddyConversationHeaders } from "../buddy-conversation-headers";
10
+ import { buddyCliHeaders, buddyCliTraceHeaders } from "../buddy-cli-headers";
11
+ import { buddyUserId, resolveBuddyIdentity } from "../buddy-identity";
12
+ import {
13
+ buildWorkBuddyUserAgent,
14
+ getWorkBuddyClientVersion,
15
+ getWorkBuddyCliVersion,
16
+ } from "../../../operations/client-versions";
17
+
18
+ /**
19
+ * WorkBuddy serves chat from a versioned path on a base URL that carries no
20
+ * version segment (billing uses the unversioned `/billing/…` path), so both the
21
+ * adapter config and the static catalog rows must state it. The generic
22
+ * `/chat/completions` default would join into
23
+ * `https://www.workbuddy.ai/chat/completions`, which that upstream answers with
24
+ * a 405 HTML error page.
25
+ */
26
+ export const WORKBUDDY_CHAT_PATH = "/v2/chat/completions" as const;
27
+
28
+ export {
29
+ buildWorkBuddyUserAgent,
30
+ getWorkBuddyClientVersion,
31
+ getWorkBuddyCliVersion,
32
+ };
33
+
34
+ /** WorkBuddy international gateway domain (Origin/Referer/X-Domain). */
35
+ export const WORKBUDDY_DOMAIN = "www.workbuddy.ai";
36
+
37
+ /**
38
+ * Stable per-account device identifiers.
39
+ *
40
+ * WorkBuddy's desktop client presents one fixed virtual device per account.
41
+ * Deriving `X-Machine-ID` / `X-Session-ID` from the account id (sha256, 36 hex)
42
+ * keeps them constant across restarts while staying distinct per account, so
43
+ * multiple pooled accounts are not correlated by a missing or drifting device
44
+ * fingerprint. Mirrors the reference gateway's `wb2a:<purpose>:<uid>` scheme.
45
+ */
46
+ function deriveAccountStableId(accountId: string, purpose: string): string {
47
+ const sum = createHash("sha256").update(`wb2a:${purpose}:${accountId}`).digest("hex");
48
+ return sum.slice(0, 36);
49
+ }
50
+
51
+ /**
52
+ * Per-request WorkBuddy chat headers — the exact desktop-client contract.
53
+ *
54
+ * The international gateway gates chat on the desktop `User-Agent` (platform
55
+ * brand `WorkBuddy AI`; the wrong brand trips 403 code 11140), plus the
56
+ * `X-CodeBuddy-Request` risk gate and the account-scoped device headers.
57
+ * The background client-version monitor keeps this value fresh; dispatch only
58
+ * reads the cached value and never waits on a version endpoint.
59
+ *
60
+ * `cliIdentity` selects the reference CLI contract instead. The two are not
61
+ * variations of one set: the CLI drops every browser header and the device
62
+ * token, and adds the agent envelope. Left unset, nothing changes.
63
+ */
64
+ export async function workbuddyHeaders(
65
+ context?: ProviderDispatchContext,
66
+ request?: CanonicalRequest,
67
+ domain = WORKBUDDY_DOMAIN,
68
+ clientVersion = getWorkBuddyClientVersion(),
69
+ cliIdentity?: boolean,
70
+ ): Promise<Record<string, string>> {
71
+ const conversation = buddyConversationHeaders({
72
+ ...(resolveInboundSessionId(context, request) === undefined
73
+ ? {}
74
+ : { conversationId: resolveInboundSessionId(context, request) }),
75
+ });
76
+ const accountId = context?.credential.account_id;
77
+ if (resolveBuddyIdentity(context, cliIdentity) === "cli") {
78
+ // The reference CLI: issued by the OpenAI Node SDK, so no browser header
79
+ // and no device token, and the machine/session device pair does not
80
+ // exist — only `X-User-Id`, which is `anonymous` without a uid.
81
+ return {
82
+ accept: "application/json",
83
+ "user-agent": `CLI/${getWorkBuddyCliVersion()} CodeBuddy/${getWorkBuddyCliVersion()}`,
84
+ "x-domain": domain,
85
+ "x-agent-purpose": "conversation",
86
+ "x-ide-name": "CLI",
87
+ "x-ide-type": "CLI",
88
+ "x-ide-version": getWorkBuddyCliVersion(),
89
+ "x-product": "SaaS",
90
+ "x-no-enterprise-id": "1",
91
+ ...conversation,
92
+ ...buddyCliTraceHeaders(conversation),
93
+ ...buddyCliHeaders({ userId: buddyUserId(context) }),
94
+ };
95
+ }
96
+ const headers: Record<string, string> = {
97
+ accept: "application/json, text/event-stream",
98
+ "user-agent": buildWorkBuddyUserAgent(clientVersion, getWorkBuddyCliVersion()),
99
+ origin: `https://${domain}`,
100
+ referer: `https://${domain}/`,
101
+ "accept-language": "en-US",
102
+ "x-requested-with": "XMLHttpRequest",
103
+ "x-codebuddy-request": "1",
104
+ "x-domain": domain,
105
+ "x-agent-purpose": "conversation",
106
+ "x-ide-name": "WorkBuddy",
107
+ "x-ide-type": "WorkBuddy",
108
+ "x-ide-version": clientVersion,
109
+ // Both references send the literal `SaaS` here. `WorkBuddy` was a value
110
+ // this repo invented; the desktop product name belongs in `x-ide-name`,
111
+ // and the CN test below compares the two realms against each other rather
112
+ // than to a hard-coded string.
113
+ "x-product": "SaaS",
114
+ // Personal international accounts carry no enterprise id: declare it
115
+ // explicitly so upstream risk control does not treat the omission as
116
+ // suspicious.
117
+ "x-no-enterprise-id": "1",
118
+ };
119
+ // The conversation/tracing family the upstream's channel check looks for. A
120
+ // live request carrying only `x-session-id` was refused for a missing
121
+ // conversation id; the aggregation key below is the one the reference marks
122
+ // mandatory.
123
+ Object.assign(headers, conversation);
124
+ if (accountId) {
125
+ headers["x-user-id"] = accountId;
126
+ headers["x-machine-id"] = deriveAccountStableId(accountId, "machine");
127
+ headers["x-session-id"] = deriveAccountStableId(accountId, "session");
128
+ }
129
+ const deviceToken = readBuddyDeviceToken(context);
130
+ if (deviceToken !== undefined) headers["x-device-token"] = deviceToken;
131
+ return headers;
132
+ }
133
+
134
+ /**
135
+ * Desktop risk token, sent only when the account actually has one.
136
+ *
137
+ * The account's own `auth_state` wins. When it carries none, a file the
138
+ * desktop client writes (`CARTETHYIA_DEVICE_TOKEN_FILE`, default
139
+ * `/app/data/device_token`) is read instead — cached for five minutes, and
140
+ * ignored when it is larger than 1 KB, because a token file that size is not
141
+ * a token. A missing file is the normal case and reads as "no token".
142
+ */
143
+ const DEVICE_TOKEN_FILE_MAX_BYTES = 1024;
144
+ const DEVICE_TOKEN_FILE_TTL_MS = 5 * 60_000;
145
+ let deviceTokenFileCache: { readonly value: string | undefined; readonly readAt: number } | undefined;
146
+
147
+ export function readBuddyDeviceToken(context?: ProviderDispatchContext): string | undefined {
148
+ const state = context?.credential.auth_state;
149
+ const value = state?.["deviceToken"] ?? state?.["device_token"];
150
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
151
+ return readDeviceTokenFile();
152
+ }
153
+
154
+ function readDeviceTokenFile(): string | undefined {
155
+ const now = Date.now();
156
+ if (deviceTokenFileCache !== undefined && now - deviceTokenFileCache.readAt < DEVICE_TOKEN_FILE_TTL_MS) {
157
+ return deviceTokenFileCache.value;
158
+ }
159
+ const path = process.env["CARTETHYIA_DEVICE_TOKEN_FILE"] ?? "/app/data/device_token";
160
+ let value: string | undefined;
161
+ try {
162
+ const raw = readFileSync(path);
163
+ if (raw.byteLength > 0 && raw.byteLength <= DEVICE_TOKEN_FILE_MAX_BYTES) {
164
+ const text = raw.toString("utf8").trim();
165
+ if (text.length > 0) value = text;
166
+ }
167
+ } catch {
168
+ value = undefined;
169
+ }
170
+ deviceTokenFileCache = { value, readAt: now };
171
+ return value;
172
+ }
173
+
174
+ /**
175
+ * Shared OpenAI-compatible chat-adapter config for WorkBuddy: bearer auth
176
+ * (api_key and oauth both use `Authorization: Bearer`), `/v2/chat/completions`
177
+ * endpoint, per-request desktop-client headers, and the payload hook.
178
+ */
179
+ export function workbuddyAdapterConfig(args: {
180
+ providerId: ProviderId;
181
+ baseUrl: string;
182
+ domain?: string;
183
+ prePayload: (
184
+ payload: Record<string, unknown>,
185
+ request: CanonicalRequest,
186
+ candidate: ProviderDispatchTarget,
187
+ ) => void;
188
+ fetchImpl?: typeof fetch;
189
+ cliIdentity?: boolean;
190
+ }): OpenAICompatibleAdapterConfig {
191
+ return withBearerAuthentication({
192
+ provider_id: args.providerId,
193
+ base_url: args.baseUrl,
194
+ endpoint_paths_by_wire_family: { chat: WORKBUDDY_CHAT_PATH },
195
+ buildExtraHeaders: (context, request) =>
196
+ workbuddyHeaders(context, request, args.domain ?? WORKBUDDY_DOMAIN, undefined, args.cliIdentity),
197
+ prePayload: args.prePayload,
198
+ ...(args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}),
199
+ });
200
+ }