cartethyia 2.7.0 → 2.7.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 (320) hide show
  1. package/.env.example +107 -106
  2. package/README.md +25 -2
  3. package/dist/dashboard/assets/{BannedPage-P76QLGJA.js → BannedPage-D-J75hBM.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-D-J75hBM.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-DdBggJS9.js → CliToolDetailPage-CVe6RaBH.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-CVe6RaBH.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-aN1iXhhP.js → CliToolsPage-Dd8ZSamp.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-Dd8ZSamp.js.gz +0 -0
  9. package/dist/dashboard/assets/{CombosPage-DE-F1nxZ.js → CombosPage-QoJ6foxo.js} +1 -1
  10. package/dist/dashboard/assets/CombosPage-QoJ6foxo.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-3CUxMy2X.js → CustomizationPage-CKUFTcW4.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-CKUFTcW4.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js.gz +0 -0
  17. package/dist/dashboard/assets/LoginPage-DubdQcvX.js +1 -0
  18. package/dist/dashboard/assets/LoginPage-DubdQcvX.js.gz +0 -0
  19. package/dist/dashboard/assets/{ModelPicker-DKVslqG3.js → ModelPicker-BH8Tehdi.js} +1 -1
  20. package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js +4 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +1 -0
  26. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  27. package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js +1 -0
  28. package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js.gz +0 -0
  29. package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js +9 -0
  30. package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js.gz +0 -0
  31. package/dist/dashboard/assets/{QuotaPage-BjldQwIU.js → QuotaPage-ATpaxrbl.js} +1 -1
  32. package/dist/dashboard/assets/QuotaPage-ATpaxrbl.js.gz +0 -0
  33. package/dist/dashboard/assets/{SettingsPage-DUIoLRxk.js → SettingsPage-CETophpx.js} +1 -1
  34. package/dist/dashboard/assets/SettingsPage-CETophpx.js.gz +0 -0
  35. package/dist/dashboard/assets/SetupPage-BdgECjqq.js +1 -0
  36. package/dist/dashboard/assets/SetupPage-BdgECjqq.js.gz +0 -0
  37. package/dist/dashboard/assets/{SortableList-fI7JkdIB.js → SortableList-CAK-EN9z.js} +1 -1
  38. package/dist/dashboard/assets/SortableList-CAK-EN9z.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-CHVZqskZ.js → StudioPage-B7W2_RhT.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-B7W2_RhT.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-6T4EFDjW.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-6T4EFDjW.js.gz +0 -0
  43. package/dist/dashboard/assets/{api-keys-DFY_fZU4.js → api-keys-CUi6UXsR.js} +1 -1
  44. package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
  45. package/dist/dashboard/assets/app-KvsUJUMF.css +2 -0
  46. package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-CwdnZARt.js → assert-67xXH5rf.js} +1 -1
  48. package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-Dr-uf0Au.js → badge-DC2L43Mj.js} +1 -1
  50. package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-BddmMdoS.js → banner-BBVcb0J3.js} +1 -1
  52. package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
  53. package/dist/dashboard/assets/{card-CK0PXWlX.js → card-Cca1XC6G.js} +1 -1
  54. package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
  55. package/dist/dashboard/assets/{cli-tools-CcB6mGh2.js → cli-tools-KvsWa1Jl.js} +1 -1
  56. package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
  57. package/dist/dashboard/assets/{clipboard-button-DGb2P9Yx.js → clipboard-button-DUzEbynq.js} +1 -1
  58. package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
  59. package/dist/dashboard/assets/{dialog-LkfuL50u.js → dialog-DFvWhwTa.js} +1 -1
  60. package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
  61. package/dist/dashboard/assets/{drawer-ZbEZ_84c.js → drawer-CFM2sMX1.js} +1 -1
  62. package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-CWBY5A0f.js → entry-BLBLxX0h.js} +3 -3
  64. package/dist/dashboard/assets/entry-BLBLxX0h.js.gz +0 -0
  65. package/dist/dashboard/assets/{entry-BJfsVMvA.js → entry-Bqy8RFvM.js} +1 -1
  66. package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
  67. package/dist/dashboard/assets/{entry-CvC1-Zqx.js → entry-_xHo5x6r.js} +1 -1
  68. package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
  69. package/dist/dashboard/assets/formatters-mpBPqAQ8.js +1 -0
  70. package/dist/dashboard/assets/formatters-mpBPqAQ8.js.gz +0 -0
  71. package/dist/dashboard/assets/{github-badge-BxpznFLt.js → github-badge-C9e8Ts7e.js} +1 -1
  72. package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
  73. package/dist/dashboard/assets/{index-CDzP3ovJ.js → index-D6QzEC_n.js} +2 -2
  74. package/dist/dashboard/assets/index-D6QzEC_n.js.gz +0 -0
  75. package/dist/dashboard/assets/{inline-Ae13MQ5w.js → inline-BFUcvllj.js} +1 -1
  76. package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
  77. package/dist/dashboard/assets/{input-D23fo7cp.js → input-C_ILAvBb.js} +1 -1
  78. package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
  79. package/dist/dashboard/assets/{layout-CSsxjj5P.js → layout-CBIyT6Rt.js} +1 -1
  80. package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
  81. package/dist/dashboard/assets/{list-row-dmbwZMDY.js → list-row-ebJ_-Nsb.js} +1 -1
  82. package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
  83. package/dist/dashboard/assets/{live-BG1oUsZZ.js → live-D0mm--ME.js} +1 -1
  84. package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-DSF-e4ho.js → mutation-sMD965oO.js} +1 -1
  86. package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
  87. package/dist/dashboard/assets/{numbers-Bm_aECY7.js → numbers-BO9dKaFr.js} +1 -1
  88. package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
  89. package/dist/dashboard/assets/providers/inception.svg +5 -0
  90. package/dist/dashboard/assets/providers/inception.svg.gz +0 -0
  91. package/dist/dashboard/assets/{query-keys-CF56nYIV.js → query-keys-CfL_Zdoh.js} +1 -1
  92. package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
  93. package/dist/dashboard/assets/{routing-CeA71n7G.js → routing-BnIpi1IJ.js} +1 -1
  94. package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
  95. package/dist/dashboard/assets/{select-KIT0rC6G.js → select-Cj5Nw43g.js} +1 -1
  96. package/dist/dashboard/assets/select-Cj5Nw43g.js.gz +0 -0
  97. package/dist/dashboard/assets/{settings-B_2gkePf.js → settings-BAFhDgX8.js} +1 -1
  98. package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
  99. package/dist/dashboard/assets/{stack-D7WOSzcX.js → stack-EMrOMt4I.js} +1 -1
  100. package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
  101. package/dist/dashboard/assets/{state-BBxtKZOQ.js → state-CWoo7ty5.js} +1 -1
  102. package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
  103. package/dist/dashboard/assets/{switch-DNS_D5Ea.js → switch-CCjT6IkF.js} +1 -1
  104. package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
  105. package/dist/dashboard/assets/{useMutation-BgoxdoMk.js → useMutation-DjkLHUcE.js} +1 -1
  106. package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
  107. package/dist/dashboard/index.html +1 -1
  108. package/dist/dashboard/index.html.gz +0 -0
  109. package/dist/dashboard/precompressed-assets.json +54 -53
  110. package/migrations/full-postgres/0024_add_pool_provider_cooldowns.sql +12 -0
  111. package/migrations/full-postgres/0025_add_pool_random_strategy.sql +3 -0
  112. package/migrations/lite-sqlite3/0024_add_pool_provider_cooldowns.sql +12 -0
  113. package/migrations/lite-sqlite3/0025_add_pool_random_strategy.sql +20 -0
  114. package/package.json +96 -96
  115. package/scripts/cli/console-menu.ts +5 -2
  116. package/scripts/cli/tray.ts +21 -6
  117. package/src/boot/app.ts +684 -714
  118. package/src/boot/dependencies.ts +65 -62
  119. package/src/config/cooldowns.ts +16 -0
  120. package/src/config/providers.ts +13 -1
  121. package/src/config/resources.ts +70 -70
  122. package/src/config/security.ts +37 -37
  123. package/src/config/spec.ts +371 -358
  124. package/src/console/console-router.ts +233 -225
  125. package/src/console/domain-registration.ts +398 -381
  126. package/src/console/providers/catalog/catalog-projections.ts +2 -6
  127. package/src/console/providers/catalog/contracts.ts +19 -0
  128. package/src/console/providers/catalog/provider-operations.ts +38 -0
  129. package/src/console/providers/catalog/routes.ts +14 -0
  130. package/src/console/providers/catalog/store-helpers.ts +326 -0
  131. package/src/console/providers/catalog/store.ts +1292 -1563
  132. package/src/console/quota/account-quota-global.ts +2 -2
  133. package/src/console/quota/account-quota-shared.ts +1 -1
  134. package/src/console/quota/account-quota-tenant.ts +35 -12
  135. package/src/console/quota/quota-view.ts +2 -2
  136. package/src/console/routing/pools/contracts.ts +41 -1
  137. package/src/console/routing/pools/operations.ts +183 -4
  138. package/src/console/routing/pools/relay-deploy.ts +17 -4
  139. package/src/console/routing/pools/routes.ts +30 -0
  140. package/src/console/routing/pools/store.ts +98 -8
  141. package/src/main.ts +183 -167
  142. package/src/network/outbound-fetch.ts +226 -365
  143. package/src/network/pool/agent.ts +8 -0
  144. package/src/network/pool/pool-state.ts +7 -0
  145. package/src/network/pool/selector.ts +110 -2
  146. package/src/network/pool-cooldown-store.ts +252 -0
  147. package/src/network/pool-health-machine.ts +5 -3
  148. package/src/network/pool-health.ts +36 -9
  149. package/src/observability/event-query.ts +57 -0
  150. package/src/observability/metrics.ts +467 -467
  151. package/src/observability/runtime-metrics.ts +269 -269
  152. package/src/persistence/health-event-flush.ts +2 -0
  153. package/src/persistence/schema.ts +19 -0
  154. package/src/persistence/usage-flush.ts +8 -3
  155. package/src/protocol/canonical-model.ts +1 -1
  156. package/src/protocol/dsml.ts +377 -0
  157. package/src/protocol/gateway-error.ts +232 -232
  158. package/src/{providers/provider-reasoning.ts → protocol/reasoning.ts} +10 -9
  159. package/src/protocol/request/chat.ts +7 -7
  160. package/src/protocol/request/codex.ts +2 -2
  161. package/src/protocol/request/gemini.ts +1 -1
  162. package/src/protocol/request/messages.ts +6 -6
  163. package/src/protocol/request/responses.ts +7 -7
  164. package/src/protocol/response/dsml.ts +12 -133
  165. package/src/{transport → protocol}/translation/attachment-integrity.ts +2 -2
  166. package/src/{transport → protocol}/translation/cache-controls.ts +1 -1
  167. package/src/{transport → protocol}/translation/capabilities.ts +2 -2
  168. package/src/{transport → protocol}/translation/normalize.ts +1 -1
  169. package/src/{transport → protocol}/translation/quirks.ts +1 -1
  170. package/src/{transport → protocol}/translation/search.ts +1 -1
  171. package/src/{transport → protocol}/translation/thinking.ts +23 -1
  172. package/src/{transport → protocol}/translation/tool-id.ts +1 -1
  173. package/src/{transport → protocol}/translation/tool-repair.ts +2 -2
  174. package/src/protocol/upstream-parsing.ts +176 -0
  175. package/src/providers/adapters/compatible-adapter.ts +11 -2
  176. package/src/providers/authentication/oauth-refresh-service.ts +9 -5
  177. package/src/providers/default-registry.ts +21 -5
  178. package/src/providers/discovery/probe-phases.ts +16 -3
  179. package/src/providers/discovery/probing-service.ts +4 -1
  180. package/src/providers/integrations/anthropic.ts +1 -1
  181. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -4
  182. package/src/providers/integrations/buddy/buddy-chat-shared.ts +141 -8
  183. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -1
  184. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -82
  185. package/src/providers/integrations/buddy/buddy-identity.ts +25 -0
  186. package/src/providers/integrations/buddy/buddy-server-tools.ts +240 -0
  187. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +36 -0
  188. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +6 -31
  189. package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -0
  190. package/src/providers/integrations/buddy/codebuddy/shared.ts +19 -4
  191. package/src/providers/integrations/buddy/turing-device-token.ts +262 -0
  192. package/src/providers/integrations/buddy/workbuddy/provider.ts +2 -0
  193. package/src/providers/integrations/buddy/workbuddy/shared.ts +56 -3
  194. package/src/providers/integrations/claude/claude.ts +1 -1
  195. package/src/providers/integrations/cloudflareai/cloudflareai.ts +1 -1
  196. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  197. package/src/providers/integrations/codex/codex.ts +1 -1
  198. package/src/providers/integrations/factory/factory.ts +1 -1
  199. package/src/providers/integrations/github/github.ts +1 -1
  200. package/src/providers/integrations/grok/grok.ts +1 -1
  201. package/src/providers/integrations/inception/provider.ts +91 -0
  202. package/src/providers/integrations/inferhub.ts +1 -1
  203. package/src/providers/integrations/kiro/kiro-request.ts +1 -1
  204. package/src/providers/integrations/mistral.ts +193 -19
  205. package/src/providers/integrations/opencode/fingerprint.ts +19 -2
  206. package/src/providers/integrations/opencode/prompt.ts +111 -0
  207. package/src/providers/integrations/opencode/provider.ts +101 -23
  208. package/src/providers/integrations/opencode/tool-definitions.ts +4 -4
  209. package/src/providers/integrations/qoder/qoder-checkin.ts +303 -0
  210. package/src/providers/integrations/qoder/qoder.ts +1641 -1458
  211. package/src/providers/integrations/shared/install-id.ts +2 -1
  212. package/src/providers/integrations/trae/trae-cache.ts +110 -0
  213. package/src/providers/integrations/trae/trae-oauth.ts +29 -4
  214. package/src/providers/integrations/trae/trae-protocol.ts +2 -1
  215. package/src/providers/integrations/trae/trae.ts +106 -23
  216. package/src/providers/operations/account-error-classification.ts +1660 -0
  217. package/src/providers/operations/account-health-service.ts +621 -2097
  218. package/src/providers/operations/account-reset-service.ts +2 -0
  219. package/src/providers/provider-contracts.ts +0 -2
  220. package/src/providers/provider-metadata.ts +25 -4
  221. package/src/providers/provider-probe.ts +2 -2
  222. package/src/{console → providers}/quota/cache.ts +1 -1
  223. package/src/{console → providers}/quota/refresh.ts +5 -5
  224. package/src/transport/dispatch/attempt-finalize.ts +114 -12
  225. package/src/transport/dispatch/attempt-loop.ts +3 -0
  226. package/src/transport/dispatch/empty-stream.ts +224 -217
  227. package/src/transport/dispatch/generation.ts +37 -55
  228. package/src/transport/dispatch/handler-deps.ts +32 -0
  229. package/src/transport/dispatch/leases.ts +9 -4
  230. package/src/transport/dispatch/proxy-request.ts +6 -30
  231. package/src/transport/dispatch/responses-compact.ts +22 -43
  232. package/src/transport/dispatch/streaming/finalize.ts +3 -0
  233. package/src/transport/dispatch/streaming/stream.ts +0 -8
  234. package/src/transport/dispatch/systemone.ts +22 -42
  235. package/src/transport/dispatch/token-count.ts +23 -39
  236. package/src/transport/dispatch/upstream.ts +1 -3
  237. package/src/transport/dispatch/websearch.ts +22 -41
  238. package/src/transport/failure-policy.ts +59 -178
  239. package/src/transport/guards/abuse-guard.ts +10 -0
  240. package/src/transport/guards/auth-guard.ts +109 -109
  241. package/src/transport/guards/error-lifecycle.ts +1 -1
  242. package/src/transport/guards/pipeline.ts +220 -220
  243. package/src/transport/guards/request-context.ts +245 -238
  244. package/src/transport/request/degradation.ts +20 -1
  245. package/src/transport/request/inflight.ts +4 -114
  246. package/src/transport/request/preparer.ts +6 -6
  247. package/src/transport/request/state.ts +66 -38
  248. package/src/transport/request/tenant-preferences.ts +1 -1
  249. package/src/transport/routing/route-catalog.ts +41 -31
  250. package/src/transport/routing/router.ts +2 -2
  251. package/src/transport/surface/messages/parse.ts +1 -1
  252. package/src/workers/buddy-growth-tasks.ts +346 -425
  253. package/src/workers/daily-checkin.ts +17 -6
  254. package/src/workers/quota-refresh-sweep.ts +2 -2
  255. package/src/workers/scheduler.ts +17 -1
  256. package/dist/dashboard/assets/BannedPage-P76QLGJA.js.gz +0 -0
  257. package/dist/dashboard/assets/CliToolDetailPage-DdBggJS9.js.gz +0 -0
  258. package/dist/dashboard/assets/CliToolsPage-aN1iXhhP.js.gz +0 -0
  259. package/dist/dashboard/assets/CombosPage-DE-F1nxZ.js.gz +0 -0
  260. package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js +0 -1
  261. package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js.gz +0 -0
  262. package/dist/dashboard/assets/CustomizationPage-3CUxMy2X.js.gz +0 -0
  263. package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js +0 -1
  264. package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js.gz +0 -0
  265. package/dist/dashboard/assets/LoginPage-B_0FUKWc.js +0 -1
  266. package/dist/dashboard/assets/LoginPage-B_0FUKWc.js.gz +0 -0
  267. package/dist/dashboard/assets/ModelPicker-DKVslqG3.js.gz +0 -0
  268. package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js +0 -1
  269. package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js.gz +0 -0
  270. package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js +0 -3
  271. package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js.gz +0 -0
  272. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +0 -1
  273. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  274. package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js +0 -1
  275. package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js.gz +0 -0
  276. package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js +0 -7
  277. package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js.gz +0 -0
  278. package/dist/dashboard/assets/QuotaPage-BjldQwIU.js.gz +0 -0
  279. package/dist/dashboard/assets/SettingsPage-DUIoLRxk.js.gz +0 -0
  280. package/dist/dashboard/assets/SetupPage-_bCaTM0v.js +0 -1
  281. package/dist/dashboard/assets/SetupPage-_bCaTM0v.js.gz +0 -0
  282. package/dist/dashboard/assets/SortableList-fI7JkdIB.js.gz +0 -0
  283. package/dist/dashboard/assets/StudioPage-CHVZqskZ.js.gz +0 -0
  284. package/dist/dashboard/assets/UsagePage-DiFBymHj.js +0 -56
  285. package/dist/dashboard/assets/UsagePage-DiFBymHj.js.gz +0 -0
  286. package/dist/dashboard/assets/api-keys-DFY_fZU4.js.gz +0 -0
  287. package/dist/dashboard/assets/app-BOJ1_xUB.css +0 -2
  288. package/dist/dashboard/assets/app-BOJ1_xUB.css.gz +0 -0
  289. package/dist/dashboard/assets/assert-CwdnZARt.js.gz +0 -0
  290. package/dist/dashboard/assets/badge-Dr-uf0Au.js.gz +0 -0
  291. package/dist/dashboard/assets/banner-BddmMdoS.js.gz +0 -0
  292. package/dist/dashboard/assets/card-CK0PXWlX.js.gz +0 -0
  293. package/dist/dashboard/assets/cli-tools-CcB6mGh2.js.gz +0 -0
  294. package/dist/dashboard/assets/clipboard-button-DGb2P9Yx.js.gz +0 -0
  295. package/dist/dashboard/assets/cog-CdWhsajs.js +0 -1
  296. package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
  297. package/dist/dashboard/assets/dialog-LkfuL50u.js.gz +0 -0
  298. package/dist/dashboard/assets/drawer-ZbEZ_84c.js.gz +0 -0
  299. package/dist/dashboard/assets/entry-BJfsVMvA.js.gz +0 -0
  300. package/dist/dashboard/assets/entry-CWBY5A0f.js.gz +0 -0
  301. package/dist/dashboard/assets/entry-CvC1-Zqx.js.gz +0 -0
  302. package/dist/dashboard/assets/github-badge-BxpznFLt.js.gz +0 -0
  303. package/dist/dashboard/assets/index-CDzP3ovJ.js.gz +0 -0
  304. package/dist/dashboard/assets/inline-Ae13MQ5w.js.gz +0 -0
  305. package/dist/dashboard/assets/input-D23fo7cp.js.gz +0 -0
  306. package/dist/dashboard/assets/layout-CSsxjj5P.js.gz +0 -0
  307. package/dist/dashboard/assets/list-row-dmbwZMDY.js.gz +0 -0
  308. package/dist/dashboard/assets/live-BG1oUsZZ.js.gz +0 -0
  309. package/dist/dashboard/assets/mutation-DSF-e4ho.js.gz +0 -0
  310. package/dist/dashboard/assets/numbers-Bm_aECY7.js.gz +0 -0
  311. package/dist/dashboard/assets/query-keys-CF56nYIV.js.gz +0 -0
  312. package/dist/dashboard/assets/routing-CeA71n7G.js.gz +0 -0
  313. package/dist/dashboard/assets/select-KIT0rC6G.js.gz +0 -0
  314. package/dist/dashboard/assets/settings-B_2gkePf.js.gz +0 -0
  315. package/dist/dashboard/assets/stack-D7WOSzcX.js.gz +0 -0
  316. package/dist/dashboard/assets/state-BBxtKZOQ.js.gz +0 -0
  317. package/dist/dashboard/assets/switch-DNS_D5Ea.js.gz +0 -0
  318. package/dist/dashboard/assets/useMutation-BgoxdoMk.js.gz +0 -0
  319. /package/dist/dashboard/assets/{app-CaXZ9SCn.js → app-BaWd0puM.js} +0 -0
  320. /package/dist/dashboard/assets/{app-CaXZ9SCn.js.gz → app-BaWd0puM.js.gz} +0 -0
@@ -16,19 +16,18 @@ import { providerBaseUrl } from "../../../provider-metadata";
16
16
  import {
17
17
  codebuddyAdapterConfig,
18
18
  } from "./shared";
19
+ import { CODEBUDDY_CN_RAW } from "./cn-catalog";
19
20
  import { CODEBUDDY_SYSTEM_PROMPT } from "./provider";
20
21
  import {
21
22
  BUDDY_CN_IMAGE_MODEL_ID_SET,
22
23
  makeBuddyCnImageModels,
23
24
  makeBuddyModel,
24
- type BuddyRawEntry,
25
25
  } from "../buddy-catalog-shared";
26
26
  import { BUDDY_CN_MODELS_PATH, fetchBuddyDirectoryModels } from "../buddy-discovery-shared";
27
27
  import {
28
28
  buddyPrePayloadCommon,
29
- coalesceConsecutiveUserMessages,
30
- dropEmptyBuddyMessages,
31
- ensureBuddyLeadingSystem,
29
+ finalizeBuddyMessages,
30
+ injectBuddyParallelToolCalls,
32
31
  normalizeBuddyToolNames,
33
32
  } from "../buddy-chat-shared";
34
33
 
@@ -93,42 +92,18 @@ export function codeBuddyCnPrePayload(
93
92
  if (Array.isArray(payload["messages"])) {
94
93
  const messages = payload["messages"] as Array<Record<string, unknown>>;
95
94
  neutralizeCnMessages(messages);
96
- coalesceConsecutiveUserMessages(messages);
97
- dropEmptyBuddyMessages(messages);
98
- ensureBuddyLeadingSystem(messages, CODEBUDDY_CN_NEUTRAL_PROMPT);
95
+ finalizeBuddyMessages(messages, { leadPrompt: CODEBUDDY_CN_NEUTRAL_PROMPT });
99
96
  }
100
97
  ensurePayloadModel(payload, candidate, "CodeBuddy CN");
101
98
  normalizeBuddyToolNames(payload);
99
+ injectBuddyParallelToolCalls(payload, request);
102
100
  void request;
103
101
  }
104
102
 
105
103
  // Model catalog — CodeBuddy CN console and CLI catalog.
106
104
  // Exported because `buddyModelOutputLimit` searches every buddy raw table, and
107
105
  // a CN-only id would otherwise never get an output limit.
108
- export const CODEBUDDY_CN_RAW: readonly BuddyRawEntry[] = [
109
- ["glm-5.1", "GLM 5.1", true, true, 200_000, 48_000],
110
- ["glm-5.2", "GLM 5.2", true, true, 1_000_000, 64_000],
111
- ["glm-5.3", "GLM 5.3", true, true, 1_000_000, 64_000],
112
- ["glm-5.3-flash", "GLM 5.3 Flash", true, true, 1_000_000, 131_072],
113
- ["glm-5v-turbo", "GLM 5v Turbo", true, true, 200_000, 64_000],
114
- ["minimax-m3", "MiniMax M3", true, true, 512_000, 64_000],
115
- // 1M context, reasoning always on (upstream states both); vision inherited
116
- // from the M3 row, which is the same family.
117
- ["minimax-m3.1-flash", "MiniMax M3.1 Flash", true, true, 1_000_000, 128_000],
118
- ["mimo-v2.6-pro", "MiMo V2.6 Pro", true, true, 1_000_000, 128_000],
119
- ["kimi-k2.8-preview", "Kimi K2.8 Preview", true, true, 1_000_000, 64_000],
120
- ["kimi-k3.1-pro", "Kimi K3.1 Pro", true, true, 1_000_000, 32_000],
121
- ["hy3", "Hy3", true, true, 192_000, 64_000],
122
- ["hy4-preview", "Hy4 Preview", true, true, 960_000, 64_000],
123
- ["space-bunny", "Space Bunny", true, true, 1_000_000, 128_000],
124
- ["step-5-preview", "Step 5 Preview", true, true, 1_000_000, 64_000],
125
- ["deepseek-v4-pro", "DeepSeek V4 Pro", true, true, 1_000_000, 128_000],
126
- ["deepseek-v4.1-flash", "DeepSeek V4.1 Flash", true, true, 1_000_000, 128_000],
127
- // Live on the CN roster but absent here, so a request for it resolved to
128
- // nothing. Limits as published by /v3/config.
129
- ["kimi-k3-1", "Kimi K3-1", true, true, 1_000_000, 32_000],
130
- ["minimax-m2.7", "MiniMax M2.7", true, true, 200_000, 48_000],
131
- ];
106
+ export { CODEBUDDY_CN_RAW };
132
107
  /**
133
108
  * CN's image-generation rows (`hunyuan-image-alpha`).
134
109
  *
@@ -30,6 +30,7 @@ import { BUDDY_INTL_MODELS_PATH, fetchBuddyDirectoryModels } from "../buddy-disc
30
30
  import {
31
31
  applyBuddySystemPrompt,
32
32
  buddyPrePayloadCommon,
33
+ injectBuddyParallelToolCalls,
33
34
  normalizeBuddyToolNames,
34
35
  } from "../buddy-chat-shared";
35
36
 
@@ -86,6 +87,7 @@ function codeBuddyIntlPrePayload(
86
87
  applyBuddySystemPrompt(messages as Array<Record<string, unknown>>, CODEBUDDY_SYSTEM_PROMPT);
87
88
  }
88
89
  normalizeBuddyToolNames(payload);
90
+ injectBuddyParallelToolCalls(payload, request);
89
91
  void request;
90
92
  }
91
93
 
@@ -3,9 +3,10 @@ import type { CanonicalRequest } from "../../../../protocol/canonical-model";
3
3
  import type { ProviderDispatchContext, ProviderDispatchTarget, ProviderId } from "../../../provider-contracts";
4
4
  import { resolveInboundSessionId } from "../../../operations/session-resolution";
5
5
  import { buddyConversationHeaders } from "../buddy-conversation-headers";
6
- import { buddyEnterprise, buddyUserId, resolveBuddyIdentity } from "../buddy-identity";
6
+ import { buddyEnterprise, buddyUserId, resolveBuddyDeviceBinding, resolveBuddyIdentity } from "../buddy-identity";
7
7
  import { buildCodeBuddyUserAgent, getCodeBuddyCnVersion, getCodeBuddyVersion } from "../../../operations/client-versions";
8
- import { readBuddyDeviceToken } from "../workbuddy/shared";
8
+ import { buddyDeviceTokenHeaders } from "../workbuddy/shared";
9
+ import { CODEBUDDY_TURING_CONFIG } from "../turing-device-token";
9
10
  import { buddyCliHeaders, buddyCliTraceHeaders } from "../buddy-cli-headers";
10
11
 
11
12
  /** Client identity variant behind the per-request CodeBuddy headers. */
@@ -34,7 +35,6 @@ export async function codebuddyHeaders(
34
35
  ): Promise<Record<string, string>> {
35
36
  const identity = variant === "IDE" ? "IDE" : "CLI";
36
37
  const ua = buildCodeBuddyUserAgent(identity);
37
- const deviceToken = readBuddyDeviceToken(context);
38
38
  const conversationId = resolveInboundSessionId(context, request);
39
39
  const conversation = buddyConversationHeaders({
40
40
  ...(conversationId === undefined ? {} : { conversationId }),
@@ -88,7 +88,22 @@ export async function codebuddyHeaders(
88
88
  // turn-level aggregation key, and a chat request without it is refused for a
89
89
  // missing conversation id.
90
90
  ...conversation,
91
- ...(deviceToken === undefined ? {} : { "X-Device-Token": deviceToken }),
91
+ // Per-account device binding (qimei36) — distinct device per account.
92
+ ...(() => {
93
+ const binding = resolveBuddyDeviceBinding(context);
94
+ return binding === undefined ? {} : { "X-Device-Id-List": JSON.stringify({ qimei36: binding }) };
95
+ })(),
96
+ // The desktop client sends the device-token pair on both realms. SDK
97
+ // token when configured, captured token otherwise, or the official failure
98
+ // placeholder. CodeBuddy uses its own SDK payload profile (same SDK,
99
+ // distinct product identity). (CLI branch above already returned without
100
+ // device headers.)
101
+ ...(await buddyDeviceTokenHeaders({
102
+ realm: identity === "IDE" ? "intl" : "cn",
103
+ context,
104
+ cliIdentity,
105
+ turingConfig: CODEBUDDY_TURING_CONFIG,
106
+ })),
92
107
  };
93
108
  }
94
109
 
@@ -0,0 +1,262 @@
1
+ /**
2
+ * TuringShield device-token integration for the buddy providers.
3
+ *
4
+ * WorkBuddy/CodeBuddy desktop clients derive `X-Device-Token` from Tencent's
5
+ * TuringShield SDK (native addon + DLL, channel 400111). The upstream risk
6
+ * control expects that header on chat traffic, and the official desktop app
7
+ * always presents either a real token or an `X-Device-Token-Error` — never
8
+ * neither.
9
+ *
10
+ * The SDK is a proprietary native binary shipped inside this repository under
11
+ * `vendor/turing-sdk/` (extracted from the WorkBuddy desktop app, channel
12
+ * 400111). `CARTETHYIA_TURING_SDK_PATH` remains as an optional override for
13
+ * deployments that bring their own copy. When neither is present the service
14
+ * falls back to the same "sdk_not_initialized" failure state the desktop app
15
+ * emits when the SDK cannot run.
16
+ *
17
+ * Loading and calling the SDK is safe: `configure()` is local-only, and
18
+ * `fetchDeviceToken()` performs the SDK's normal network exchange with the
19
+ * Turing endpoint (the same call the desktop client makes). On platforms
20
+ * without native support (non-win32/darwin) the service degrades to the
21
+ * fallback.
22
+ */
23
+ import { existsSync } from "node:fs";
24
+ import path from "node:path";
25
+ import { createHash } from "node:crypto";
26
+ import { log } from "../../../observability/logger";
27
+ import { resolvePackagePath } from "../../../config/package-paths";
28
+
29
+ /** Channel assigned to the WorkBuddy desktop product (see cli/product.json). */
30
+ export const TURING_CHANNEL_ID = 400111;
31
+
32
+ /** Per-provider SDK configuration (payload identity sent to Turing). */
33
+ export interface TuringSdkConfig {
34
+ readonly channelId: number;
35
+ readonly productName: string;
36
+ readonly productVersion: string;
37
+ }
38
+
39
+ /** Canonical config for the WorkBuddy profile. */
40
+ export const WORKBUDDY_TURING_CONFIG: TuringSdkConfig = {
41
+ channelId: TURING_CHANNEL_ID,
42
+ productName: "WorkBuddy AI",
43
+ productVersion: "5.8.4",
44
+ };
45
+
46
+ /** Canonical config for the CodeBuddy profile (same SDK, distinct payload). */
47
+ export const CODEBUDDY_TURING_CONFIG: TuringSdkConfig = {
48
+ channelId: TURING_CHANNEL_ID,
49
+ productName: "CodeBuddy",
50
+ productVersion: "4.12.1",
51
+ };
52
+
53
+ /** Mirror the desktop client's service defaults (main-bootstrap.js). */
54
+ const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
55
+ const DEFAULT_REFRESH_INTERVAL_MS = 5 * 60_000;
56
+ const DEFAULT_RETRY_BASE_DELAY_MS = 30_000;
57
+ const DEFAULT_RETRY_MAX_DELAY_MS = 5 * 60_000;
58
+ const MAX_BACKOFF_LEVEL = 20;
59
+
60
+ /**
61
+ * Resolve the directory holding the Turing SDK native files, or null.
62
+ *
63
+ * Priority: `CARTETHYIA_TURING_SDK_PATH` override → bundled copy in
64
+ * `vendor/turing-sdk/build/Release/` under the package root (works both in
65
+ * source and in the compiled binary, where `CARTETHYIA_PACKAGE_ROOT` points at
66
+ * `/app`).
67
+ */
68
+ export function resolveTuringSdkRoot(): string | null {
69
+ const env = process.env["CARTETHYIA_TURING_SDK_PATH"];
70
+ if (env !== undefined && env.trim().length > 0) return env.trim();
71
+ // Bundled SDK (committed in vendor/).
72
+ const bundled = resolvePackagePath("vendor", "turing-sdk", "build", "Release");
73
+ if (existsSync(path.join(bundled, "turing_sdk.node"))) return bundled;
74
+ return null;
75
+ }
76
+
77
+ /** Pure header decision, matching the desktop app's `token || error` contract. */
78
+ export function turingHeadersForRequest(args: {
79
+ token?: string;
80
+ error?: string;
81
+ cliIdentity?: boolean;
82
+ }): Record<string, string> {
83
+ if (args.cliIdentity) return {};
84
+ const token = args.token?.trim();
85
+ if (token !== undefined && token.length > 0) return { "x-device-token": token };
86
+ const error = args.error?.trim();
87
+ if (error !== undefined && error.length > 0) return { "x-device-token-error": error };
88
+ return { "x-device-token-error": "sdk_not_initialized" };
89
+ }
90
+
91
+ /** Stable identifier for logs (never the token itself). */
92
+ function logDeviceFingerprint(): string {
93
+ const parts = [process.platform, process.arch, process.env["COMPUTERNAME"] ?? process.env["HOSTNAME"] ?? ""];
94
+ return createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 12);
95
+ }
96
+
97
+ export class TuringDeviceTokenService {
98
+ private binding: { configure(...a: unknown[]): unknown; fetchDeviceToken(o: unknown): unknown } | null = null;
99
+ private loadAttempted = false;
100
+ private configured = false;
101
+
102
+ private cachedToken: string | undefined;
103
+ private cachedError: string | undefined;
104
+ private cachedAt = 0;
105
+ private consecutiveFailures = 0;
106
+ private nextRetryAt = 0;
107
+ private inflight: Promise<string> | undefined;
108
+
109
+ constructor(private readonly config: TuringSdkConfig = WORKBUDDY_TURING_CONFIG) {}
110
+
111
+ private ensureLoaded(): boolean {
112
+ if (this.loadAttempted) return this.configured;
113
+ this.loadAttempted = true;
114
+ const root = resolveTuringSdkRoot();
115
+ if (root === null) {
116
+ log.warn("[turing-sdk] no SDK root (env unset & vendor missing); device token fallback active");
117
+ return false;
118
+ }
119
+ const nodePath = path.join(root, "turing_sdk.node");
120
+ if (!existsSync(nodePath)) {
121
+ log.warn("[turing-sdk] turing_sdk.node not found at " + nodePath);
122
+ return false;
123
+ }
124
+ try {
125
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
126
+ const loaded: unknown = require(nodePath);
127
+ if (loaded === null || typeof loaded !== "object" || typeof (loaded as Record<string, unknown>)["configure"] !== "function") {
128
+ log.warn("[turing-sdk] native addon loaded but exposes no configure()");
129
+ return false;
130
+ }
131
+ const binding = loaded as NonNullable<TuringDeviceTokenService["binding"]>;
132
+ binding.configure(this.config.channelId, this.config.productName, this.config.productVersion);
133
+ this.binding = binding;
134
+ this.configured = true;
135
+ log.info(
136
+ "[turing-sdk] native SDK configured (channel " + this.config.channelId +
137
+ ", product " + this.config.productName + " " + this.config.productVersion +
138
+ ", " + logDeviceFingerprint() + ")",
139
+ );
140
+ } catch (error) {
141
+ log.warn("[turing-sdk] failed to load/configure native SDK: " + (error instanceof Error ? error.message : String(error)));
142
+ this.binding = null;
143
+ }
144
+ return this.configured;
145
+ }
146
+
147
+ /** Returns a real token when the SDK can produce one; undefined otherwise. */
148
+ async fetchDeviceToken(): Promise<string | undefined> {
149
+ if (!this.ensureLoaded()) return undefined;
150
+ const now = Date.now();
151
+ if (this.inflight) return this.inflight;
152
+ const task = this.performFetch().finally(() => {
153
+ this.inflight = undefined;
154
+ });
155
+ this.inflight = task;
156
+ try {
157
+ const token = await task;
158
+ this.cachedToken = token;
159
+ this.cachedError = undefined;
160
+ this.cachedAt = now;
161
+ this.consecutiveFailures = 0;
162
+ this.nextRetryAt = 0;
163
+ return token;
164
+ } catch (error) {
165
+ const message = error instanceof Error ? error.message : String(error);
166
+ this.cachedError = message.replace(/[^\x20-\x7E]/g, "").slice(0, 64);
167
+ this.consecutiveFailures += 1;
168
+ const level = Math.min(Math.max(0, this.consecutiveFailures - 1), MAX_BACKOFF_LEVEL);
169
+ const delay = Math.min(DEFAULT_RETRY_BASE_DELAY_MS * 2 ** level, DEFAULT_RETRY_MAX_DELAY_MS);
170
+ this.nextRetryAt = Date.now() + delay;
171
+ return undefined;
172
+ }
173
+ }
174
+
175
+ private async performFetch(): Promise<string> {
176
+ const binding = this.binding;
177
+ if (!binding) throw new Error("sdk_not_initialized");
178
+ const timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
179
+ const promise = binding.fetchDeviceToken({
180
+ usingCachedMessage: true,
181
+ includesOutdatedMessage: false,
182
+ includesDeviceInfo: true,
183
+ timeoutMs,
184
+ });
185
+ const token = await this.withTimeout(Promise.resolve(promise) as Promise<string>, timeoutMs);
186
+ const text = typeof token === "string" ? token : String(token ?? "");
187
+ if (text.trim().length === 0) throw new Error("Turing SDK returned an empty device token");
188
+ return text.trim();
189
+ }
190
+
191
+ private async withTimeout(promise: Promise<string>, ms: number): Promise<string> {
192
+ let timer: ReturnType<typeof setTimeout> | undefined;
193
+ try {
194
+ return await Promise.race([
195
+ promise,
196
+ new Promise<string>((_, reject) => {
197
+ timer = setTimeout(() => reject(new Error("Turing SDK device token request timed out after " + ms + "ms")), ms);
198
+ timer.unref?.();
199
+ }),
200
+ ]);
201
+ } finally {
202
+ if (timer !== undefined) clearTimeout(timer);
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Headers for one buddy request. Ensures a cached token exists (refreshing
208
+ * in the background when stale), then returns the desktop `token || error`
209
+ * contract.
210
+ */
211
+ async headersForRequest(cliIdentity?: boolean, requested?: TuringSdkConfig): Promise<Record<string, string>> {
212
+ // The Turing SDK rejects a channelId change after initialization. Keep the
213
+ // first config that actually configures the binding; later conflicting
214
+ // requests are warned once and keep serving the active token.
215
+ if (requested !== undefined) {
216
+ const activeOrMine = this.configured ? this.config : requested;
217
+ if (this.configured && (requested.channelId !== this.config.channelId || requested.productName !== this.config.productName)) {
218
+ log.warn("[turing-sdk] ignoring conflicting SDK config (channel " + requested.channelId + ", " + requested.productName + "); active is channel " + this.config.channelId + ", " + this.config.productName);
219
+ } else {
220
+ void activeOrMine;
221
+ }
222
+ }
223
+ let token: string | undefined = this.cachedToken;
224
+ let error: string | undefined = this.cachedError;
225
+ const now = Date.now();
226
+ if (token === undefined) {
227
+ if (now < this.nextRetryAt) {
228
+ // Keep whatever we had; still within backoff.
229
+ } else {
230
+ token = await this.fetchDeviceToken();
231
+ if (token === undefined) error = this.cachedError;
232
+ }
233
+ } else if (now - this.cachedAt >= DEFAULT_REFRESH_INTERVAL_MS) {
234
+ // Stale: refresh opportunistically but keep serving the cached token.
235
+ void this.fetchDeviceToken();
236
+ }
237
+ return turingHeadersForRequest({
238
+ ...(token === undefined ? {} : { token }),
239
+ ...(error === undefined ? {} : { error }),
240
+ ...(cliIdentity === undefined ? {} : { cliIdentity }),
241
+ });
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Process-wide services keyed by their SDK config (channel/product). Each
247
+ * provider identity gets its own service so its payload (productName,
248
+ * productVersion) is used when the SDK is first configured. The underlying
249
+ * native addon is shared, but configure() runs per instance.
250
+ */
251
+ const servicesByConfig = new Map<string, TuringDeviceTokenService>();
252
+ export function getTuringDeviceTokenService(
253
+ config: TuringSdkConfig = WORKBUDDY_TURING_CONFIG,
254
+ ): TuringDeviceTokenService {
255
+ const key = `${config.channelId}:${config.productName}`;
256
+ let service = servicesByConfig.get(key);
257
+ if (service === undefined) {
258
+ service = new TuringDeviceTokenService(config);
259
+ servicesByConfig.set(key, service);
260
+ }
261
+ return service;
262
+ }
@@ -34,6 +34,7 @@ import { BUDDY_INTL_MODELS_PATH, fetchBuddyDirectoryModels } from "../buddy-disc
34
34
  import {
35
35
  applyBuddySystemPrompt,
36
36
  buddyPrePayloadCommon,
37
+ injectBuddyParallelToolCalls,
37
38
  normalizeBuddyToolNames,
38
39
  } from "../buddy-chat-shared";
39
40
 
@@ -141,6 +142,7 @@ export function workbuddyPrePayload(
141
142
  restoreHistoricalToolDefinitions(payload, normalizedMessages);
142
143
  }
143
144
  normalizeBuddyToolNames(payload);
145
+ injectBuddyParallelToolCalls(payload, request);
144
146
  void request;
145
147
  }
146
148
 
@@ -8,12 +8,18 @@ import type { ProviderDispatchContext, ProviderDispatchTarget, ProviderId } from
8
8
  import { resolveInboundSessionId } from "../../../operations/session-resolution";
9
9
  import { buddyConversationHeaders } from "../buddy-conversation-headers";
10
10
  import { buddyCliHeaders, buddyCliTraceHeaders } from "../buddy-cli-headers";
11
- import { buddyEnterprise, buddyUserId, resolveBuddyIdentity } from "../buddy-identity";
11
+ import { buddyEnterprise, buddyUserId, resolveBuddyDeviceBinding, resolveBuddyIdentity } from "../buddy-identity";
12
12
  import {
13
13
  buildWorkBuddyUserAgent,
14
14
  getWorkBuddyClientVersion,
15
15
  getWorkBuddyCliVersion,
16
16
  } from "../../../operations/client-versions";
17
+ import {
18
+ getTuringDeviceTokenService,
19
+ resolveTuringSdkRoot,
20
+ type TuringSdkConfig,
21
+ WORKBUDDY_TURING_CONFIG,
22
+ } from "../turing-device-token";
17
23
 
18
24
  /**
19
25
  * WorkBuddy serves chat from a versioned path on a base URL that carries no
@@ -140,8 +146,17 @@ export async function workbuddyHeaders(
140
146
  headers["x-machine-id"] = deriveAccountStableId(accountId, "machine");
141
147
  headers["x-session-id"] = deriveAccountStableId(accountId, "session");
142
148
  }
143
- const deviceToken = readBuddyDeviceToken(context);
144
- if (deviceToken !== undefined) headers["x-device-token"] = deviceToken;
149
+ // Per-account device binding: distinct device identity per account (see
150
+ // resolveBuddyDeviceBinding). Sent as the desktop's X-Device-Id-List.
151
+ const binding = resolveBuddyDeviceBinding(context);
152
+ if (binding !== undefined) {
153
+ headers["x-device-id-list"] = JSON.stringify({ qimei36: binding });
154
+ }
155
+ Object.assign(headers, await buddyDeviceTokenHeaders({
156
+ realm: domain.endsWith(".cn") ? "cn" : "intl",
157
+ context,
158
+ cliIdentity,
159
+ }));
145
160
  return headers;
146
161
  }
147
162
 
@@ -165,6 +180,44 @@ export function readBuddyDeviceToken(context?: ProviderDispatchContext): string
165
180
  return readDeviceTokenFile();
166
181
  }
167
182
 
183
+ /**
184
+ * The reference `X-Device-Token` pair, per the official desktop app.
185
+ *
186
+ * The desktop client (WorkBuddyAI, channel 400111) always presents one of the
187
+ * two on chat traffic — a real token from the TuringShield SDK, or an
188
+ * `X-Device-Token-Error` when the SDK cannot produce one. It never omits both,
189
+ * and it sends the header on every realm (Intl and CN alike). The SDK is a
190
+ * proprietary native addon; when it is not configured we fall back to a
191
+ * captured token (auth_state / `CARTETHYIA_DEVICE_TOKEN_FILE`) and only then
192
+ * to the official failure state.
193
+ *
194
+ * - `CARTETHYIA_TURING_SDK_PATH` → real token via `TuringDeviceTokenService`.
195
+ * - Legacy token (auth_state/file) → used when the SDK is unavailable.
196
+ * - CLI identity never carries a device token (the CLI has no device).
197
+ */
198
+ export async function buddyDeviceTokenHeaders(args: {
199
+ realm: "intl" | "cn";
200
+ context: ProviderDispatchContext | undefined;
201
+ cliIdentity: boolean | undefined;
202
+ turingConfig?: TuringSdkConfig;
203
+ }): Promise<Record<string, string>> {
204
+ const { context, cliIdentity, turingConfig } = args;
205
+ void args.realm; // the desktop client sends the token on both realms
206
+ if (resolveBuddyIdentity(context, cliIdentity) === "cli") return {};
207
+ // Native SDK first (real token), when configured.
208
+ if (resolveTuringSdkRoot() !== null) {
209
+ const sdkHeaders = await getTuringDeviceTokenService(turingConfig ?? WORKBUDDY_TURING_CONFIG).headersForRequest(false);
210
+ if (sdkHeaders["x-device-token"] !== undefined) return sdkHeaders;
211
+ // SDK configured but could not produce a token: mirror its failure state,
212
+ // like the desktop client does — do not fall back to a stale file token.
213
+ return sdkHeaders;
214
+ }
215
+ // No SDK on this deployment: a captured token is better than nothing.
216
+ const token = readBuddyDeviceToken(context);
217
+ if (token !== undefined) return { "x-device-token": token };
218
+ return { "x-device-token-error": "sdk_not_initialized" };
219
+ }
220
+
168
221
  function readDeviceTokenFile(): string | undefined {
169
222
  const now = Date.now();
170
223
  if (deviceTokenFileCache !== undefined && now - deviceTokenFileCache.readAt < DEVICE_TOKEN_FILE_TTL_MS) {
@@ -49,7 +49,7 @@ import { getClaudeCliVersion, adoptClaudeCliVersion, getClaudeSdkVersion } from
49
49
  import { adoptRequiredVersion } from "../../operations/client-version-resolver";
50
50
  import { getOrCreateInstallId } from "../shared/install-id";
51
51
  import { canonicalToClaudeMessagesPayload } from "../../../protocol/request/messages";
52
- import { applyParamQuirks } from "../../../transport/translation/quirks";
52
+ import { applyParamQuirks } from "../../../protocol/translation/quirks";
53
53
  import { CLAUDE_BILLING_HEADER_PREFIX, endpointUrl } from "../../../protocol/codecs";
54
54
  import { sendClaudeMessagesRequest } from "../../../protocol/upstream/send-claude-messages";
55
55
  import {
@@ -25,7 +25,7 @@ import type { CanonicalEvent, CanonicalRequest } from "../../../protocol/canonic
25
25
  import { GatewayError } from "../../../protocol/gateway-error";
26
26
  import { buildClaudeMessagesRequest, filterClaudeCustomHeaders } from "../shared/claude-messages";
27
27
  import { sendClaudeMessagesRequest } from "../../../protocol/upstream/send-claude-messages";
28
- import { applyParamQuirks } from "../../../transport/translation/quirks";
28
+ import { applyParamQuirks } from "../../../protocol/translation/quirks";
29
29
  import { decodeCloudflareAiGatewayCredential } from "../../../security/credential-codec";
30
30
 
31
31
  export const CLOUDFLAREAI_PROVIDER_ID = "cloudflareai" as const;
@@ -7,11 +7,11 @@
7
7
  import { GatewayError } from "../../../protocol/gateway-error";
8
8
  import {
9
9
  classifyUpstreamError,
10
- parseRetryAfter,
11
10
  extractUpstreamMessage,
12
11
  upstreamErrorIdentifier,
13
12
  upstreamRequestId,
14
13
  } from "../../../transport/failure-policy";
14
+ import { parseRetryAfter } from "../../../protocol/upstream-parsing";
15
15
  import { tryParseJsonObject } from "../../../protocol/codecs";
16
16
 
17
17
  function toNumberOrUndefined(value: string | null): number | undefined {
@@ -33,9 +33,9 @@ import { isRecord, joinUrl } from "../../../protocol/codecs";
33
33
  import { postUpstreamJson } from "../../../protocol/upstream/post-json";
34
34
  import {
35
35
  MIN_RETRY_BUDGET_MS,
36
- parseUpstreamBackoff,
37
36
  sleep as defaultSleep,
38
37
  } from "../../../transport/failure-policy";
38
+ import { parseUpstreamBackoff } from "../../../protocol/upstream-parsing";
39
39
 
40
40
  /**
41
41
  * Sleep used by the 429 capacity-wait loop, overridable in tests.
@@ -49,7 +49,7 @@ import { createUpstreamDeadlineLifecycle, abortGatewayError } from "../../operat
49
49
  import { mapUpstreamHttpError } from "../../../transport/failure-policy";
50
50
  import { buildClaudeMessagesRequest, filterClaudeCustomHeaders } from "../shared/claude-messages";
51
51
  import { sendClaudeMessagesRequest } from "../../../protocol/upstream/send-claude-messages";
52
- import { applyParamQuirks } from "../../../transport/translation/quirks";
52
+ import { applyParamQuirks } from "../../../protocol/translation/quirks";
53
53
  import { createApiKeyAdapter } from "../../configured-provider";
54
54
  import type { ApiKeyProviderSpec } from "../../configured-provider";
55
55
  import { decodeFactoryDroidCredential } from "../../../security/credential-codec";
@@ -25,7 +25,7 @@ import { postUpstreamJson } from "../../../protocol/upstream/post-json";
25
25
  import { providerBaseUrl } from "../../provider-metadata";
26
26
  import { getOrCreateInstallId } from "../shared/install-id";
27
27
  import { resolveRedisClient } from "../../../persistence/redis";
28
- import { applyParamQuirks } from "../../../transport/translation/quirks";
28
+ import { applyParamQuirks } from "../../../protocol/translation/quirks";
29
29
  import { resolveInboundSessionId } from "../../operations/session-resolution";
30
30
  import {
31
31
  GITHUB_REQUEST_HEADERS,
@@ -20,7 +20,7 @@ import { providerBaseUrl } from "../../provider-metadata";
20
20
  import { boundedUpstreamArray, boundedUpstreamNumber, sanitizeUpstreamLabel } from "../../provider-sanitize";
21
21
  import { isRecord } from "../../../protocol/codecs";
22
22
  import { modelsDevCatalog } from "../../discovery/models-dev-catalog";
23
- import type { ReasoningEffortLevel } from "../../../transport/translation/thinking";
23
+ import type { ReasoningEffortLevel } from "../../../protocol/translation/thinking";
24
24
  import type { FetchLike } from "../../quota/quota-contracts";
25
25
  import { resolveInboundSessionId, resolvePromptCacheKey } from "../../operations/session-resolution";
26
26
  import {
@@ -0,0 +1,91 @@
1
+ import type { ApiKeyProviderSpec } from "../../configured-provider";
2
+ import { fetchOpenAICompatibleModels } from "../../discovery/openai-model-discovery";
3
+ import { defineModel, type ModelDefinition } from "../../provider-model-definition";
4
+
5
+ const INCEPTION_PROVIDER_ID = "inception" as const;
6
+
7
+ /**
8
+ * Inception Labs (Mercury) — bearer-authenticated OpenAI-compatible chat.
9
+ *
10
+ * The upstream is a plain `POST /v1/chat/completions` service (SSE streaming,
11
+ * tool calling, structured outputs, `reasoning_effort`). Base URL comes from
12
+ * `BUNDLED_PROVIDER_METADATA`; the chat path is declared below the `/v1`
13
+ * root, exactly like every other version-rooted OpenAI-compatible provider.
14
+ */
15
+ export const INCEPTION_SPEC: ApiKeyProviderSpec = {
16
+ provider_id: INCEPTION_PROVIDER_ID,
17
+ endpoint_paths_by_wire_family: {},
18
+ };
19
+
20
+ /**
21
+ * Offline floor: the Mercury chat model this provider serves, pinned to the
22
+ * free-tier allowance (100M free tokens at signup).
23
+ *
24
+ * The roster intentionally carries only `mercury-3` — the operator's choice.
25
+ * Mercury Voice is enterprise-gated, Mercury Decide is a native decisions
26
+ * endpoint, and Mercury Edit 2 is FIM/edit; none of them is a chat model, and
27
+ * a chat wire would not serve them.
28
+ *
29
+ * `freeTier` is the catalog flag the console groups on; `free: true` zeroes
30
+ * the cost the way every other free-allowance provider models its rows — the
31
+ * allowance does not bill, so the row must not show a price. Limits are
32
+ * mercury-3's own chat-completions numbers: 1M context, 131072 max output,
33
+ * reasoning (instant/low/medium/high), tool calling.
34
+ */
35
+ export const INCEPTION_MODELS: readonly ModelDefinition[] = [
36
+ defineModel({
37
+ id: "mercury-3",
38
+ wireFamily: "chat",
39
+ endpoint: "/chat/completions",
40
+ ctx: 1_000_000,
41
+ out: 131_072,
42
+ reasoning: true,
43
+ toolCall: true,
44
+ free: true,
45
+ freeTier: true,
46
+ }),
47
+ ];
48
+
49
+ /**
50
+ * Live `/v1/models` listing, pinned to the free tier.
51
+ *
52
+ * The listing requires the API key (matches the chat endpoint's bearer), so
53
+ * the credential is forwarded when present; an unauthenticated fetch fails
54
+ * cleanly and the static roster stays authoritative.
55
+ *
56
+ * Every fetched row is stamped `freeTier` and zero-priced, the same rule the
57
+ * static roster declares: this provider is pinned to its free allowance, so a
58
+ * model upstream adds must not land in the billed tail.
59
+ *
60
+ * A fetched id that the static roster already knows keeps the roster row
61
+ * whole, not the listing's fields: upstream publishes `max_output_length` and
62
+ * no `reasoning`/`tool_call` keys, so the shared OpenAI-compatible reader
63
+ * would otherwise overwrite mercury-3's advertised 131072 output and reasoning
64
+ * flag with its 64k/not-reasoning defaults. The roster is the canonical
65
+ * metadata for the models we know; the listing only adds genuinely new ids.
66
+ */
67
+ export async function discoverInceptionFreeModels(options: {
68
+ readonly baseUrl: string;
69
+ readonly credential?: string;
70
+ readonly fetcher?: typeof fetch;
71
+ }): Promise<readonly ModelDefinition[] | null> {
72
+ const listed = await fetchOpenAICompatibleModels({
73
+ baseUrl: options.baseUrl,
74
+ providerId: "inception",
75
+ ...(typeof options.credential === "string" && options.credential.trim().length > 0
76
+ ? { headers: { authorization: `Bearer ${options.credential}` } }
77
+ : {}),
78
+ ...(options.fetcher === undefined ? {} : { fetcher: options.fetcher }),
79
+ });
80
+ if (listed === null) return null;
81
+ const known = new Map(INCEPTION_MODELS.map((model) => [model.modelId, model]));
82
+ return listed.map((model) => {
83
+ const roster = known.get(model.modelId);
84
+ if (roster !== undefined) return roster;
85
+ return {
86
+ ...model,
87
+ freeTier: true,
88
+ cost: { input: 0, output: 0, pricing_model: "pay-per-use" },
89
+ };
90
+ });
91
+ }