cartethyia 2.7.5 → 2.7.7

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 (300) hide show
  1. package/.env.example +8 -3
  2. package/README.md +26 -2
  3. package/dist/dashboard/assets/BannedPage-B05aF8Dg.js +1 -0
  4. package/dist/dashboard/assets/BannedPage-B05aF8Dg.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-CLUhmjUy.js +3 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-CLUhmjUy.js.gz +0 -0
  7. package/dist/dashboard/assets/CliToolsPage-CQmrqqrk.js +1 -0
  8. package/dist/dashboard/assets/CliToolsPage-CQmrqqrk.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-D5rol2j2.js → ConsoleLogPage-DPgbklad.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-DPgbklad.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-BKj_r0rp.js → CustomizationPage-pnfAqr_-.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-pnfAqr_-.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js.gz +0 -0
  17. package/dist/dashboard/assets/{InjectSystemPromptPanel-B5nZvmf0.js → InjectSystemPromptPanel-C-e2AMz6.js} +245 -2
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-C-e2AMz6.js.gz +0 -0
  19. package/dist/dashboard/assets/LoginPage-J8wPbmfT.js +1 -0
  20. package/dist/dashboard/assets/LoginPage-J8wPbmfT.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-DnQPSIA8.js → ModelPicker-C6WlpU5E.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.js +7 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-D_a3r6Gc.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-D_a3r6Gc.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-5SnUSy3j.js → ProxyPage-BBTRBAQ3.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-BBTRBAQ3.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-B2WqMSG1.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-B2WqMSG1.js.gz +0 -0
  35. package/dist/dashboard/assets/{SettingsPage-BJtRZ4qD.js → SettingsPage-D7xp5Ij_.js} +1 -1
  36. package/dist/dashboard/assets/SettingsPage-D7xp5Ij_.js.gz +0 -0
  37. package/dist/dashboard/assets/SetupPage-LND6_Ss7.js +1 -0
  38. package/dist/dashboard/assets/SetupPage-LND6_Ss7.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-BfAb5-o_.js → StudioPage-BzNnm6Bi.js} +4 -4
  40. package/dist/dashboard/assets/StudioPage-BzNnm6Bi.js.gz +0 -0
  41. package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js +1 -0
  42. package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js.gz +0 -0
  43. package/dist/dashboard/assets/{UsagePage-BNzeZ2Vk.js → UsagePage-C1TbL24N.js} +13 -13
  44. package/dist/dashboard/assets/UsagePage-C1TbL24N.js.gz +0 -0
  45. package/dist/dashboard/assets/api-keys-KvX83mRe.js +1 -0
  46. package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
  47. package/dist/dashboard/assets/app-DRnQLhsc.js +9 -0
  48. package/dist/dashboard/assets/app-DRnQLhsc.js.gz +0 -0
  49. package/dist/dashboard/assets/app-mweRcsKp.css +2 -0
  50. package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
  51. package/dist/dashboard/assets/{assert-C0K-c4eo.js → assert-CidQVoNo.js} +1 -1
  52. package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
  53. package/dist/dashboard/assets/{badge-DfbhQU4k.js → badge-BaX2GTrn.js} +1 -1
  54. package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
  55. package/dist/dashboard/assets/{banner-BllT4LcF.js → banner-DFH4Bkte.js} +1 -1
  56. package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
  57. package/dist/dashboard/assets/{card-CtkX66Wd.js → card-CxDw0lK6.js} +1 -1
  58. package/dist/dashboard/assets/card-CxDw0lK6.js.gz +0 -0
  59. package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js +1 -0
  60. package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js.gz +0 -0
  61. package/dist/dashboard/assets/{cli-tools-CQXVMn4P.js → cli-tools-Bwv5-zGZ.js} +1 -1
  62. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-BQqBQd2n.js → clipboard-button-DfS1ntiq.js} +1 -1
  64. package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
  65. package/dist/dashboard/assets/dialog-BK-NRr5u.js +1 -0
  66. package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-BrTEZ8zu.js → drawer-QqPul6sC.js} +1 -1
  68. package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-BMlYJVFj.js +1 -0
  70. package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
  71. package/dist/dashboard/assets/entry-Dmx7gBiN.js +1 -0
  72. package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-vqGb8yVz.js +4 -0
  74. package/dist/dashboard/assets/entry-vqGb8yVz.js.gz +0 -0
  75. package/dist/dashboard/assets/formatters-DP-5E01-.js +1 -0
  76. package/dist/dashboard/assets/formatters-DP-5E01-.js.gz +0 -0
  77. package/dist/dashboard/assets/house-h9AZlJuT.js +1 -0
  78. package/dist/dashboard/assets/house-h9AZlJuT.js.gz +0 -0
  79. package/dist/dashboard/assets/{index-FQlj0HUT.js → index-D36vCZht.js} +2 -2
  80. package/dist/dashboard/assets/index-D36vCZht.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-C4gU5iOL.js → inline-CHJQwxLa.js} +1 -1
  82. package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-BHHX_Tmm.js → input-BlmlizVV.js} +1 -1
  84. package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
  85. package/dist/dashboard/assets/{layout-CajrYUnD.js → layout-Bz93I-yE.js} +1 -1
  86. package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
  87. package/dist/dashboard/assets/{list-row-Bx2I7XUe.js → list-row-Ml16KCfu.js} +1 -1
  88. package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
  89. package/dist/dashboard/assets/{live-DNNqRbj2.js → live-7_QpnYGu.js} +1 -1
  90. package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
  91. package/dist/dashboard/assets/{mutation-Rr0TIvcj.js → mutation-GU7XdEjz.js} +1 -1
  92. package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
  93. package/dist/dashboard/assets/{network-C5UBACrJ.js → network-CuMDaTzT.js} +1 -1
  94. package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
  95. package/dist/dashboard/assets/numbers-CoqpaDMx.js +2 -0
  96. package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
  97. package/dist/dashboard/assets/{pagination-Xsi9Qx04.js → pagination-B5aOoweZ.js} +1 -1
  98. package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
  99. package/dist/dashboard/assets/providers/zai.webp +0 -0
  100. package/dist/dashboard/assets/providers/zcode.webp +0 -0
  101. package/dist/dashboard/assets/{query-keys-BloKtaXJ.js → query-keys-YSoQOUh6.js} +1 -1
  102. package/dist/dashboard/assets/{query-keys-BloKtaXJ.js.gz → query-keys-YSoQOUh6.js.gz} +0 -0
  103. package/dist/dashboard/assets/quota-DJ1QlAOq.js +1 -0
  104. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  105. package/dist/dashboard/assets/{routing-in0f_IfK.js → routing-fnCsxgNO.js} +1 -1
  106. package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
  107. package/dist/dashboard/assets/{select-BLULNqTp.js → select-C9Ssle9b.js} +2 -2
  108. package/dist/dashboard/assets/select-C9Ssle9b.js.gz +0 -0
  109. package/dist/dashboard/assets/{settings-CcGczj6I.js → settings-BxslsErn.js} +1 -1
  110. package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
  111. package/dist/dashboard/assets/{stack-D2lIR0tg.js → stack-j7cXjgYX.js} +1 -1
  112. package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
  113. package/dist/dashboard/assets/{state-DTARjfiW.js → state-DHmEnLa2.js} +1 -1
  114. package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
  115. package/dist/dashboard/assets/{switch-c0oPSnqW.js → switch-DmZcyTIC.js} +1 -1
  116. package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
  117. package/dist/dashboard/assets/{useMutation-m8WGNPrz.js → useMutation-CbCP4w1o.js} +1 -1
  118. package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
  119. package/dist/dashboard/index.html +2 -2
  120. package/dist/dashboard/index.html.gz +0 -0
  121. package/dist/dashboard/precompressed-assets.json +57 -57
  122. package/migrations/full-postgres/0042_add_telemetry_degenerate_stop.sql +14 -0
  123. package/migrations/full-postgres/0043_add_provider_custom_user_agent.sql +14 -0
  124. package/migrations/lite-sqlite3/0042_add_telemetry_degenerate_stop.sql +12 -0
  125. package/migrations/lite-sqlite3/0043_add_provider_custom_user_agent.sql +10 -0
  126. package/package.json +3 -1
  127. package/src/boot/dependencies.ts +10 -2
  128. package/src/config/providers.ts +13 -7
  129. package/src/config/security.ts +1 -1
  130. package/src/config/spec.ts +31 -17
  131. package/src/config/usage.ts +27 -0
  132. package/src/console/api-keys/routes.ts +19 -23
  133. package/src/console/cli-tools/cartethyia-websearch-plugin.ts +186 -0
  134. package/src/console/cli-tools/contracts.ts +39 -295
  135. package/src/console/cli-tools/fs-ops.ts +29 -0
  136. package/src/console/cli-tools/injectors/claude.ts +3 -3
  137. package/src/console/cli-tools/injectors/codex.ts +6 -6
  138. package/src/console/cli-tools/injectors/driver.ts +8 -105
  139. package/src/console/cli-tools/injectors/kilo.ts +146 -21
  140. package/src/console/cli-tools/injectors/opencode.ts +68 -17
  141. package/src/console/cli-tools/routes.ts +6 -0
  142. package/src/console/cli-tools/service.ts +7 -6
  143. package/src/console/observability/contracts.ts +6 -0
  144. package/src/console/observability/store.ts +3 -0
  145. package/src/console/providers/catalog/contracts.ts +4 -0
  146. package/src/console/providers/catalog/routes.ts +1 -0
  147. package/src/console/providers/detail/routes.ts +8 -0
  148. package/src/console/providers/detail/store.ts +4 -0
  149. package/src/console/quota/account-quota-tenant.ts +74 -0
  150. package/src/console/share/share-stats.ts +7 -9
  151. package/src/observability/event-log.ts +7 -0
  152. package/src/observability/event-query.ts +2 -0
  153. package/src/observability/telemetry-buffer.ts +9 -1
  154. package/src/persistence/boot.ts +13 -4
  155. package/src/persistence/schema.ts +15 -1
  156. package/src/persistence/stores/api-key-store.ts +19 -1
  157. package/src/protocol/canonical-model.ts +7 -0
  158. package/src/protocol/dsml.ts +5 -76
  159. package/src/protocol/request/gemini.ts +20 -1
  160. package/src/protocol/tool-identity.ts +15 -1
  161. package/src/providers/adapters/compatible-adapter.ts +29 -1
  162. package/src/providers/default-registry.ts +20 -4
  163. package/src/providers/discovery/discovery-types.ts +18 -0
  164. package/src/providers/discovery/probe-phases.ts +45 -0
  165. package/src/providers/discovery/probing-service.ts +41 -6
  166. package/src/providers/integrations/antigravity/antigravity-protocol.ts +108 -16
  167. package/src/providers/integrations/antigravity/antigravity-quota.ts +18 -6
  168. package/src/providers/integrations/antigravity/antigravity.ts +44 -11
  169. package/src/providers/integrations/codex/codex.ts +28 -1
  170. package/src/providers/integrations/cursor/cursor.ts +2 -3
  171. package/src/providers/integrations/github/github-discovery.ts +15 -2
  172. package/src/providers/integrations/github/github-oauth.ts +178 -9
  173. package/src/providers/integrations/github/github.ts +95 -14
  174. package/src/providers/integrations/kiro/kiro.ts +1 -2
  175. package/src/providers/integrations/qoder/chat-proto.ts +328 -0
  176. package/src/providers/integrations/qoder/claim-qoder-credits.ts +258 -0
  177. package/src/providers/integrations/qoder/qoder-adapter.ts +275 -0
  178. package/src/providers/integrations/qoder/qoder-auth.ts +201 -0
  179. package/src/providers/integrations/qoder/qoder-body.ts +78 -0
  180. package/src/providers/integrations/qoder/qoder-catalog.ts +14 -4
  181. package/src/providers/integrations/qoder/qoder-chat.ts +514 -0
  182. package/src/providers/integrations/qoder/qoder-cosy.ts +162 -0
  183. package/src/providers/integrations/qoder/qoder-grpc.ts +667 -0
  184. package/src/providers/integrations/qoder/qoder-ide.ts +584 -0
  185. package/src/providers/integrations/qoder/qoder-oauth.ts +4 -4
  186. package/src/providers/integrations/qoder/qoder-quota.ts +1 -1
  187. package/src/providers/integrations/qoder/qoder-realm.ts +40 -4
  188. package/src/providers/integrations/qoder/qoder-types.ts +51 -0
  189. package/src/providers/integrations/qoder/qoder-umid.ts +123 -8
  190. package/src/providers/integrations/qoder/qoder.ts +147 -1830
  191. package/src/providers/integrations/trae/trae.ts +2 -2
  192. package/src/providers/operations/account-error-classification.ts +67 -17
  193. package/src/providers/operations/byok-wire-profile.ts +19 -15
  194. package/src/providers/provider-contracts.ts +7 -0
  195. package/src/providers/provider-usage.ts +24 -55
  196. package/src/security/abuse.ts +5 -5
  197. package/src/security/admission/service.ts +11 -4
  198. package/src/security/api-key-auth.ts +5 -3
  199. package/src/transport/dispatch/attempt-finalize.ts +69 -21
  200. package/src/transport/dispatch/attempt-loop.ts +22 -0
  201. package/src/transport/dispatch/upstream.ts +4 -2
  202. package/src/transport/failure-policy.ts +15 -1
  203. package/src/transport/guards/error-lifecycle.ts +4 -0
  204. package/src/transport/request/state.ts +6 -0
  205. package/src/transport/routing/route-catalog.ts +8 -0
  206. package/src/transport/routing/route-model.ts +4 -0
  207. package/src/transport/routing/router.ts +8 -110
  208. package/src/transport/surface/reasoning-summary.ts +9 -14
  209. package/src/transport/vision-handoff.ts +83 -28
  210. package/dist/dashboard/assets/BannedPage-Cj15GsMp.js +0 -1
  211. package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
  212. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +0 -1
  213. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
  214. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js +0 -1
  215. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  216. package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js +0 -1
  217. package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
  218. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +0 -1
  219. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
  220. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
  221. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
  222. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
  223. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js +0 -1
  224. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  225. package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
  226. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +0 -1
  227. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
  228. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +0 -5
  229. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
  230. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +0 -1
  231. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
  232. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +0 -1
  233. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
  234. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
  235. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +0 -1
  236. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
  237. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
  238. package/dist/dashboard/assets/SetupPage-WTa0sktC.js +0 -1
  239. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  240. package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
  241. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js +0 -1
  242. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
  243. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
  244. package/dist/dashboard/assets/api-keys-l29JROPv.js +0 -1
  245. package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
  246. package/dist/dashboard/assets/app--PJWq8_C.css +0 -2
  247. package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
  248. package/dist/dashboard/assets/app-Bgd3zANQ.js +0 -9
  249. package/dist/dashboard/assets/app-Bgd3zANQ.js.gz +0 -0
  250. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  251. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  252. package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
  253. package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
  254. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  255. package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
  256. package/dist/dashboard/assets/dialog-BEK7KP0H.js +0 -1
  257. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  258. package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
  259. package/dist/dashboard/assets/entry-CHWxbS56.js +0 -1
  260. package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
  261. package/dist/dashboard/assets/entry-EBHJ3DrE.js +0 -1
  262. package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
  263. package/dist/dashboard/assets/entry-LCS85hiq.js +0 -4
  264. package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
  265. package/dist/dashboard/assets/formatters-BAACA727.js +0 -1
  266. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  267. package/dist/dashboard/assets/github-badge-YSCE4UC2.js +0 -1
  268. package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
  269. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  270. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  271. package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
  272. package/dist/dashboard/assets/layout-CajrYUnD.js.gz +0 -0
  273. package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
  274. package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
  275. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  276. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  277. package/dist/dashboard/assets/numbers-CUWAZK4J.js +0 -2
  278. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  279. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  280. package/dist/dashboard/assets/providers/zai-coding-plan.svg +0 -6
  281. package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
  282. package/dist/dashboard/assets/quota-DvQbD_sr.js +0 -1
  283. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  284. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  285. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  286. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  287. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  288. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  289. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  290. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  291. package/src/console/cli-tools/injectors/cline.ts +0 -110
  292. package/src/console/cli-tools/injectors/copilot.ts +0 -108
  293. package/src/console/cli-tools/injectors/cowork.ts +0 -64
  294. package/src/console/cli-tools/injectors/deepseek-tui.ts +0 -85
  295. package/src/console/cli-tools/injectors/droid.ts +0 -96
  296. package/src/console/cli-tools/injectors/grok-build.ts +0 -70
  297. package/src/console/cli-tools/injectors/hermes.ts +0 -101
  298. package/src/console/cli-tools/injectors/jcode.ts +0 -115
  299. package/src/console/cli-tools/injectors/openclaw.ts +0 -161
  300. package/src/providers/integrations/qoder/qoder-text-tools.ts +0 -277
@@ -1,5 +1,5 @@
1
1
  import type {} from "../contracts";
2
- import { ensureV1Suffix, homeDir, readJsonFile, readTextFile, removeFile, textGet, textHas, textRemove, textUpsert, writeJsonFile, writeTextFile } from "../fs-ops";
2
+ import { ensureV1Suffix, homeDir, readJsonFile, readTextFile, removeFile, textGet, textHas, textRemove, textUpsert, writeJsonFileWithBackup, writeTextFileWithBackup } from "../fs-ops";
3
3
  import type { InjectorSpec } from "../contracts";
4
4
 
5
5
 
@@ -93,12 +93,12 @@ export const codexSpec: InjectorSpec = {
93
93
  ` refresh_interval_ms = 0`,
94
94
  ].join("\n"),
95
95
  );
96
- await writeTextFile(path, text);
96
+ await writeTextFileWithBackup(path, text);
97
97
 
98
98
  const auth = ((await readJsonFile(codexAuthPath())) as Record<string, string> | null) ?? {};
99
99
  auth[CODEX_PROVIDER] = input.apiKey;
100
- await writeJsonFile(codexAuthPath(), auth);
101
- await writeTextFile(codexAuthHelperPath(), CODEX_AUTH_HELPER_SOURCE);
100
+ await writeJsonFileWithBackup(codexAuthPath(), auth);
101
+ await writeTextFileWithBackup(codexAuthHelperPath(), CODEX_AUTH_HELPER_SOURCE);
102
102
  },
103
103
 
104
104
  async reset(path) {
@@ -118,12 +118,12 @@ export const codexSpec: InjectorSpec = {
118
118
  if (textGet(text, { kind: "flat", key: "model_provider" }) === CODEX_PROVIDER) {
119
119
  text = textUpsert(text, { kind: "flat", key: "model_provider", insertAtTop: true }, "openai");
120
120
  }
121
- await writeTextFile(path, text);
121
+ await writeTextFileWithBackup(path, text);
122
122
 
123
123
  const auth = (await readJsonFile(codexAuthPath())) as Record<string, string> | null;
124
124
  if (auth?.[CODEX_PROVIDER]) {
125
125
  delete auth[CODEX_PROVIDER];
126
- await writeJsonFile(codexAuthPath(), auth);
126
+ await writeJsonFileWithBackup(codexAuthPath(), auth);
127
127
  }
128
128
  await removeFile(codexAuthHelperPath());
129
129
  },
@@ -6,25 +6,15 @@ import type {
6
6
  ApplyResult,
7
7
  DownloadResult,
8
8
  InjectorSpec,
9
- ToolDef,
10
9
  ToolId,
11
10
  ToolInjector,
12
11
  ToolStatus,
13
12
  } from "../contracts";
14
- import { TOOL_IDS, TOOL_REGISTRY } from "../contracts";
15
- import { checkBinaryInstalled, ensureDir, ensureV1Suffix, fileExists, keyPrefix } from "../fs-ops";
13
+ import { TOOL_IDS } from "../contracts";
14
+ import { checkBinaryInstalled, ensureDir, fileExists, keyPrefix } from "../fs-ops";
16
15
  import { claudeSpec } from './claude';
17
- import { clineSpec } from './cline';
18
16
  import { codexSpec } from './codex';
19
- import { copilotSpec } from './copilot';
20
- import { coworkSpec } from './cowork';
21
- import { deepseekTuiSpec } from './deepseek-tui';
22
- import { droidSpec } from './droid';
23
- import { grokBuildSpec } from './grok-build';
24
- import { hermesSpec } from './hermes';
25
- import { jcodeSpec } from './jcode';
26
17
  import { kiloSpec } from './kilo';
27
- import { openclawSpec } from './openclaw';
28
18
  import { opencodeSpec } from './opencode';
29
19
 
30
20
  // ── injectors/driver.ts ──
@@ -92,11 +82,12 @@ export function createFileInjector(spec: InjectorSpec): ToolInjector {
92
82
  const path = spec.resolvePath();
93
83
  const dir = spec.resolveDir ? spec.resolveDir() : dirname(path);
94
84
  await ensureDir(dir);
95
- await spec.apply(input, path);
85
+ const warnings = await spec.apply(input, path);
96
86
  return {
97
87
  success: true,
98
88
  settingsPath: path,
99
89
  message: spec.messages?.applied ?? `${spec.displayName ?? spec.toolId} settings applied`,
90
+ ...(Array.isArray(warnings) && warnings.length > 0 ? { warnings } : {}),
100
91
  };
101
92
  },
102
93
 
@@ -131,114 +122,26 @@ export function createFileInjector(spec: InjectorSpec): ToolInjector {
131
122
  };
132
123
  }
133
124
 
134
- /** Create a guide injector for a specific tool definition (any `configType: "guide"` entry). */
135
- function guideInjectorFor(def: ToolDef): ToolInjector {
136
- return {
137
- toolId: def.id,
138
-
139
- async getStatus(): Promise<ToolStatus> {
140
- // Guide tools are always "not installed" from an injection standpoint —
141
- // we can't detect the binary reliably, and config is manual.
142
- return {
143
- toolId: def.id,
144
- installed: false,
145
- configured: false,
146
- settingsPath: null,
147
- currentEndpoint: null,
148
- currentApiKeyPrefix: null,
149
- currentModels: null,
150
- message: "Guide-only tool — use Download Config to get setup instructions",
151
- };
152
- },
153
-
154
- async apply(): Promise<ApplyResult> {
155
- return { success: false, message: `${def.name} is a guide-only tool — use Download Config` };
156
- },
157
-
158
- async reset(): Promise<ApplyResult> {
159
- return { success: true, message: `${def.name} has no injected config to reset` };
160
- },
161
-
162
- async download(input: ApplyInput): Promise<DownloadResult> {
163
- const baseUrl = ensureV1Suffix(input.endpoint);
164
- const model = input.activeModel ?? input.modelIds[0] ?? "";
165
- const apiKey = input.apiKey;
166
- const codeBlock = def.codeBlock;
167
-
168
- if (codeBlock) {
169
- const content = codeBlock.code
170
- .replace(/\{\{baseUrl\}\}/g, baseUrl)
171
- .replace(/\{\{apiKey\}\}/g, apiKey)
172
- .replace(/\{\{model\}\}/g, model);
173
- const ext =
174
- codeBlock.language === "json" ? "json" : codeBlock.language === "bash" ? "sh" : "txt";
175
- return {
176
- content,
177
- filename: `${def.id}-config.${ext}`,
178
- mimeType: codeBlock.language === "json" ? "application/json" : "text/plain",
179
- };
180
- }
181
-
182
- // No code block — generate a simple guide summary.
183
- const steps = def.guideSteps ?? [];
184
- const lines = [`# ${def.name} — Setup Guide`, ""];
185
- for (const step of steps) {
186
- lines.push(`## Step ${step.step}: ${step.title}`);
187
- if (step.desc) lines.push(step.desc);
188
- if (step.value) {
189
- lines.push("```");
190
- lines.push(
191
- step.value
192
- .replace(/\{\{baseUrl\}\}/g, baseUrl)
193
- .replace(/\{\{apiKey\}\}/g, apiKey)
194
- .replace(/\{\{model\}\}/g, model),
195
- );
196
- lines.push("```");
197
- }
198
- lines.push("");
199
- }
200
- lines.push(`Endpoint: ${baseUrl}`);
201
- lines.push(`API Key: ${apiKey}`);
202
- lines.push(`Model: ${model}`);
203
- return { content: lines.join("\n"), filename: `${def.id}-guide.txt`, mimeType: "text/plain" };
204
- },
205
- };
206
- }
207
-
208
125
  // ── injectors/index.ts ──
209
126
  /**
210
127
  * Single dispatch point for CliToolService: given a toolId, look up the
211
128
  * injector and call getStatus/apply/reset/download.
212
129
  *
213
130
  * File-based injectors are driven by a generic driver (createFileInjector)
214
- * configured via declarative specs. Guide-only tools (registry `configType:
215
- * "guide"`) are all built from `guideInjectorFor` — no per-tool constant
216
- * needed; the registry itself is the single source of the list. The map is
217
- * built exhaustively from that registry so a non-guide tool without an
218
- * injector fails loudly at import, never silently at lookup time.
131
+ * configured via declarative specs. The map is built exhaustively from that
132
+ * registry so a tool without an injector fails loudly at import, never
133
+ * silently at lookup time.
219
134
  */
220
135
  const FILE_INJECTORS: Partial<Record<ToolId, ToolInjector>> = {
221
136
  claude: createFileInjector(claudeSpec),
222
137
  codex: createFileInjector(codexSpec),
223
- cline: createFileInjector(clineSpec),
224
138
  opencode: createFileInjector(opencodeSpec),
225
- droid: createFileInjector(droidSpec),
226
- hermes: createFileInjector(hermesSpec),
227
- "grok-build": createFileInjector(grokBuildSpec),
228
- copilot: createFileInjector(copilotSpec),
229
- "deepseek-tui": createFileInjector(deepseekTuiSpec),
230
- jcode: createFileInjector(jcodeSpec),
231
139
  kilo: createFileInjector(kiloSpec),
232
- openclaw: createFileInjector(openclawSpec),
233
- cowork: createFileInjector(coworkSpec),
234
140
  };
235
141
 
236
142
  export const INJECTORS: Record<ToolId, ToolInjector> = Object.fromEntries(
237
143
  TOOL_IDS.map((id) => {
238
- const injector =
239
- TOOL_REGISTRY[id].configType === "guide"
240
- ? guideInjectorFor(TOOL_REGISTRY[id])
241
- : FILE_INJECTORS[id];
144
+ const injector = FILE_INJECTORS[id];
242
145
  if (injector === undefined) {
243
146
  throw new Error(`No injector registered for tool: ${id}`);
244
147
  }
@@ -1,13 +1,36 @@
1
1
  import { platform } from "node:os";
2
2
  import type {} from "../contracts";
3
- import { resolveApplyTarget, jsonDownload } from "../contracts";
4
- import { homeDir, isLocalEndpoint, join, readJsonFile, writeJsonFile } from "../fs-ops";
3
+ import { jsonDownload } from "../contracts";
4
+ import { homeDir, isLocalEndpoint, join, readJsonFile, writeJsonFileWithBackup } from "../fs-ops";
5
5
  import type { InjectorSpec } from "../contracts";
6
6
 
7
7
  const IS_WIN = platform() === "win32";
8
8
  const IS_MAC = platform() === "darwin";
9
9
 
10
10
  // Kilo Code injector spec.
11
+ //
12
+ // Kilo Code is a fork of OpenCode, but it now reads its own config file —
13
+ // `~/.config/kilo/kilo.jsonc` (Windows: `%USERPROFILE%\.config\kilo\kilo.jsonc`)
14
+ // — with the same provider shape as OpenCode: `provider.<id>.npm/options/models`
15
+ // plus a top-level `model`. Unlike OpenCode, Kilo runs on the AI SDK, so the
16
+ // `npm` value must be an `@ai-sdk/*` package (default `@ai-sdk/openai-compatible`).
17
+ //
18
+ // The older VS Code extension also keeps a credential store at
19
+ // `~/.local/share/kilo/auth.json`; we still write the endpoint there for
20
+ // backward compatibility, but the canonical config is now `kilo.jsonc`.
21
+
22
+ const KILO_PROVIDER_ID = "cartethyia";
23
+
24
+ function resolveKiloConfigDirectory(): string {
25
+ if (IS_WIN) return join(process.env.USERPROFILE ?? homeDir(), ".config", "kilo");
26
+ if (IS_MAC) return join(homeDir(), ".config", "kilo");
27
+ return join(homeDir(), ".config", "kilo");
28
+ }
29
+
30
+ function kiloConfigPath(): string {
31
+ return join(resolveKiloConfigDirectory(), "kilo.jsonc");
32
+ }
33
+
11
34
  function resolveKiloDataDirectory(): string {
12
35
  if (IS_WIN) return join(process.env.LOCALAPPDATA ?? homeDir(), "kilo");
13
36
  if (IS_MAC) return join(homeDir(), "Library", "Application Support", "kilo");
@@ -21,15 +44,52 @@ function kiloVscodeSettingsPath(): string {
21
44
  return join(homeDir(), ".config", "Code", "User", "settings.json");
22
45
  }
23
46
 
47
+ function kiloModelDef(model: string): unknown {
48
+ return {
49
+ name: model,
50
+ limit: { context: 128000, output: 64000 },
51
+ };
52
+ }
53
+
54
+ function buildKiloModels(models: readonly string[]): Record<string, unknown> {
55
+ const map: Record<string, unknown> = {};
56
+ for (const m of models) {
57
+ if (!m) continue;
58
+ map[m] = kiloModelDef(m);
59
+ }
60
+ return map;
61
+ }
62
+
24
63
  export const kiloSpec: InjectorSpec = {
25
64
  toolId: "kilo",
26
65
  displayName: "Kilo Code",
27
66
  binary: "kilo",
28
- resolveDir: resolveKiloDataDirectory,
29
- resolvePath: () => join(resolveKiloDataDirectory(), "auth.json"),
67
+ resolveDir: resolveKiloConfigDirectory,
68
+ resolvePath: kiloConfigPath,
30
69
 
31
70
  async readStatus(path) {
32
- const auth = (await readJsonFile(path)) as Record<
71
+ const config = (await readJsonFile(path)) as {
72
+ provider?: Record<string, { npm?: string; options?: { baseURL?: string; apiKey?: string }; models?: Record<string, unknown> }>;
73
+ model?: string;
74
+ } | null;
75
+ const provider = config?.provider?.[KILO_PROVIDER_ID];
76
+ const endpoint = provider?.options?.baseURL ?? null;
77
+ const models = provider?.models ? Object.keys(provider.models) : null;
78
+ const active =
79
+ typeof config?.model === "string" && config.model.startsWith(`${KILO_PROVIDER_ID}/`)
80
+ ? config.model.split("/").slice(1).join("/")
81
+ : null;
82
+ if (endpoint) {
83
+ return {
84
+ configured: isLocalEndpoint(endpoint),
85
+ currentEndpoint: endpoint,
86
+ rawApiKey: provider?.options?.apiKey ?? null,
87
+ currentModels: active && models ? [active, ...models.filter((m) => m !== active)] : models,
88
+ };
89
+ }
90
+
91
+ // Fallback to the legacy auth.json store (older VS Code extension).
92
+ const auth = (await readJsonFile(join(resolveKiloDataDirectory(), "auth.json"))) as Record<
33
93
  string,
34
94
  { baseUrl?: string; baseURL?: string; apiKey?: string; model?: string }
35
95
  > | null;
@@ -47,22 +107,77 @@ export const kiloSpec: InjectorSpec = {
47
107
  },
48
108
 
49
109
  async apply(input, path) {
50
- const { model, baseUrl } = resolveApplyTarget(input);
51
- const auth = ((await readJsonFile(path)) as Record<string, unknown> | null) ?? {};
52
- auth["openai-compatible"] = { baseUrl, apiKey: input.apiKey, model };
53
- await writeJsonFile(path, auth);
110
+ // Canonical config: ~/.config/kilo/kilo.jsonc
111
+ const existing = (await readJsonFile(path)) as Record<string, unknown> | null;
112
+ const config: Record<string, unknown> = (existing ?? {}) as Record<string, unknown>;
113
+ if (!config.provider || typeof config.provider !== "object") config.provider = {};
114
+ const providers = config.provider as Record<string, unknown>;
115
+ const prior = (providers[KILO_PROVIDER_ID] as Record<string, unknown> | undefined) ?? {};
116
+ const priorModels = (prior.models as Record<string, unknown> | undefined) ?? {};
117
+ const otherProviders = Object.keys(providers).filter((id) => id !== KILO_PROVIDER_ID);
118
+ providers[KILO_PROVIDER_ID] = {
119
+ npm: input.sdk ?? (prior as { npm?: string }).npm ?? "@ai-sdk/openai-compatible",
120
+ options: {
121
+ ...((prior.options as Record<string, unknown> | undefined) ?? {}),
122
+ baseURL: input.endpoint,
123
+ apiKey: input.apiKey,
124
+ },
125
+ models: { ...priorModels, ...buildKiloModels(input.modelIds) },
126
+ };
127
+ const activeModel = input.activeModel ?? input.modelIds[0] ?? "";
128
+ config.model = activeModel === "" ? "" : `${KILO_PROVIDER_ID}/${activeModel}`;
129
+
130
+ // YOLO mode: auto-approve shell/read/write.
131
+ if (input.bypassPermissions === true) {
132
+ config.permissions = [
133
+ { action: "shell", resource: "*", effect: "allow" },
134
+ { action: "external_directory", resource: "*", effect: "allow" },
135
+ { action: "question", resource: "*", effect: "deny" },
136
+ ];
137
+ } else if (input.bypassPermissions === false) {
138
+ delete config.permissions;
139
+ }
140
+
141
+ await writeJsonFileWithBackup(path, config);
142
+
143
+ // Legacy credential store for the older VS Code extension.
144
+ const authPath = join(resolveKiloDataDirectory(), "auth.json");
145
+ const auth = ((await readJsonFile(authPath)) as Record<string, unknown> | null) ?? {};
146
+ auth["openai-compatible"] = { baseUrl: input.endpoint, apiKey: input.apiKey, model: activeModel };
147
+ await writeJsonFileWithBackup(authPath, auth);
54
148
 
55
149
  const settings =
56
150
  ((await readJsonFile(kiloVscodeSettingsPath())) as Record<string, unknown> | null) ?? {};
57
151
  settings["kilo.code.authProviderOverride"] = "openai-compatible";
58
- await writeJsonFile(kiloVscodeSettingsPath(), settings);
152
+ await writeJsonFileWithBackup(kiloVscodeSettingsPath(), settings);
153
+
154
+ return otherProviders.length > 0
155
+ ? [
156
+ `Config already has other provider(s): ${otherProviders.join(", ")}. They were kept as-is — only ${KILO_PROVIDER_ID} was written/updated.`,
157
+ ]
158
+ : undefined;
59
159
  },
60
160
 
61
161
  async reset(path) {
62
- const auth = (await readJsonFile(path)) as Record<string, unknown> | null;
63
- if (!auth) return false;
64
- delete auth["openai-compatible"];
65
- await writeJsonFile(path, auth);
162
+ const config = (await readJsonFile(path)) as Record<string, unknown> | null;
163
+ if (config && config.provider && typeof config.provider === "object") {
164
+ const providers = config.provider as Record<string, unknown>;
165
+ delete providers[KILO_PROVIDER_ID];
166
+ if (Object.keys(providers).length === 0) delete config.provider;
167
+ }
168
+ if (typeof config?.model === "string" && config.model.startsWith(`${KILO_PROVIDER_ID}/`)) {
169
+ delete config.model;
170
+ }
171
+ if (config) await writeJsonFileWithBackup(path, config);
172
+
173
+ const auth = (await readJsonFile(join(resolveKiloDataDirectory(), "auth.json"))) as Record<
174
+ string,
175
+ unknown
176
+ > | null;
177
+ if (auth) {
178
+ delete auth["openai-compatible"];
179
+ await writeJsonFileWithBackup(join(resolveKiloDataDirectory(), "auth.json"), auth);
180
+ }
66
181
 
67
182
  const settings = (await readJsonFile(kiloVscodeSettingsPath())) as Record<
68
183
  string,
@@ -70,21 +185,31 @@ export const kiloSpec: InjectorSpec = {
70
185
  > | null;
71
186
  if (settings && "kilo.code.authProviderOverride" in settings) {
72
187
  delete settings["kilo.code.authProviderOverride"];
73
- await writeJsonFile(kiloVscodeSettingsPath(), settings);
188
+ await writeJsonFileWithBackup(kiloVscodeSettingsPath(), settings);
74
189
  }
75
190
  },
76
191
 
77
192
  download(input) {
78
- const { model, baseUrl } = resolveApplyTarget(input);
79
- return jsonDownload({ "openai-compatible": { baseUrl, apiKey: input.apiKey, model } }, {
80
- filename: "auth.json",
81
- });
193
+ const activeModel = input.activeModel ?? input.modelIds[0] ?? "";
194
+ const config: Record<string, unknown> = {
195
+ provider: {
196
+ [KILO_PROVIDER_ID]: {
197
+ npm: input.sdk ?? "@ai-sdk/openai-compatible",
198
+ options: {
199
+ baseURL: input.endpoint,
200
+ apiKey: input.apiKey,
201
+ },
202
+ models: buildKiloModels(input.modelIds),
203
+ },
204
+ },
205
+ model: activeModel === "" ? "" : `${KILO_PROVIDER_ID}/${activeModel}`,
206
+ };
207
+ return jsonDownload(config, { filename: "kilo.jsonc" });
82
208
  },
83
209
 
84
210
  messages: {
85
211
  applied: "Kilo Code settings applied",
86
212
  reset: "Cartethyia settings removed from Kilo Code",
87
- resetMissing: "No auth file to reset",
213
+ resetMissing: "No config file to reset",
88
214
  },
89
215
  };
90
-
@@ -1,6 +1,8 @@
1
1
  import type {} from "../contracts";
2
- import { ensureV1Suffix, homeDir, join, readJsonFile, writeJsonFile } from "../fs-ops";
2
+ import { ensureV1Suffix, homeDir, join, readJsonFile, writeJsonFileWithBackup } from "../fs-ops";
3
3
  import type { InjectorSpec } from "../contracts";
4
+ import { DEFAULT_SDK } from "../contracts";
5
+ import { writeWebsearchPlugin, removeWebsearchPlugin } from "../cartethyia-websearch-plugin";
4
6
 
5
7
 
6
8
  // OpenCode injector spec.
@@ -18,6 +20,7 @@ interface OpencodeProvider {
18
20
  interface OpencodeConfig {
19
21
  provider?: Record<string, OpencodeProvider>;
20
22
  model?: string;
23
+ websearch?: { provider?: string };
21
24
  agent?: {
22
25
  explorer?: {
23
26
  description?: string;
@@ -50,11 +53,14 @@ export const opencodeSpec: InjectorSpec = {
50
53
  if (!config) {
51
54
  return { configured: false, currentEndpoint: null, rawApiKey: null, currentModels: null };
52
55
  }
53
- const provider = config.provider?.cartethyia;
56
+ // The provider key written by this injector is the websearch provider name
57
+ // when one is configured, otherwise the default `cartethyia`.
58
+ const providerId = config.websearch?.provider ?? "cartethyia";
59
+ const provider = config.provider?.[providerId];
54
60
  const endpoint = provider?.options?.baseURL ?? null;
55
61
  const models = provider?.models ? Object.keys(provider.models) : null;
56
62
  const active =
57
- typeof config.model === "string" && config.model.startsWith("cartethyia/")
63
+ typeof config.model === "string" && config.model.startsWith(`${providerId}/`)
58
64
  ? config.model.split("/").slice(1).join("/")
59
65
  : null;
60
66
  return {
@@ -69,10 +75,16 @@ export const opencodeSpec: InjectorSpec = {
69
75
  const existing = (await readJsonFile(path)) as OpencodeConfig | null;
70
76
  const config: OpencodeConfig = (existing ?? {}) as OpencodeConfig;
71
77
 
78
+ // The provider key written into opencode.jsonc follows the websearch
79
+ // provider name when one is chosen, which keeps the websearch plugin and
80
+ // the provider block pointing at the same entry.
81
+ const providerId = input.websearch ?? "cartethyia";
82
+
72
83
  if (!config.provider) config.provider = {};
73
- const prior = config.provider.cartethyia;
74
- config.provider.cartethyia = {
75
- npm: "@ai-sdk/openai-compatible",
84
+ const prior = config.provider[providerId];
85
+ const otherProviders = Object.keys(config.provider).filter((id) => id !== providerId);
86
+ config.provider[providerId] = {
87
+ npm: input.sdk ?? prior?.npm ?? DEFAULT_SDK,
76
88
  options: {
77
89
  ...(prior?.options ?? {}),
78
90
  baseURL: ensureV1Suffix(input.endpoint),
@@ -82,47 +94,85 @@ export const opencodeSpec: InjectorSpec = {
82
94
  };
83
95
 
84
96
  const activeModel = input.activeModel ?? input.modelIds[0] ?? "";
85
- config.model = activeModel === "" ? "" : `cartethyia/${activeModel}`;
97
+ config.model = activeModel === "" ? "" : `${providerId}/${activeModel}`;
98
+
99
+ if (input.websearch !== undefined) {
100
+ config.websearch = { provider: input.websearch };
101
+ }
102
+
103
+ // YOLO mode: auto-approve shell/read/write. When on, write OpenCode's
104
+ // permission rules; when off, leave whatever the user had (or nothing).
105
+ if (input.bypassPermissions === true) {
106
+ config.permissions = [
107
+ { action: "shell", resource: "*", effect: "allow" },
108
+ { action: "external_directory", resource: "*", effect: "allow" },
109
+ { action: "question", resource: "*", effect: "deny" },
110
+ ];
111
+ } else if (input.bypassPermissions === false) {
112
+ delete config.permissions;
113
+ }
86
114
 
87
115
  if (!config.agent) config.agent = {};
88
116
  const subagentModel = input.subagentModel ?? input.modelIds[0] ?? activeModel;
89
117
  config.agent.explorer = {
90
118
  description: "Fast explorer subagent",
91
119
  mode: "subagent",
92
- model: `cartethyia/${subagentModel}`,
120
+ model: `${providerId}/${subagentModel}`,
93
121
  };
94
122
 
95
- await writeJsonFile(path, config);
123
+ await writeJsonFileWithBackup(path, config);
124
+
125
+ // Write (or refresh) the websearch provider plugin when web search is
126
+ // routed through the gateway. The plugin id follows the provider name.
127
+ if (input.websearch !== undefined) {
128
+ await writeWebsearchPlugin(opencodeSpec.resolveDir?.() ?? join(homeDir(), ".config", "opencode"), providerId);
129
+ }
130
+
131
+ return otherProviders.length > 0
132
+ ? [
133
+ `Config already has other provider(s): ${otherProviders.join(", ")}. They were kept as-is — only ${providerId} was written/updated.`,
134
+ ]
135
+ : undefined;
96
136
  },
97
137
 
98
138
  async reset(path) {
99
139
  const config = (await readJsonFile(path)) as OpencodeConfig | null;
100
140
  if (!config) return false;
101
141
 
142
+ const providerId = config.websearch?.provider ?? "cartethyia";
143
+
102
144
  if (config.provider) {
103
- delete config.provider.cartethyia;
145
+ delete config.provider[providerId];
104
146
  if (Object.keys(config.provider).length === 0) delete config.provider;
105
147
  }
106
148
 
107
- if (typeof config.model === "string" && config.model.startsWith("cartethyia/")) {
149
+ if (typeof config.model === "string" && config.model.startsWith(`${providerId}/`)) {
108
150
  delete config.model;
109
151
  }
110
152
 
111
- if (config.agent?.explorer?.model?.startsWith("cartethyia/")) {
153
+ if (config.agent?.explorer?.model?.startsWith(`${providerId}/`)) {
112
154
  delete config.agent.explorer;
113
155
  if (Object.keys(config.agent).length === 0) delete config.agent;
114
156
  }
115
157
 
116
- await writeJsonFile(path, config);
158
+ if (config.websearch) {
159
+ delete config.websearch;
160
+ }
161
+
162
+ await writeJsonFileWithBackup(path, config);
163
+
164
+ // Remove the generated websearch plugin when present.
165
+ await removeWebsearchPlugin(opencodeSpec.resolveDir?.() ?? join(homeDir(), ".config", "opencode"), providerId);
117
166
  },
118
167
 
119
168
  download(input) {
120
169
  const activeModel = input.activeModel ?? input.modelIds[0] ?? "";
121
170
  const subagentModel = input.subagentModel ?? input.modelIds[0] ?? activeModel;
171
+ const providerId = input.websearch ?? "cartethyia";
122
172
  const config: OpencodeConfig = {
123
173
  provider: {
124
- cartethyia: {
125
- npm: "@ai-sdk/openai-compatible",
174
+ [providerId]: {
175
+ npm: input.sdk ?? DEFAULT_SDK,
126
176
  options: {
127
177
  baseURL: ensureV1Suffix(input.endpoint),
128
178
  apiKey: input.apiKey,
@@ -130,12 +180,13 @@ export const opencodeSpec: InjectorSpec = {
130
180
  models: buildOpencodeModels(input.modelIds),
131
181
  },
132
182
  },
133
- model: activeModel === "" ? "" : `cartethyia/${activeModel}`,
183
+ model: activeModel === "" ? "" : `${providerId}/${activeModel}`,
184
+ ...(input.websearch !== undefined ? { websearch: { provider: input.websearch } } : {}),
134
185
  agent: {
135
186
  explorer: {
136
187
  description: "Fast explorer subagent",
137
188
  mode: "subagent",
138
- model: `cartethyia/${subagentModel}`,
189
+ model: `${providerId}/${subagentModel}`,
139
190
  },
140
191
  },
141
192
  };
@@ -102,6 +102,10 @@ function parseApplyInput(
102
102
  ...(value.mode === "file" || value.mode === "remote" || value.mode === "both"
103
103
  ? { mode: value.mode }
104
104
  : {}),
105
+ ...(typeof value.sdk === "string" && value.sdk.length > 0 ? { sdk: value.sdk } : {}),
106
+ ...(typeof value.websearch === "string" && value.websearch.length > 0
107
+ ? { websearch: value.websearch }
108
+ : {}),
105
109
  };
106
110
  }
107
111
 
@@ -128,6 +132,8 @@ const applyBody = t.Object({
128
132
  }),
129
133
  ),
130
134
  bypassPermissions: t.Optional(t.Boolean()),
135
+ sdk: t.Optional(t.String()),
136
+ websearch: t.Optional(t.String()),
131
137
  });
132
138
 
133
139
  const mappingBody = t.Object({
@@ -44,8 +44,6 @@ function toToolRegistryEntry(def: ToolDef): ToolRegistryEntry {
44
44
  ...(def.settingsFile === undefined ? {} : { settingsFile: def.settingsFile }),
45
45
  ...(def.docsUrl === undefined ? {} : { docsUrl: def.docsUrl }),
46
46
  ...(def.notes === undefined ? {} : { notes: def.notes }),
47
- ...(def.guideSteps === undefined ? {} : { guideSteps: def.guideSteps }),
48
- ...(def.codeBlock === undefined ? {} : { codeBlock: def.codeBlock }),
49
47
  };
50
48
  }
51
49
 
@@ -64,8 +62,8 @@ function applyMessage(outcome: ApplyOutcome, toolId: string, fileMessage: string
64
62
  case "remote":
65
63
  return `Saved the remote route for ${toolId}. No config file was written.`;
66
64
  case "none":
67
- // A guide tool has no config to inject; the injector's own message says
68
- // what to do instead (download the guide), so surface it verbatim.
65
+ // Nothing was written and no remote route was saved; surface the
66
+ // injector's own message verbatim.
69
67
  return fileMessage.length > 0 ? fileMessage : `Nothing to apply for ${toolId}.`;
70
68
  }
71
69
  }
@@ -274,8 +272,8 @@ export class CliToolService {
274
272
  *
275
273
  * `mode` defaults to `both` so the common single-machine case does the
276
274
  * obviously-right thing without the operator choosing. A tool that has no
277
- * injectable config (a guide tool) reports `remote` honestly instead of
278
- * claiming a file write it never performed.
275
+ * injectable config reports `remote` honestly instead of claiming a file
276
+ * write it never performed.
279
277
  */
280
278
  async applyConfig(
281
279
  tenantId: string,
@@ -292,12 +290,14 @@ export class CliToolService {
292
290
  let wroteFile = false;
293
291
  let settingsPath: string | undefined;
294
292
  let fileMessage = "";
293
+ let fileWarnings: readonly string[] | undefined;
295
294
 
296
295
  if (wantsFile) {
297
296
  const result: ApplyResult = await injector.apply(input);
298
297
  wroteFile = result.success;
299
298
  if (result.settingsPath !== undefined) settingsPath = result.settingsPath;
300
299
  fileMessage = result.message;
300
+ fileWarnings = result.warnings;
301
301
  }
302
302
 
303
303
  let savedRemoteRoute = false;
@@ -321,6 +321,7 @@ export class CliToolService {
321
321
  savedRemoteRoute,
322
322
  ...(settingsPath === undefined ? {} : { settingsPath }),
323
323
  message: applyMessage(outcome, toolId, fileMessage),
324
+ ...(fileWarnings && fileWarnings.length > 0 ? { warnings: fileWarnings } : {}),
324
325
  };
325
326
  }
326
327
 
@@ -232,6 +232,12 @@ export interface UsageRequestItem {
232
232
  * written before the column existed.
233
233
  */
234
234
  payloadCaptureState?: string;
235
+ /**
236
+ * True for a completed request whose output was far smaller than the model's
237
+ * ceiling and which ended with an ordinary `stop` (a "degenerate stop").
238
+ * Absent for everything else — including rows written before the column.
239
+ */
240
+ degenerateStop?: boolean;
235
241
  /**
236
242
  * Bitmask of the payload stages captured for this request: 1 request,
237
243
  * 2 provider request, 4 provider response, 8 client response. Read from the