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
@@ -192,7 +192,7 @@ export class ProviderProbingService {
192
192
  const providerWireRow = await this.loadProviderWireRow(providerId);
193
193
  const requiresAccount = providerWireRow?.requiresAccount ?? true;
194
194
 
195
- const { wireFamily, serviceKind, endpointPath, sourceSurface, capabilityProfile } = await resolveProbeTarget({
195
+ const { wireFamily, serviceKind, endpointPath, sourceSurface, capabilityProfile, outputLimit } = await resolveProbeTarget({
196
196
  db: this.db,
197
197
  bundledModelCatalog: this.bundledModelCatalog,
198
198
  defaultEndpoints: this.defaultEndpoints,
@@ -253,6 +253,9 @@ export class ProviderProbingService {
253
253
  // over capabilities the route actually has (`prompt_caching`, `tools`,
254
254
  // `reasoning`), reporting a healthy provider as broken.
255
255
  capabilities: capabilityProfile,
256
+ // The model row's own output ceiling, so a probe with a max-output
257
+ // ceiling (OpenCode free) shapes with the model's limit, not a guess.
258
+ ...(outputLimit == null ? {} : { output_limit: outputLimit }),
256
259
  };
257
260
 
258
261
  const probeSignal = AbortSignal.timeout(30_000);
@@ -17,7 +17,7 @@ import type { CanonicalEvent, CanonicalRequest } from "../../protocol/canonical-
17
17
  import { GatewayError } from "../../protocol/gateway-error";
18
18
  import { buildClaudeMessagesRequest, filterClaudeCustomHeaders } from "./shared/claude-messages";
19
19
  import { sendClaudeMessagesRequest } from "../../protocol/upstream/send-claude-messages";
20
- import { applyParamQuirks } from "../../transport/translation/quirks";
20
+ import { applyParamQuirks } from "../../protocol/translation/quirks";
21
21
  import type { ProviderDispatchTarget, ProviderAdapter, ProviderDispatchContext, ProviderId } from "../provider-contracts";
22
22
  import { providerBaseUrl } from "../provider-metadata";
23
23
 
@@ -42,7 +42,6 @@ export const BUDDY_INTL_EFFORTS: Readonly<Record<string, readonly string[]>> = {
42
42
  "glm-5.2": ["high", "xhigh"],
43
43
  "glm-5.3": ["low", "high", "max"],
44
44
  hy3: ["low", "high"],
45
- "hy3-preview": ["low", "high"],
46
45
  "hy4-preview-f": ["high"],
47
46
  };
48
47
  export const BUDDY_CN_EFFORTS: Readonly<Record<string, readonly string[]>> = {
@@ -54,7 +53,6 @@ export const BUDDY_CN_EFFORTS: Readonly<Record<string, readonly string[]>> = {
54
53
  "glm-5.3": ["low", "high", "max"],
55
54
  "glm-5.3-flash": ["low", "high", "max"],
56
55
  hy3: ["low", "high"],
57
- "hy3-preview": ["low", "high"],
58
56
  "hy4-preview": ["high"],
59
57
  };
60
58
 
@@ -336,7 +334,7 @@ export const BUDDY_SHARED_RAW: readonly BuddyRawEntry[] = [
336
334
  ["glm-5.1", "GLM-5.1", true, false, 200_000, 131_072],
337
335
  ["glm-5.2", "GLM-5.2", true, true, 1_000_000, 48_000],
338
336
  ["glm-5.3", "GLM-5.3", true, true, 1_000_000, 48_000],
339
- ["glm-5.3-flash", "GLM-5.3 Flash", true, true, 1_000_000, 32_000],
337
+ ["glm-5.3-flash", "GLM-5.3 Flash", true, false, 1_000_000, 32_000],
340
338
  ["glm-5v-turbo", "GLM-5V Turbo", true, true, 200_000, 131_072],
341
339
  ["gpt-5.3-codex", "GPT-5.3 Codex", true, true, 400_000, 128_000],
342
340
  ["gpt-5.4", "GPT-5.4", true, true, 272_000, 72_000],
@@ -351,7 +349,6 @@ export const BUDDY_SHARED_RAW: readonly BuddyRawEntry[] = [
351
349
  ["grok-4.6", "Grok 4.6", true, true, 500_000, 500_000],
352
350
  ["grok-4.7", "Grok 4.7", true, true, 500_000, 128_000],
353
351
  ["hy3", "Hy3", true, true, 192_000, 64_000, true],
354
- ["hy3-preview", "Hy3 Preview", true, false, 262_144, 64_000],
355
352
  ["hy4-preview", "Hy4 Preview", true, true, 1_000_000, 64_000, true],
356
353
  ["hy4-preview-f", "Hy4 Preview F", true, true, 1_000_000, 64_000, true],
357
354
  ["kimi-k2.5", "Kimi K2.5", true, false, 164_000, 262_144],
@@ -21,14 +21,14 @@ import { createHash } from "node:crypto";
21
21
  import { completeRequiredSchema } from "../../../protocol/codecs";
22
22
  import type { CanonicalRequest } from "../../../protocol/canonical-model";
23
23
  import { isRecord } from "../../../protocol/codecs";
24
- import { applyDeepSeekReasoning, applyOpenAIReasoning, backfillDeepSeekReasoningContent, clampedEffortOutputFloor } from "../../provider-reasoning";
24
+ import { applyDeepSeekReasoning, applyOpenAIReasoning, backfillDeepSeekReasoningContent, clampedEffortOutputFloor } from "../../../protocol/reasoning";
25
25
  import { BUDDY_CN_EFFORTS, BUDDY_INTL_EFFORTS, BUDDY_SHARED_RAW } from "./buddy-catalog-shared";
26
26
  // Imported for the CN roster only: a CN-only id has no row in the shared
27
27
  // table, and without one `raiseOutputForRaisedEffort` silently no-ops, so a
28
28
  // raised effort still answers inside a budget sized for the lower one. It is
29
29
  // read inside the function below — never at module init — so the cycle with
30
30
  // cn-provider (which imports this module) resolves before any call.
31
- import { CODEBUDDY_CN_RAW } from "./codebuddy/cn-provider";
31
+ import { CODEBUDDY_CN_RAW } from "./codebuddy/cn-catalog";
32
32
  import { buddyAccountUid } from "./buddy-oauth-shared";
33
33
  import type { ProviderDispatchContext } from "../../provider-contracts";
34
34
 
@@ -987,13 +987,146 @@ export function ensureBuddyLeadingSystem(
987
987
  * (`11151`), then guarantee a leading system turn (`11128`). The system text
988
988
  * is the variant's own prompt, so each pre-payload hook passes its prompt in.
989
989
  */
990
- function finalizeBuddyMessages(
990
+ /**
991
+ * Longest message text that gets an automatic ephemeral prompt-cache marker,
992
+ * mirroring the reference gateway (`MIN_AUTO_CACHE_TEXT_LENGTH`).
993
+ */
994
+ const MIN_AUTO_CACHE_TEXT_LENGTH = 1024;
995
+
996
+ /**
997
+ * Marks one message's text content cacheable in place: the system turn and the
998
+ * last user turn are the long, stable ones a prefix cache serves best. A
999
+ * string message becomes a typed text block; a text part inside an array gets
1000
+ * `cache_control: {type: "ephemeral"}`. Only the first qualifying text part of
1001
+ * a message is marked, like the reference.
1002
+ */
1003
+ function markCacheableTextContent(message: Record<string, unknown>): void {
1004
+ const content = message["content"];
1005
+ if (typeof content === "string") {
1006
+ if (content.trim().length >= MIN_AUTO_CACHE_TEXT_LENGTH) {
1007
+ message["content"] = [
1008
+ { type: "text", text: content, cache_control: { type: "ephemeral" } },
1009
+ ];
1010
+ }
1011
+ return;
1012
+ }
1013
+ if (!Array.isArray(content)) return;
1014
+ for (let index = 0; index < content.length; index += 1) {
1015
+ const part = content[index];
1016
+ if (!isRecord(part) || part["type"] !== "text") continue;
1017
+ const text = typeof part["text"] === "string" ? part["text"] : "";
1018
+ if (text.trim().length >= MIN_AUTO_CACHE_TEXT_LENGTH && part["cache_control"] === undefined) {
1019
+ content[index] = { ...part, cache_control: { type: "ephemeral" } };
1020
+ return;
1021
+ }
1022
+ }
1023
+ }
1024
+
1025
+ /**
1026
+ * Auto-injects ephemeral prompt-cache markers on the system turn and the last
1027
+ * user turn when the caller declared none. Mirrors `applyPromptCacheControl` in
1028
+ * the reference gateway: an explicit `cache_control` anywhere is respected and
1029
+ * disables the auto-pass so the caller's own caching strategy wins.
1030
+ */
1031
+ export function applyBuddyAutoPromptCache(messages: Array<Record<string, unknown>>): void {
1032
+ const hasExplicit = messages.some((message) =>
1033
+ Array.isArray(message["content"]) &&
1034
+ (message["content"] as Array<Record<string, unknown>>).some(
1035
+ (part) => isRecord(part) && part["cache_control"] !== undefined,
1036
+ ),
1037
+ );
1038
+ if (hasExplicit) return;
1039
+ if (messages.length === 0) return;
1040
+ const first = messages[0];
1041
+ if (isRecord(first) && first["role"] === "system") markCacheableTextContent(first);
1042
+ let lastUser = -1;
1043
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
1044
+ if (isRecord(messages[index]) && messages[index]!["role"] === "user") {
1045
+ lastUser = index;
1046
+ break;
1047
+ }
1048
+ }
1049
+ if (lastUser >= 0) markCacheableTextContent(messages[lastUser]!);
1050
+ }
1051
+
1052
+ /**
1053
+ * Strips the `content` key from assistant tool_calls-only turns.
1054
+ *
1055
+ * Mirrors the reference gateway's OpenAI transcript: an assistant turn that
1056
+ * only asks for tools carries `tool_calls` and no content at all (Go's
1057
+ * `omitzero`-style SDKs omit the key rather than send `null`/`""`). Keeping
1058
+ * an empty string content invites the upstream to fall back to its prose/DSML
1059
+ * channel; omitting the key keeps it on the structured `tool_calls` channel.
1060
+ */
1061
+ export function omitBuddyAssistantToolCallContent(messages: Array<Record<string, unknown>>): void {
1062
+ for (const message of messages) {
1063
+ if (!isRecord(message) || message["role"] !== "assistant") continue;
1064
+ const calls = message["tool_calls"];
1065
+ if (!Array.isArray(calls) || calls.length === 0) continue;
1066
+ const content = message["content"];
1067
+ if (typeof content === "string" && content.trim().length === 0) {
1068
+ delete message["content"];
1069
+ } else if (Array.isArray(content) && content.length === 0) {
1070
+ delete message["content"];
1071
+ }
1072
+ }
1073
+ }
1074
+
1075
+ /**
1076
+ * Forwards the caller's parallel tool-call intent onto the buddy wire.
1077
+ *
1078
+ * The canonical control is `generation_controls.parallel_tool_calls`; the
1079
+ * reference gateway always passes it through so upstream keeps the structured
1080
+ * tool channel, and the same is applied here.
1081
+ */
1082
+ export function injectBuddyParallelToolCalls(
1083
+ payload: Record<string, unknown>,
1084
+ request: CanonicalRequest,
1085
+ ): void {
1086
+ const parallel = request.generation_controls?.parallel_tool_calls;
1087
+ if (parallel !== undefined) payload["parallel_tool_calls"] = parallel;
1088
+ }
1089
+
1090
+ export interface BuddyMessageFinalizeOptions {
1091
+ /** The fixed leading system prompt installed on the wire. */
1092
+ readonly leadPrompt: string;
1093
+ /** Coalesce consecutive user turns (default true). */
1094
+ readonly coalesceConsecutiveUser?: boolean;
1095
+ /** Drop empty-content turns the upstream rejects (default true). */
1096
+ readonly dropEmpty?: boolean;
1097
+ /** Guarantee the wire opens with a system turn (default true). */
1098
+ readonly ensureLeadingSystem?: boolean;
1099
+ /** Omit content on assistant tool_calls-only turns (default true). */
1100
+ readonly omitToolCallContent?: boolean;
1101
+ /** Auto-inject ephemeral prompt-cache markers (default true). */
1102
+ readonly autoPromptCache?: boolean;
1103
+ }
1104
+
1105
+ /**
1106
+ * The single authoritative buddy message-shape pipeline. Every variant runs
1107
+ * the same sequence — coalesce, drop empty turns, guarantee the leading system
1108
+ * turn, omit content on tool_calls-only assistant turns, then auto-cache — so
1109
+ * the wire shape cannot drift between CodeBuddy intl, CN, and WorkBuddy.
1110
+ */
1111
+ export function finalizeBuddyMessages(
991
1112
  messages: Array<Record<string, unknown>>,
992
- systemPrompt: string,
1113
+ options: BuddyMessageFinalizeOptions,
993
1114
  ): void {
994
- coalesceConsecutiveUserMessages(messages);
995
- dropEmptyBuddyMessages(messages);
996
- ensureBuddyLeadingSystem(messages, systemPrompt);
1115
+ if (options.coalesceConsecutiveUser !== false) {
1116
+ coalesceConsecutiveUserMessages(messages);
1117
+ }
1118
+ if (options.dropEmpty !== false) {
1119
+ dropEmptyBuddyMessages(messages);
1120
+ }
1121
+ if (options.ensureLeadingSystem !== false) {
1122
+ ensureBuddyLeadingSystem(messages, options.leadPrompt);
1123
+ }
1124
+ if (options.omitToolCallContent !== false) {
1125
+ omitBuddyAssistantToolCallContent(messages);
1126
+ }
1127
+ if (options.autoPromptCache !== false) {
1128
+ applyBuddyAutoPromptCache(messages);
1129
+ }
997
1130
  }
998
1131
 
999
1132
  /**
@@ -1035,5 +1168,5 @@ export function applyBuddySystemPrompt(
1035
1168
  messages.push({ ...message });
1036
1169
  }
1037
1170
  }
1038
- finalizeBuddyMessages(messages, systemPrompt);
1171
+ finalizeBuddyMessages(messages, { leadPrompt: systemPrompt });
1039
1172
  }
@@ -24,6 +24,7 @@ import { providerBaseUrl } from "../../provider-metadata";
24
24
  import { sleep } from "../../../shared/sleep";
25
25
  import { WORKBUDDY_DOMAIN } from "./workbuddy/shared";
26
26
  import { BUDDY_PROVIDER_IDS } from "../../provider-metadata";
27
+ import { QODER_CHECKIN_PROVIDER_IDS } from "../qoder/qoder-checkin";
27
28
  import {
28
29
  buildWorkBuddyUserAgent,
29
30
  getWorkBuddyClientVersion,
@@ -38,7 +39,10 @@ import { codebuddyDomain, type CodeBuddyVariant } from "./codebuddy/shared";
38
39
  * shared family set so this list cannot drift from the health recorder's or the
39
40
  * request preparer's.
40
41
  */
41
- export const DAILY_CHECKIN_PROVIDER_IDS: readonly string[] = [...BUDDY_PROVIDER_IDS];
42
+ export const DAILY_CHECKIN_PROVIDER_IDS: readonly string[] = [
43
+ ...BUDDY_PROVIDER_IDS,
44
+ ...QODER_CHECKIN_PROVIDER_IDS,
45
+ ];
42
46
 
43
47
  /** Abort budget for one upstream check-in request. */
44
48
  const TIMEOUT_MS = 15_000;
@@ -337,35 +337,6 @@ export async function redeemBuddyStreakTier(
337
337
  // 3. Streak lottery
338
338
  // ---------------------------------------------------------------------------
339
339
 
340
- const LOTTERY_CHANCES_PATH = "/activity/growth/lottery/chances";
341
- const LOTTERY_DRAW_PATH = "/activity/growth/lottery/draw";
342
-
343
- /** Remaining draw chances granted by a redeemed tier. */
344
- export async function readBuddyLotteryChances(
345
- options: BuddyGrowthCall,
346
- ): Promise<number | null> {
347
- const reply = await call(options, "GET", LOTTERY_CHANCES_PATH);
348
- if (!reply.ok || reply.data === null) return null;
349
- const value = Number(reply.data["balance"]);
350
- return Number.isFinite(value) ? value : null;
351
- }
352
-
353
- /**
354
- * Draws once. A fresh `client_token` per draw: reusing one lets the upstream
355
- * treat the draw as a duplicate and swallow it.
356
- */
357
- export async function drawBuddyLottery(
358
- options: BuddyGrowthCall,
359
- ): Promise<BuddyGrowthResult> {
360
- const reply = await call(options, "POST", LOTTERY_DRAW_PATH, {
361
- client_token: clientToken(options, "draw"),
362
- });
363
- if (refused(reply, 400, ["insufficient", "disabled", "no chance", "未开启"])) {
364
- return { state: "not_eligible", error: null, credit: null };
365
- }
366
- return done(reply);
367
- }
368
-
369
340
  // ---------------------------------------------------------------------------
370
341
  // 4. Cat travel
371
342
  // ---------------------------------------------------------------------------
@@ -523,56 +494,3 @@ export async function adoptFirstBuddy(
523
494
  return done(reply);
524
495
  }
525
496
 
526
- // ---------------------------------------------------------------------------
527
- // 6. Trial gift pack + gift/compensation claims
528
- // ---------------------------------------------------------------------------
529
-
530
- const TRIAL_PATH = "/billing/ide/trial";
531
- const GIFT_PATH = "/billing/meter/claim-gift";
532
- const COMPENSATION_PATH = "/billing/meter/claim-compensation";
533
- /** "Already claimed" on the trial pack. */
534
- const TRIAL_ALREADY_CODE = 14051;
535
-
536
- /**
537
- * Claims the one-off trial credit pack.
538
- *
539
- * International only: the reference reports the CN hosts do not serve this
540
- * endpoint at all, and a 404 there is a `skip`, not a failure.
541
- */
542
- export async function claimBuddyTrial(
543
- options: BuddyGrowthCall,
544
- ): Promise<BuddyGrowthResult> {
545
- const reply = await call(options, "POST", TRIAL_PATH, {});
546
- if (coded(reply, TRIAL_ALREADY_CODE) || refused(reply, 400, ["14051"])) {
547
- return { state: "already", error: null, credit: null };
548
- }
549
- // Not served on this host at all — a 404/405 here is a routing fact, not an
550
- // account problem.
551
- if (reply.status === 404 || reply.status === 405) {
552
- return { state: "skipped", error: null, credit: null };
553
- }
554
- return done(reply);
555
- }
556
-
557
- /**
558
- * Claims the newcomer gift, if one is present.
559
- *
560
- * Once per account; a business error afterwards is the normal "already claimed"
561
- * answer and is never a failure.
562
- */
563
- export async function claimBuddyGift(
564
- options: BuddyGrowthCall,
565
- ): Promise<BuddyGrowthResult> {
566
- const reply = await call(options, "POST", GIFT_PATH, {});
567
- if (!reply.ok) return { state: "already", error: null, credit: null };
568
- return done(reply);
569
- }
570
-
571
- /** Claims an activity compensation, if one is present. Same once-only semantics. */
572
- export async function claimBuddyCompensation(
573
- options: BuddyGrowthCall,
574
- ): Promise<BuddyGrowthResult> {
575
- const reply = await call(options, "POST", COMPENSATION_PATH, {});
576
- if (!reply.ok) return { state: "already", error: null, credit: null };
577
- return done(reply);
578
- }
@@ -1,4 +1,5 @@
1
1
  import type { ProviderDispatchContext } from "../../provider-contracts";
2
+ import { createHash } from "node:crypto";
2
3
  import { decodeJwtPayload } from "../../authentication/oauth-flow-store";
3
4
 
4
5
  /**
@@ -96,3 +97,27 @@ export function buddyEnterprise(context?: ProviderDispatchContext): string | und
96
97
  }
97
98
  return undefined;
98
99
  }
100
+
101
+ /**
102
+ * Per-account device binding (`qimei36`) for the buddy providers.
103
+ *
104
+ * The desktop auth state sends `X-Device-Id-List: {"qimei36":"…"}` alongside
105
+ * the Turing token — a second, independent device identity. Pinning it per
106
+ * account makes every account in a pool present a distinct device to the
107
+ * upstream risk control instead of sharing the machine's single fingerprint.
108
+ *
109
+ * Resolution order:
110
+ * 1. `auth_state.qimei36` — explicit operator-set binding (imported or edited).
111
+ * 2. Determinstic derivation from the internal account id (sha256 → 32 hex).
112
+ * This keeps the binding stable across restarts without a database write,
113
+ * and gives each freshly-added account its own id automatically.
114
+ *
115
+ * Returns `undefined` only when no account id is available (unauthenticated).
116
+ */
117
+ export function resolveBuddyDeviceBinding(context?: ProviderDispatchContext): string | undefined {
118
+ const explicit = context?.credential.auth_state?.["qimei36"];
119
+ if (typeof explicit === "string" && explicit.trim().length > 0) return explicit.trim();
120
+ const accountId = context?.credential.account_id;
121
+ if (accountId === undefined || accountId.length === 0) return undefined;
122
+ return createHash("sha256").update(`wb2a:qimei36:${accountId}`).digest("hex").slice(0, 32);
123
+ }
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Gateway-executed "server tools" for the Buddy family, ported from the
3
+ * reference gateway (`lib/server/proxy/server-tools/` in `codebuddy2api`).
4
+ *
5
+ * Some clients ask the *provider* to run a web tool by declaring a
6
+ * provider-executed type: Anthropic sends `web_search_20250305` /
7
+ * `web_fetch_20250910`, the Responses API sends `web_search_preview`.
8
+ * Upstream CodeBuddy has none of these, so the gateway executes the call
9
+ * against a backend and hands the findings back as if upstream had produced
10
+ * them.
11
+ *
12
+ * The distinction is declaration-driven, never name-driven: a client's own
13
+ * ordinary `function` named `WebSearch` (Claude Code) is resolved by the
14
+ * client, and the gateway must not pick it up. Only calls whose *declared
15
+ * type* carries the provider-executed prefix are server tools.
16
+ *
17
+ * This module is the pure engine: classification, invocation building,
18
+ * execution against injected backends, and the transcript messages a follow-up
19
+ * upstream call needs. The streaming loop that re-drives the upstream within
20
+ * one request is the adapter-side consumer.
21
+ */
22
+ import type { ToolDefinition } from "../../../protocol/canonical-model";
23
+
24
+ export type ServerToolKind = "web_search" | "web_fetch";
25
+
26
+ export interface ServerToolDeclaration {
27
+ readonly kind: ServerToolKind;
28
+ readonly name: string;
29
+ }
30
+
31
+ export interface ServerToolInvocation {
32
+ readonly id: string;
33
+ readonly kind: ServerToolKind;
34
+ readonly rawArguments: string;
35
+ }
36
+
37
+ export interface ServerToolRunResult {
38
+ /** Text handed back upstream as the tool's result message. */
39
+ readonly content: string;
40
+ readonly toolCallId: string;
41
+ }
42
+
43
+ export interface ServerToolExecutor {
44
+ /** Runs one web fetch; `null` means the backend is unavailable. */
45
+ readonly fetch?: ((input: { url: string }) => Promise<string>) | undefined;
46
+ /** Runs one web search; `null` means the backend is unavailable. */
47
+ readonly search?: ((input: { query: string }) => Promise<string>) | undefined;
48
+ }
49
+
50
+ /** The declared-type prefixes the provider-executed tools carry. */
51
+ const SEARCH_TYPE_PREFIX = "web_search_";
52
+ const FETCH_TYPE_PREFIX = "web_fetch_";
53
+
54
+ function normalizeTypeName(value: string): string {
55
+ return value.trim().toLowerCase().replace(/[_-]+/g, " ");
56
+ }
57
+
58
+ function classifyByType(type: string): ServerToolKind | null {
59
+ const normalized = normalizeTypeName(type);
60
+ if (normalized.startsWith(normalizeTypeName(SEARCH_TYPE_PREFIX))) return "web_search";
61
+ if (normalized.startsWith(normalizeTypeName(FETCH_TYPE_PREFIX))) return "web_fetch";
62
+ return null;
63
+ }
64
+
65
+ /**
66
+ * Classifies one canonical tool declaration, or returns `null` for a
67
+ * client-owned tool. Only the declared type counts; the name is never
68
+ * consulted, so a client function named `WebSearch` is never mistaken for the
69
+ * server tool. `native_type` retains the raw Anthropic/OpenAI type spelling
70
+ * (`web_search_20250305`, `web_fetch_20250910`) when the surface carries one;
71
+ * otherwise the normalized `tool_type` decides.
72
+ */
73
+ export function classifyServerToolDeclaration(tool: ToolDefinition): ServerToolKind | null {
74
+ const raw = tool.native_type?.trim();
75
+ if (raw !== undefined && raw.length > 0) {
76
+ const byRaw = classifyByType(raw);
77
+ if (byRaw !== null) return byRaw;
78
+ }
79
+ if (tool.tool_type === "web_search") return "web_search";
80
+ if (tool.tool_type !== undefined && tool.tool_type !== "function") {
81
+ const byType = classifyByType(tool.tool_type);
82
+ if (byType !== null) return byType;
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /** Indexes declared server tools by the name a call would carry. */
88
+ export function findServerToolDeclarations(
89
+ tools: readonly ToolDefinition[] | undefined,
90
+ ): Map<string, ServerToolKind> {
91
+ const out = new Map<string, ServerToolKind>();
92
+ for (const tool of tools ?? []) {
93
+ const kind = classifyServerToolDeclaration(tool);
94
+ if (kind !== null) out.set(tool.name, kind);
95
+ }
96
+ return out;
97
+ }
98
+
99
+ /** True when a call's name matches a declared server tool. */
100
+ export function isServerToolCall(
101
+ name: string,
102
+ declared: ReadonlyMap<string, ServerToolKind>,
103
+ ): boolean {
104
+ return declared.has(name);
105
+ }
106
+
107
+ function parseJsonArguments(raw: string): Record<string, unknown> {
108
+ try {
109
+ const parsed: unknown = JSON.parse(raw);
110
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
111
+ return parsed as Record<string, unknown>;
112
+ }
113
+ } catch {
114
+ // fall through
115
+ }
116
+ return {};
117
+ }
118
+
119
+ /** Builds one invocation from a tool call, kind already classified. */
120
+ export function buildServerToolInvocation(
121
+ callId: string,
122
+ name: string,
123
+ kind: ServerToolKind,
124
+ rawArguments: string,
125
+ ): ServerToolInvocation {
126
+ return { id: callId || `server_tool_${name}`, kind, rawArguments };
127
+ }
128
+
129
+ function textOf(value: unknown): string {
130
+ return typeof value === "string" ? value.trim() : "";
131
+ }
132
+
133
+ function safeFetchUrl(url: string): string | null {
134
+ let parsed: URL;
135
+ try {
136
+ parsed = new URL(url);
137
+ } catch {
138
+ return null;
139
+ }
140
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
141
+ return parsed.toString();
142
+ }
143
+
144
+ /**
145
+ * Runs one invocation against the injected backend. Failures become text
146
+ * rather than exceptions: the arguments came from the model, so the useful
147
+ * outcome is for it to see what went wrong and retry or answer without the
148
+ * findings — not for the whole turn to fail.
149
+ */
150
+ async function runOne(
151
+ executor: ServerToolExecutor,
152
+ invocation: ServerToolInvocation,
153
+ ): Promise<ServerToolRunResult> {
154
+ const args = parseJsonArguments(invocation.rawArguments);
155
+ if (invocation.kind === "web_fetch") {
156
+ const url = safeFetchUrl(textOf(args["url"] ?? args["target"]));
157
+ if (url === null) {
158
+ return {
159
+ toolCallId: invocation.id,
160
+ content: "web_fetch failed: the model supplied an invalid or unsupported URL and nothing was fetched.",
161
+ };
162
+ }
163
+ if (executor.fetch === undefined) {
164
+ return {
165
+ toolCallId: invocation.id,
166
+ content: "web_fetch unavailable: the gateway has no fetch backend configured.",
167
+ };
168
+ }
169
+ try {
170
+ const content = await executor.fetch({ url });
171
+ return { toolCallId: invocation.id, content };
172
+ } catch (error) {
173
+ return {
174
+ toolCallId: invocation.id,
175
+ content: `web_fetch failed: ${error instanceof Error ? error.message : String(error)}`,
176
+ };
177
+ }
178
+ }
179
+ const query = textOf(args["query"]);
180
+ if (query.length === 0) {
181
+ return {
182
+ toolCallId: invocation.id,
183
+ content: "web_search failed: the model supplied an empty query and nothing was searched.",
184
+ };
185
+ }
186
+ if (executor.search === undefined) {
187
+ return {
188
+ toolCallId: invocation.id,
189
+ content: "web_search unavailable: the gateway has no search backend configured.",
190
+ };
191
+ }
192
+ try {
193
+ const content = await executor.search({ query });
194
+ return { toolCallId: invocation.id, content };
195
+ } catch (error) {
196
+ return {
197
+ toolCallId: invocation.id,
198
+ content: `web_search failed: ${error instanceof Error ? error.message : String(error)}`,
199
+ };
200
+ }
201
+ }
202
+
203
+ /** Runs every invocation and returns one tool message per call. */
204
+ export async function executeServerToolInvocations(
205
+ executor: ServerToolExecutor,
206
+ invocations: readonly ServerToolInvocation[],
207
+ ): Promise<readonly ServerToolRunResult[]> {
208
+ return Promise.all(invocations.map((invocation) => runOne(executor, invocation)));
209
+ }
210
+
211
+ /**
212
+ * Builds the transcript messages a follow-up upstream call needs: the
213
+ * assistant turn carrying the calls, then one `tool` result per call. Matches
214
+ * the OpenAI-shaped transcript the buddy upstream expects.
215
+ */
216
+ export function buildServerToolFollowUpMessages(
217
+ assistantToolCalls: Array<{ id: string; name: string; arguments: string }>,
218
+ results: readonly ServerToolRunResult[],
219
+ ): Array<Record<string, unknown>> {
220
+ const messages: Array<Record<string, unknown>> = [];
221
+ if (assistantToolCalls.length > 0) {
222
+ messages.push({
223
+ role: "assistant",
224
+ content: "",
225
+ tool_calls: assistantToolCalls.map((call) => ({
226
+ id: call.id,
227
+ type: "function",
228
+ function: { name: call.name, arguments: call.arguments },
229
+ })),
230
+ });
231
+ }
232
+ for (const result of results) {
233
+ messages.push({
234
+ role: "tool",
235
+ tool_call_id: result.toolCallId,
236
+ content: result.content,
237
+ });
238
+ }
239
+ return messages;
240
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * CodeBuddy CN model roster, isolated in a leaf module.
3
+ *
4
+ * `buddy-chat-shared` needs the raw table to answer output-limit lookups for
5
+ * CN-only ids, and `cn-provider` builds its catalog from it. Keeping the table
6
+ * here — instead of inside `cn-provider` — removes the load-time import cycle
7
+ * between `buddy-chat-shared` ⇄ `cn-provider` (and its buddy-media/provider
8
+ * variants): both sides import this module, and this module imports nothing
9
+ * back.
10
+ */
11
+ import type { BuddyRawEntry } from "../buddy-catalog-shared";
12
+
13
+ export const CODEBUDDY_CN_RAW: readonly BuddyRawEntry[] = [
14
+ ["glm-5.1", "GLM 5.1", true, true, 200_000, 48_000],
15
+ ["glm-5.2", "GLM 5.2", true, true, 1_000_000, 64_000],
16
+ ["glm-5.3", "GLM 5.3", true, true, 1_000_000, 64_000],
17
+ ["glm-5.3-flash", "GLM 5.3 Flash", true, true, 1_000_000, 131_072],
18
+ ["glm-5v-turbo", "GLM 5v Turbo", true, true, 200_000, 64_000],
19
+ ["minimax-m3", "MiniMax M3", true, true, 512_000, 64_000],
20
+ // 1M context, reasoning always on (upstream states both); vision inherited
21
+ // from the M3 row, which is the same family.
22
+ ["minimax-m3.1-flash", "MiniMax M3.1 Flash", true, true, 1_000_000, 128_000],
23
+ ["mimo-v2.6-pro", "MiMo V2.6 Pro", true, true, 1_000_000, 128_000],
24
+ ["kimi-k2.8-preview", "Kimi K2.8 Preview", true, true, 1_000_000, 64_000],
25
+ ["kimi-k3.1-pro", "Kimi K3.1 Pro", true, true, 1_000_000, 32_000],
26
+ ["hy3", "Hy3", true, true, 192_000, 64_000],
27
+ ["hy4-preview", "Hy4 Preview", true, true, 960_000, 64_000],
28
+ ["space-bunny", "Space Bunny", true, true, 1_000_000, 128_000],
29
+ ["step-5-preview", "Step 5 Preview", true, true, 1_000_000, 64_000],
30
+ ["deepseek-v4-pro", "DeepSeek V4 Pro", true, true, 1_000_000, 128_000],
31
+ ["deepseek-v4.1-flash", "DeepSeek V4.1 Flash", true, true, 1_000_000, 128_000],
32
+ // Live on the CN roster but absent here, so a request for it resolved to
33
+ // nothing. Limits as published by /v3/config.
34
+ ["kimi-k3-1", "Kimi K3-1", true, true, 1_000_000, 32_000],
35
+ ["minimax-m2.7", "MiniMax M2.7", true, true, 200_000, 48_000],
36
+ ];