rovecode 0.4.0-beta.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. package/README.md +57 -72
  2. package/THIRD_PARTY_NOTICES.md +0 -44
  3. package/bin/rovecode.ts +21 -0
  4. package/package.json +16 -38
  5. package/src/account/keys.ts +97 -0
  6. package/src/account/login.ts +158 -0
  7. package/src/account/provision.ts +47 -0
  8. package/src/account/store.ts +63 -0
  9. package/src/acp/server.ts +373 -0
  10. package/src/cli/account-cmd.ts +116 -0
  11. package/src/cli/connect.ts +244 -0
  12. package/src/cli/context-cmd.ts +199 -0
  13. package/src/cli/dispatch.ts +109 -0
  14. package/src/cli/doctor.ts +324 -0
  15. package/src/cli/export.ts +278 -0
  16. package/src/cli/help.ts +240 -0
  17. package/src/cli/is-tui-invocation.ts +8 -0
  18. package/src/cli/main.ts +599 -0
  19. package/src/cli/market-cmd.ts +658 -0
  20. package/src/cli/mcp-market-cmd.ts +299 -0
  21. package/src/cli/output.ts +382 -0
  22. package/src/cli/repl.ts +172 -0
  23. package/src/cli/resume.ts +32 -0
  24. package/src/cli/run-limits.ts +78 -0
  25. package/src/cli/runtime.ts +792 -0
  26. package/src/cli/setup.ts +187 -0
  27. package/src/cli/update-cmd.ts +78 -0
  28. package/src/cli/workflow-cmd.ts +100 -0
  29. package/src/coding/checkpoints.ts +270 -0
  30. package/src/coding/diff.ts +136 -0
  31. package/src/coding/files.ts +339 -0
  32. package/src/coding/hashline.ts +319 -0
  33. package/src/coding/lsp.ts +406 -0
  34. package/src/coding/repomap-cache.ts +99 -0
  35. package/src/coding/repomap-files.ts +110 -0
  36. package/src/coding/repomap.ts +392 -0
  37. package/src/core/compaction.ts +399 -0
  38. package/src/core/config.ts +289 -0
  39. package/src/core/context-report.ts +228 -0
  40. package/src/core/context.ts +60 -0
  41. package/src/core/count-remote.ts +107 -0
  42. package/src/core/execpolicy-rules.ts +196 -0
  43. package/src/core/execpolicy.ts +385 -0
  44. package/src/core/executor.ts +397 -0
  45. package/src/core/guardrails.ts +400 -0
  46. package/src/core/hooks.ts +398 -0
  47. package/src/core/images.ts +230 -0
  48. package/src/core/intro.ts +236 -0
  49. package/src/core/loop.ts +621 -0
  50. package/src/core/modes.ts +372 -0
  51. package/src/core/orchestrator.ts +207 -0
  52. package/src/core/reflection.ts +165 -0
  53. package/src/core/sandbox-config.ts +167 -0
  54. package/src/core/session-images.ts +73 -0
  55. package/src/core/session.ts +398 -0
  56. package/src/core/settings.ts +98 -0
  57. package/src/core/stuck-detector.ts +273 -0
  58. package/src/core/tasks.ts +374 -0
  59. package/src/core/token-scale.ts +108 -0
  60. package/src/core/tool-output-budget.ts +166 -0
  61. package/src/core/tools.ts +288 -0
  62. package/src/core/types.ts +330 -0
  63. package/src/core/update-check.ts +171 -0
  64. package/src/core/update.ts +158 -0
  65. package/src/core/usage.ts +204 -0
  66. package/src/core/validate.ts +121 -0
  67. package/src/core/verify-gate.ts +159 -0
  68. package/src/core/verify.ts +237 -0
  69. package/src/core/voice.ts +158 -0
  70. package/src/core/win-job.ts +183 -0
  71. package/src/design/audit.ts +797 -0
  72. package/src/design/direction.ts +190 -0
  73. package/src/design/rules.ts +157 -0
  74. package/src/eval/bench.ts +150 -0
  75. package/src/eval/gauntlet-runner.ts +218 -0
  76. package/src/eval/gauntlet.ts +226 -0
  77. package/src/eval/grader.ts +186 -0
  78. package/src/eval/record.ts +202 -0
  79. package/src/eval/redact.ts +141 -0
  80. package/src/eval/replay.ts +147 -0
  81. package/src/eval/trajectory.ts +373 -0
  82. package/src/index.ts +17 -0
  83. package/src/market/catalogs/mcp-docs.json +111 -0
  84. package/src/market/catalogs/plugins.json +111 -0
  85. package/src/market/catalogs/skills.json +478 -0
  86. package/src/market/clone.ts +72 -0
  87. package/src/market/context-cost.ts +121 -0
  88. package/src/market/digest.ts +106 -0
  89. package/src/market/index.ts +22 -0
  90. package/src/market/install.ts +578 -0
  91. package/src/market/manifest.ts +187 -0
  92. package/src/market/prereq.ts +145 -0
  93. package/src/market/registry.ts +363 -0
  94. package/src/market/resolve.ts +111 -0
  95. package/src/market/types.ts +236 -0
  96. package/src/market/validate.ts +227 -0
  97. package/src/mcp/client.ts +431 -0
  98. package/src/mcp/config.ts +239 -0
  99. package/src/mcp/local-package.ts +211 -0
  100. package/src/mcp/market-catalog.ts +84 -0
  101. package/src/mcp/market-install.ts +289 -0
  102. package/src/mcp/market.ts +0 -0
  103. package/src/mcp/tools.ts +131 -0
  104. package/src/mcp/trust.ts +49 -0
  105. package/src/memory/blocks.ts +175 -0
  106. package/src/memory/recall.ts +355 -0
  107. package/src/memory/store.ts +105 -0
  108. package/src/memory/tools.ts +99 -0
  109. package/src/plugins/cli.ts +123 -0
  110. package/src/plugins/discover.ts +108 -0
  111. package/src/plugins/index.ts +50 -0
  112. package/src/plugins/init.ts +140 -0
  113. package/src/plugins/install.ts +184 -0
  114. package/src/plugins/load.ts +149 -0
  115. package/src/plugins/manifest.ts +106 -0
  116. package/src/plugins/state.ts +83 -0
  117. package/src/providers/auth.ts +293 -0
  118. package/src/providers/cache.ts +223 -0
  119. package/src/providers/catalog-local.ts +160 -0
  120. package/src/providers/catalog.ts +408 -0
  121. package/src/providers/middleware-context.ts +86 -0
  122. package/src/providers/middleware.ts +373 -0
  123. package/src/providers/profile-glm53.ts +111 -0
  124. package/src/providers/profile-sonnet5-persona.ts +65 -0
  125. package/src/providers/profile-sonnet5-voice.ts +23 -0
  126. package/src/providers/profiles.ts +156 -0
  127. package/src/providers/provider-config.ts +311 -0
  128. package/src/providers/registry.ts +302 -0
  129. package/src/providers/response-validation.ts +80 -0
  130. package/src/providers/retry.ts +234 -0
  131. package/src/providers/router.ts +294 -0
  132. package/src/providers/sse.ts +26 -0
  133. package/src/providers/stream-errors.ts +117 -0
  134. package/src/providers/stream.ts +569 -0
  135. package/src/providers/thinking.ts +189 -0
  136. package/src/providers/wire-messages.ts +129 -0
  137. package/src/sdk/client.ts +225 -0
  138. package/src/sdk/index.ts +3 -0
  139. package/src/server/dashboard.ts +144 -0
  140. package/src/server/http.ts +343 -0
  141. package/src/server/openapi.ts +246 -0
  142. package/src/sextant/card-hits.ts +102 -0
  143. package/src/sextant/card-keys.ts +55 -0
  144. package/src/sextant/context-source.ts +157 -0
  145. package/src/sextant/draw-agents.ts +273 -0
  146. package/src/sextant/draw-code.ts +388 -0
  147. package/src/sextant/draw-context.ts +222 -0
  148. package/src/sextant/draw-frame.ts +164 -0
  149. package/src/sextant/draw-market.ts +573 -0
  150. package/src/sextant/draw-messages.ts +386 -0
  151. package/src/sextant/draw-pet.ts +230 -0
  152. package/src/sextant/draw-plan.ts +159 -0
  153. package/src/sextant/draw-tabs.ts +85 -0
  154. package/src/sextant/draw-util.ts +65 -0
  155. package/src/sextant/engine.ts +230 -0
  156. package/src/sextant/frame-hits.ts +25 -0
  157. package/src/sextant/frame.ts +101 -0
  158. package/src/sextant/git-status.ts +197 -0
  159. package/src/sextant/grid.ts +59 -0
  160. package/src/sextant/input.ts +119 -0
  161. package/src/sextant/keys.ts +488 -0
  162. package/src/sextant/layout.ts +86 -0
  163. package/src/sextant/local-commands.ts +156 -0
  164. package/src/sextant/market-source.ts +287 -0
  165. package/src/sextant/mentions.ts +141 -0
  166. package/src/sextant/message-hits.ts +26 -0
  167. package/src/sextant/model.ts +387 -0
  168. package/src/sextant/overlays.ts +451 -0
  169. package/src/sextant/panel-hits.ts +38 -0
  170. package/src/sextant/pet.ts +399 -0
  171. package/src/sextant/screen.ts +324 -0
  172. package/src/sextant/scroll-hits.ts +66 -0
  173. package/src/sextant/scrollbar.ts +82 -0
  174. package/src/sextant/selection.ts +123 -0
  175. package/src/sextant/sextant-bridge.ts +174 -0
  176. package/src/sextant/sextant-cards.ts +142 -0
  177. package/src/sextant/sextant-diff-base.ts +63 -0
  178. package/src/sextant/sextant-files.ts +154 -0
  179. package/src/sextant/sextant-frame-loop.ts +314 -0
  180. package/src/sextant/sextant-renderer.ts +478 -0
  181. package/src/sextant/sextant-repo.ts +131 -0
  182. package/src/sextant/theme.ts +66 -0
  183. package/src/sextant/tool-rows.ts +189 -0
  184. package/src/sextant/types.ts +473 -0
  185. package/src/skills/index.ts +306 -0
  186. package/src/skills/tools.ts +69 -0
  187. package/src/skills/versioned.ts +227 -0
  188. package/src/telemetry/otel.ts +353 -0
  189. package/src/telemetry/otlp.ts +68 -0
  190. package/src/tools/ask-user.ts +156 -0
  191. package/src/tools/design.ts +151 -0
  192. package/src/tools/evalcell.ts +338 -0
  193. package/src/tools/html-text.ts +139 -0
  194. package/src/tools/provider.ts +149 -0
  195. package/src/tools/task.ts +216 -0
  196. package/src/tools/todo.ts +320 -0
  197. package/src/tools/webfetch.ts +331 -0
  198. package/src/tui/app.ts +608 -0
  199. package/src/tui/attach.ts +127 -0
  200. package/src/tui/checkpoints-cmd.ts +70 -0
  201. package/src/tui/clipboard-image.ts +81 -0
  202. package/src/tui/commands.ts +277 -0
  203. package/src/tui/cost.ts +108 -0
  204. package/src/tui/info-cmd.ts +144 -0
  205. package/src/tui/mcp-cmd.ts +128 -0
  206. package/src/tui/modes-cmd.ts +45 -0
  207. package/src/tui/overlays.ts +97 -0
  208. package/src/tui/pi-renderer.ts +424 -0
  209. package/src/tui/providers-cmd.ts +366 -0
  210. package/src/tui/renderer.ts +101 -0
  211. package/src/tui/replay-marker.ts +29 -0
  212. package/src/tui/session-cmd.ts +146 -0
  213. package/src/tui/sextant-attach.ts +68 -0
  214. package/src/tui/sextant-io.ts +184 -0
  215. package/src/tui/sextant-smoke.ts +110 -0
  216. package/src/tui/smoke.ts +72 -0
  217. package/src/tui/theme.ts +59 -0
  218. package/src/tui/todo-label.ts +7 -0
  219. package/src/workflow/engine.ts +266 -0
  220. package/tsconfig.json +30 -0
  221. package/vendor/pi-tui/LICENSE +21 -0
  222. package/vendor/pi-tui/PATCHES.md +12 -0
  223. package/vendor/pi-tui/PROVENANCE.md +12 -0
  224. package/vendor/pi-tui/README.upstream.md +854 -0
  225. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  226. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  227. package/vendor/pi-tui/src/alt-screen-search.ts +158 -0
  228. package/vendor/pi-tui/src/autocomplete.ts +827 -0
  229. package/vendor/pi-tui/src/components/alt-screen-flash.ts +52 -0
  230. package/vendor/pi-tui/src/components/box.ts +138 -0
  231. package/vendor/pi-tui/src/components/cancellable-loader.ts +41 -0
  232. package/vendor/pi-tui/src/components/editor.ts +2364 -0
  233. package/vendor/pi-tui/src/components/h-stack.ts +45 -0
  234. package/vendor/pi-tui/src/components/image.ts +128 -0
  235. package/vendor/pi-tui/src/components/input.ts +448 -0
  236. package/vendor/pi-tui/src/components/loader.ts +93 -0
  237. package/vendor/pi-tui/src/components/markdown.ts +1016 -0
  238. package/vendor/pi-tui/src/components/scroll-view.ts +217 -0
  239. package/vendor/pi-tui/src/components/select-list.ts +230 -0
  240. package/vendor/pi-tui/src/components/settings-list.ts +277 -0
  241. package/vendor/pi-tui/src/components/spacer.ts +29 -0
  242. package/vendor/pi-tui/src/components/stack.ts +155 -0
  243. package/vendor/pi-tui/src/components/text.ts +108 -0
  244. package/vendor/pi-tui/src/components/truncated-text.ts +66 -0
  245. package/vendor/pi-tui/src/components/v-stack.ts +34 -0
  246. package/vendor/pi-tui/src/editor-component.ts +75 -0
  247. package/vendor/pi-tui/src/fuzzy.ts +138 -0
  248. package/vendor/pi-tui/src/index.ts +149 -0
  249. package/vendor/pi-tui/src/keybindings.ts +321 -0
  250. package/vendor/pi-tui/src/keys.ts +1402 -0
  251. package/vendor/pi-tui/src/kill-ring.ts +47 -0
  252. package/vendor/pi-tui/src/latex.ts +1381 -0
  253. package/vendor/pi-tui/src/layout-node.ts +52 -0
  254. package/vendor/pi-tui/src/layout.ts +411 -0
  255. package/vendor/pi-tui/src/native-modifiers.ts +60 -0
  256. package/vendor/pi-tui/src/native-module-path.ts +32 -0
  257. package/vendor/pi-tui/src/stdin-buffer.ts +445 -0
  258. package/vendor/pi-tui/src/terminal-colors.ts +74 -0
  259. package/vendor/pi-tui/src/terminal-image.ts +701 -0
  260. package/vendor/pi-tui/src/terminal.ts +554 -0
  261. package/vendor/pi-tui/src/tui-alt-screen.ts +1379 -0
  262. package/vendor/pi-tui/src/tui-main-screen.ts +655 -0
  263. package/vendor/pi-tui/src/tui.ts +1264 -0
  264. package/vendor/pi-tui/src/undo-stack.ts +29 -0
  265. package/vendor/pi-tui/src/utils.ts +1327 -0
  266. package/vendor/pi-tui/src/word-navigation.ts +118 -0
  267. package/vendor/pi-tui/test/test-themes.ts +39 -0
  268. package/vendor/pi-tui/test/virtual-terminal.ts +219 -0
  269. package/CHANGELOG.md +0 -527
  270. package/bin/rovecode.js +0 -24
  271. package/dist/cli/app-j6gn14w3.js +0 -2
  272. package/dist/cli/ask-user-cwstt8fz.js +0 -2
  273. package/dist/cli/auth-login-9bbp9915.js +0 -2
  274. package/dist/cli/auth-m8p9grty.js +0 -2
  275. package/dist/cli/bench-16zqdms5.js +0 -9
  276. package/dist/cli/catalog-1xchffa4.js +0 -2
  277. package/dist/cli/cli-1n1zb64f.js +0 -2
  278. package/dist/cli/client-2t9gjkck.js +0 -2
  279. package/dist/cli/commands-exafvm2b.js +0 -2
  280. package/dist/cli/connect-6zde0kn3.js +0 -2
  281. package/dist/cli/context-cmd-5t43wgqt.js +0 -2
  282. package/dist/cli/context-report-kt01pw8y.js +0 -2
  283. package/dist/cli/count-remote-ap7x3vh6.js +0 -2
  284. package/dist/cli/design-ne5zszyh.js +0 -2
  285. package/dist/cli/dispatch-2r5myxye.js +0 -2
  286. package/dist/cli/doctor-ws4fh4tn.js +0 -3
  287. package/dist/cli/executor-bdrjn634.js +0 -2
  288. package/dist/cli/export-1mxb9g5p.js +0 -2
  289. package/dist/cli/files-g104xghh.js +0 -2
  290. package/dist/cli/gauntlet-07xrjpj7.js +0 -2
  291. package/dist/cli/gauntlet-runner-xvy64436.js +0 -10
  292. package/dist/cli/gauntlet-wave3-jm91yt5w.js +0 -5
  293. package/dist/cli/gauntlet-wave4-r13py7p1.js +0 -14
  294. package/dist/cli/hashline-znvrat11.js +0 -2
  295. package/dist/cli/http-xafw6fsh.js +0 -143
  296. package/dist/cli/index-1sgjm25y.js +0 -2
  297. package/dist/cli/init-g2m0tn4m.js +0 -51
  298. package/dist/cli/install-avaqjjqq.js +0 -2
  299. package/dist/cli/loop-mmpfft01.js +0 -2
  300. package/dist/cli/main-0904f6ps.js +0 -5
  301. package/dist/cli/main-0ab9fc26.js +0 -9
  302. package/dist/cli/main-0jys2ccn.js +0 -3
  303. package/dist/cli/main-0mtcdbs7.js +0 -3
  304. package/dist/cli/main-0z1w2zsg.js +0 -3
  305. package/dist/cli/main-1dchs7xv.js +0 -18
  306. package/dist/cli/main-1ereejm1.js +0 -3
  307. package/dist/cli/main-1k1kw6b5.js +0 -3
  308. package/dist/cli/main-27y4sm2k.js +0 -38
  309. package/dist/cli/main-2wwjex5j.js +0 -58
  310. package/dist/cli/main-2yeveeve.js +0 -6
  311. package/dist/cli/main-2yfck9b5.js +0 -3
  312. package/dist/cli/main-2zmzgkwh.js +0 -3
  313. package/dist/cli/main-351pz3z7.js +0 -7
  314. package/dist/cli/main-3gjqfh7a.js +0 -6
  315. package/dist/cli/main-3nf3kgve.js +0 -3
  316. package/dist/cli/main-3pjrb2hd.js +0 -3
  317. package/dist/cli/main-3rxcvgna.js +0 -19
  318. package/dist/cli/main-4b3jgy66.js +0 -19
  319. package/dist/cli/main-4wndhjdc.js +0 -7
  320. package/dist/cli/main-4xcmvxnk.js +0 -3
  321. package/dist/cli/main-5tbz0wbz.js +0 -4
  322. package/dist/cli/main-5ywnwthm.js +0 -3
  323. package/dist/cli/main-6b62vkz0.js +0 -14
  324. package/dist/cli/main-6dnk69vp.js +0 -3
  325. package/dist/cli/main-6genrmhs.js +0 -136
  326. package/dist/cli/main-73g7eff4.js +0 -15
  327. package/dist/cli/main-7c5thhjd.js +0 -5
  328. package/dist/cli/main-7rn6bqje.js +0 -3
  329. package/dist/cli/main-80haw7qk.js +0 -4
  330. package/dist/cli/main-875s60s2.js +0 -4
  331. package/dist/cli/main-8kjxbpw4.js +0 -8
  332. package/dist/cli/main-90ds1z4e.js +0 -10
  333. package/dist/cli/main-9etavkew.js +0 -3
  334. package/dist/cli/main-a9njrkk1.js +0 -3
  335. package/dist/cli/main-aecrjq2d.js +0 -12
  336. package/dist/cli/main-ck9asesq.js +0 -9
  337. package/dist/cli/main-cta9racd.js +0 -4
  338. package/dist/cli/main-ddv7j2ag.js +0 -3
  339. package/dist/cli/main-dfreez27.js +0 -10
  340. package/dist/cli/main-f7rw7des.js +0 -3
  341. package/dist/cli/main-ggcn7rd7.js +0 -5
  342. package/dist/cli/main-gzkmycnv.js +0 -3
  343. package/dist/cli/main-hq51jg8v.js +0 -18
  344. package/dist/cli/main-jft389w9.js +0 -8
  345. package/dist/cli/main-k1eqkg83.js +0 -3
  346. package/dist/cli/main-k2y8a2aw.js +0 -9
  347. package/dist/cli/main-kcpbykxz.js +0 -4
  348. package/dist/cli/main-kd488vje.js +0 -22
  349. package/dist/cli/main-kh32yvgk.js +0 -5
  350. package/dist/cli/main-kqxnqjnv.js +0 -25
  351. package/dist/cli/main-kyn0xnsg.js +0 -3
  352. package/dist/cli/main-m1kk6fp5.js +0 -21
  353. package/dist/cli/main-mv40pcr2.js +0 -4
  354. package/dist/cli/main-n0t3973w.js +0 -3
  355. package/dist/cli/main-nqveez48.js +0 -4
  356. package/dist/cli/main-pknhvrmj.js +0 -3
  357. package/dist/cli/main-pn1w7a7j.js +0 -3
  358. package/dist/cli/main-prxxs70n.js +0 -4
  359. package/dist/cli/main-q3vsesf9.js +0 -3
  360. package/dist/cli/main-qsevpgsv.js +0 -3
  361. package/dist/cli/main-rdgdw24b.js +0 -25
  362. package/dist/cli/main-rfth4tbm.js +0 -16
  363. package/dist/cli/main-rg0wn0xf.js +0 -5
  364. package/dist/cli/main-sdmxhtv8.js +0 -4
  365. package/dist/cli/main-skbp13js.js +0 -18
  366. package/dist/cli/main-t4xnd213.js +0 -7
  367. package/dist/cli/main-vqak588n.js +0 -4
  368. package/dist/cli/main-w2n1303f.js +0 -9
  369. package/dist/cli/main-wbrdspr2.js +0 -5
  370. package/dist/cli/main-wsrg79c1.js +0 -7
  371. package/dist/cli/main-x4r0fne4.js +0 -5
  372. package/dist/cli/main-xea2f3tn.js +0 -6
  373. package/dist/cli/main-xg704a3c.js +0 -3
  374. package/dist/cli/main-xvnrabfp.js +0 -16
  375. package/dist/cli/main-xy53xf0r.js +0 -4
  376. package/dist/cli/main-y1fqy60y.js +0 -3
  377. package/dist/cli/main-yn8cd281.js +0 -34
  378. package/dist/cli/main-yr0ksc0h.js +0 -4
  379. package/dist/cli/main-z2ex2vyf.js +0 -4
  380. package/dist/cli/main-z3aayzvq.js +0 -3
  381. package/dist/cli/main-zaqh35jg.js +0 -3
  382. package/dist/cli/main-zc2e8e46.js +0 -4
  383. package/dist/cli/main-zzrfw6cf.js +0 -13
  384. package/dist/cli/main.js +0 -280
  385. package/dist/cli/market-cmd-e14kmx9n.js +0 -5
  386. package/dist/cli/mcp-login-wq7ktdek.js +0 -2
  387. package/dist/cli/mcp-market-cmd-9mg3jecy.js +0 -2
  388. package/dist/cli/notify-b7qc0cjb.js +0 -2
  389. package/dist/cli/oauth-z8whcgfx.js +0 -2
  390. package/dist/cli/output-b3ewj3ps.js +0 -16
  391. package/dist/cli/profiles-6mr5he5e.js +0 -2
  392. package/dist/cli/provider-config-g7j42q8x.js +0 -2
  393. package/dist/cli/provider-jr1y8vvm.js +0 -2
  394. package/dist/cli/registry-s8yk86g0.js +0 -2
  395. package/dist/cli/registry-t6p8d4mn.js +0 -2
  396. package/dist/cli/repl-bajwe1mh.js +0 -11
  397. package/dist/cli/resume-rwn9nz7y.js +0 -2
  398. package/dist/cli/run-flags-nah7ndpt.js +0 -2
  399. package/dist/cli/runtime-n7gafzhb.js +0 -2
  400. package/dist/cli/sandbox-config-emdy18x4.js +0 -2
  401. package/dist/cli/server-b0nvs2bn.js +0 -5
  402. package/dist/cli/session-arg-y75wd4kj.js +0 -2
  403. package/dist/cli/session-j62evmjq.js +0 -2
  404. package/dist/cli/sessions-cmd-tsnwz0ns.js +0 -7
  405. package/dist/cli/settings-df10wfez.js +0 -2
  406. package/dist/cli/setup-jzvv72fg.js +0 -2
  407. package/dist/cli/sextant-smoke-37m81ke6.js +0 -5
  408. package/dist/cli/skills-cmd-gjxnxnhx.js +0 -2
  409. package/dist/cli/smoke-p7748apt.js +0 -8
  410. package/dist/cli/start-chat-s4st3mm0.js +0 -12
  411. package/dist/cli/stream-gmeyewds.js +0 -2
  412. package/dist/cli/task-gh0kkp3n.js +0 -2
  413. package/dist/cli/tasks-z1kfpe8e.js +0 -2
  414. package/dist/cli/thinking-0eqkrz6t.js +0 -2
  415. package/dist/cli/todo-5brcrt9m.js +0 -2
  416. package/dist/cli/tools-7pzm0vj9.js +0 -2
  417. package/dist/cli/tools-s635p6s8.js +0 -2
  418. package/dist/cli/trust-cmd-cjav8zgm.js +0 -2
  419. package/dist/cli/update-check-pt31bm2f.js +0 -2
  420. package/dist/cli/update-cmd-tk131s9t.js +0 -2
  421. package/dist/cli/voice-56nabd8d.js +0 -2
  422. package/dist/cli/webfetch-xd8q596m.js +0 -2
  423. package/dist/cli/websearch-5hkf98k1.js +0 -2
  424. package/dist/cli/workflow-cmd-cy3cvzjp.js +0 -4
  425. package/dist/cli/workspace-q10g5z3e.js +0 -2
  426. package/dist/lib/index.js +0 -62
  427. package/dist/lib/models-index.json +0 -1
  428. package/dist/lib/plugins.js +0 -55
  429. package/dist/lib/providers.js +0 -17
  430. package/dist/lib/public-api.js +0 -20
  431. package/dist/lib/sdk.js +0 -360
  432. /package/{dist/cli → src/providers}/models-index.json +0 -0
@@ -0,0 +1,408 @@
1
+ /** Model catalog: capability/pricing lookups backed by the models.dev database (ADR: port #6).
2
+ *
3
+ * Source of truth is the offline snapshot bundled in @opencode-ai/models (generated from
4
+ * https://models.dev/api.json). Live refresh against models.dev itself is entirely OPT-IN
5
+ * (only when a `fetchFn` is supplied) and layers on top of — never replaces — the offline
6
+ * data, so a fresh `new ModelCatalog()` works fully offline with zero network access. */
7
+
8
+ import { readFileSync, writeFileSync, mkdirSync, statSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import type { ProviderMap, Model } from "@opencode-ai/models";
11
+ import { LOCAL_MODELS, PRICE_NOTES, PRICE_TIERS, type LocalModel, type PriceTier } from "./catalog-local.ts";
12
+
13
+ // Loaded lazily on the first lookup() so that importing catalog.ts costs nothing — the TUI paints its
14
+ // first frame before buildDef() is called.
15
+ //
16
+ // It reads src/providers/models-index.json, not @opencode-ai/models/snapshot. Same shape, same source,
17
+ // five fields per model instead of the whole models.dev record: the upstream snapshot is 4.26 MB and
18
+ // parsing it costs 55 MB resident, which every session paid on its first status update to answer "what
19
+ // is this model's window and price". The trimmed index is 1.1 MB. scripts/build-model-index.mjs
20
+ // generates it and `--check` fails when it and the installed package disagree, so an upgrade cannot
21
+ // leave the catalog quietly describing the previous release.
22
+ let _snapshot: ProviderMap | null = null;
23
+ function snapshotProviders(): ProviderMap {
24
+ if (_snapshot === null) {
25
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
26
+ _snapshot = (require("./models-index.json") as { providers: ProviderMap }).providers;
27
+ }
28
+ return _snapshot;
29
+ }
30
+
31
+ export interface ModelInfo {
32
+ provider: string;
33
+ model: string;
34
+ contextWindow?: number;
35
+ maxOutput?: number;
36
+ pricing?: { inputPerMTok?: number; outputPerMTok?: number; cacheReadPerMTok?: number; cacheWritePerMTok?: number };
37
+ supportsTools?: boolean;
38
+ supportsReasoning?: boolean;
39
+ /** where the numbers come from: models.dev (snapshot or live) or rovecode's own table (catalog-local.ts) for a
40
+ * model the snapshot lacks — `rovecode model show` says which, so a local price is never mistaken for models.dev's */
41
+ source?: "models.dev" | "local";
42
+ /** local entries only: the vendor page the numbers were read from, and when */
43
+ sourceNote?: string;
44
+ /** this model's price changes past a prompt size (xAI, Gemini). Attached whatever the row's source —
45
+ * models.dev has no field for it. A cost estimator that ignores it under-quotes a long prompt, and for
46
+ * `mode: "per-request"` it under-quotes the WHOLE request, not just the overflow. */
47
+ tier?: PriceTier;
48
+ /** a scheduled change or a condition attached to the price ("doubles on 2027-01-01") */
49
+ priceNote?: string;
50
+ }
51
+
52
+ export interface CatalogOptions {
53
+ fetchFn?: typeof fetch | null; // null = offline only; default null (live fetch is OPT-IN)
54
+ cacheDir?: string; // when live: cache api.json to <cacheDir>/models.json, ttl 24h
55
+ /** the local overlay (default catalog-local.ts LOCAL_MODELS); tests inject their own */
56
+ local?: Readonly<Record<string, Readonly<Record<string, LocalModel>>>>;
57
+ }
58
+
59
+ const CACHE_TTL_MS = 24 * 60 * 60_000;
60
+ const CACHE_FILE = "models.json";
61
+ const LIVE_URL = "https://models.dev/api.json";
62
+
63
+ /**
64
+ * rovecode provider id -> models.dev provider key. Derived by loading the offline snapshot
65
+ * (node_modules/@opencode-ai/models/dist/snapshot.js) and inspecting Object.keys(providers)
66
+ * directly rather than guessing:
67
+ * - together -> "togetherai" and fireworks -> "fireworks-ai" (not the bare names)
68
+ * - lmstudio -> "lmstudio" DOES exist in the snapshot (3 curated local models), so it maps
69
+ * - everything else here is an exact 1:1 id match confirmed present in the snapshot
70
+ *
71
+ * Deliberately absent (verified NOT a key in the snapshot -> lookup() returns undefined):
72
+ * kaesra (rovecode's own proxy brand, not a models.dev provider — its vendor-prefixed model ids
73
+ * resolve through VENDOR_PREFIX_MAP below instead), ollama (only "ollama-cloud" exists, not
74
+ * bare "ollama"), moondream, vllm.
75
+ */
76
+ const PROVIDER_MAP: Record<string, string> = {
77
+ openai: "openai",
78
+ anthropic: "anthropic",
79
+ deepseek: "deepseek",
80
+ groq: "groq",
81
+ openrouter: "openrouter",
82
+ lmstudio: "lmstudio",
83
+ together: "togetherai",
84
+ mistral: "mistral",
85
+ cerebras: "cerebras",
86
+ fireworks: "fireworks-ai",
87
+ perplexity: "perplexity",
88
+ xai: "xai",
89
+ // not built-in providers, but the ids people give `rovecode provider add` for these vendors' own endpoints
90
+ // (Google's OpenAI layer, Z.ai, Moonshot, Alibaba DashScope, MiniMax) — mapped so their models price and
91
+ // carry the reasoning flag like the built-ins (2026-09-04: models.dev snapshot 0.0.64 has all five)
92
+ google: "google",
93
+ gemini: "google",
94
+ zai: "zai",
95
+ moonshot: "moonshotai",
96
+ moonshotai: "moonshotai",
97
+ alibaba: "alibaba",
98
+ dashscope: "alibaba",
99
+ minimax: "minimax",
100
+ };
101
+
102
+ /**
103
+ * HuggingFace-style vendor prefix -> models.dev provider key, for aggregator providers
104
+ * (kaesra, or any custom base URL) that serve models under "vendor/model" ids. Lets
105
+ * lookup("kaesra", "zai-org/glm-5.3-flash") price against the zai snapshot entry.
106
+ * Verified against Object.keys(snapshotProviders): "zai", "deepseek", "moonshotai" all
107
+ * exist; there is NO bare "moonshot" key (only moonshotai/moonshotai-cn), hence the
108
+ * identity mapping for moonshotai.
109
+ */
110
+ const VENDOR_PREFIX_MAP: Record<string, string> = {
111
+ "zai-org": "zai",
112
+ "deepseek-ai": "deepseek",
113
+ "moonshotai": "moonshotai",
114
+ };
115
+
116
+ /** the tiered-price row for a model, if the vendor has one (case-insensitive, like every other lookup) */
117
+ function tierFor(providerId: string, modelId: string): PriceTier | undefined {
118
+ const table = PRICE_TIERS[providerId.toLowerCase()];
119
+ if (!table) return undefined;
120
+ const key = Object.keys(table).find((k) => k.toLowerCase() === modelId.toLowerCase());
121
+ return key === undefined ? undefined : table[key];
122
+ }
123
+
124
+ function noteFor(providerId: string, modelId: string): string | undefined {
125
+ const table = PRICE_NOTES[providerId.toLowerCase()];
126
+ if (!table) return undefined;
127
+ const key = Object.keys(table).find((k) => k.toLowerCase() === modelId.toLowerCase());
128
+ return key === undefined ? undefined : table[key];
129
+ }
130
+
131
+ /** The four rates one request is billed at. Same field names as `ModelInfo.pricing`, so a caller can pass
132
+ * either where a rate table is wanted. */
133
+ export interface Rates { inputPerMTok?: number; outputPerMTok?: number; cacheReadPerMTok?: number; cacheWritePerMTok?: number }
134
+
135
+ /** What `promptTokens` actually costs on this model, split the way the vendor bills it. PURE — no I/O, no
136
+ * clock, no rounding: a cost function multiplies these and decides its own precision.
137
+ *
138
+ * Read `promptBase`/`promptAbove` as token counts to multiply by `base`/`above`; `request` is the rate
139
+ * table for everything that is not the prompt (output, cache) on this request, because both vendors we
140
+ * have select the OUTPUT price by prompt size too. For a flat model the split is trivial (everything in
141
+ * `promptBase`) so a caller never needs to branch on whether a tier exists — only on whether it wants the
142
+ * detail. */
143
+ export interface RateBreakdown {
144
+ mode: "flat" | "per-request" | "marginal";
145
+ /** true when the prompt crossed a threshold and the upper rates are in play */
146
+ tierApplied: boolean;
147
+ thresholdTokens?: number;
148
+ promptBase: number;
149
+ promptAbove: number;
150
+ base: Rates;
151
+ above?: Rates;
152
+ request: Rates;
153
+ }
154
+
155
+ /** The rate breakdown for a prompt of `promptTokens` on this model. A negative or non-finite count is
156
+ * treated as 0 — an estimator must not be able to produce a negative bill. */
157
+ export function ratesFor(info: ModelInfo, promptTokens: number): RateBreakdown {
158
+ const base: Rates = { ...(info.pricing ?? {}) };
159
+ const n = Number.isFinite(promptTokens) && promptTokens > 0 ? Math.floor(promptTokens) : 0;
160
+ const t = info.tier;
161
+ if (!t || n <= t.thresholdTokens) {
162
+ return { mode: t ? t.mode : "flat", tierApplied: false, ...(t ? { thresholdTokens: t.thresholdTokens } : {}),
163
+ promptBase: n, promptAbove: 0, base, ...(t ? { above: ratesOf(t) } : {}), request: base };
164
+ }
165
+ const above = ratesOf(t);
166
+ return t.mode === "per-request"
167
+ // every token of the prompt, and this request's output, at the upper rate
168
+ ? { mode: "per-request", tierApplied: true, thresholdTokens: t.thresholdTokens, promptBase: 0, promptAbove: n, base, above, request: above }
169
+ // only the overflow at the upper rate; output follows the upper table, which is the conservative read
170
+ : { mode: "marginal", tierApplied: true, thresholdTokens: t.thresholdTokens, promptBase: t.thresholdTokens, promptAbove: n - t.thresholdTokens, base, above, request: above };
171
+ }
172
+
173
+ function ratesOf(t: PriceTier): Rates {
174
+ return { inputPerMTok: t.above.input, outputPerMTok: t.above.output,
175
+ ...(t.above.cacheRead !== undefined ? { cacheReadPerMTok: t.above.cacheRead } : {}),
176
+ ...(t.above.cacheWrite !== undefined ? { cacheWritePerMTok: t.above.cacheWrite } : {}) };
177
+ }
178
+
179
+ /** The price lines a surface shows — one place, so `model show`, /cost and the market cannot drift apart.
180
+ * Empty when the model is unpriced. */
181
+ export function describePricing(info: ModelInfo): string[] {
182
+ const p = info.pricing;
183
+ if (!p || (p.inputPerMTok === undefined && p.outputPerMTok === undefined)) return [];
184
+ const money = (n?: number): string => (n === undefined ? "—" : `$${n}`);
185
+ const lines = [`${money(p.inputPerMTok)} in / ${money(p.outputPerMTok)} out per 1M${p.cacheReadPerMTok !== undefined ? ` · cache read ${money(p.cacheReadPerMTok)}` : ""}${p.cacheWritePerMTok !== undefined ? ` · cache write ${money(p.cacheWritePerMTok)}` : ""}`];
186
+ if (info.tier) {
187
+ const t = info.tier;
188
+ lines.push(t.mode === "per-request"
189
+ ? `over ${t.thresholdTokens.toLocaleString("en-US")} prompt tokens the WHOLE request bills at ${money(t.above.input)} in / ${money(t.above.output)} out`
190
+ : `tokens past ${t.thresholdTokens.toLocaleString("en-US")} bill at ${money(t.above.input)} in / ${money(t.above.output)} out`);
191
+ }
192
+ if (info.priceNote) lines.push(info.priceNote);
193
+ return lines;
194
+ }
195
+
196
+ /** a local overlay row in the snapshot's shape, so one toModelInfo serves both */
197
+ function localAsModel(id: string, m: LocalModel): Model {
198
+ return {
199
+ id, name: id, description: `${m.source} — checked ${m.checked}`, attachment: false, reasoning: m.reasoning, tool_call: m.toolCall,
200
+ release_date: m.checked, modalities: { input: m.image ? ["text", "image"] : ["text"], output: ["text"] },
201
+ limit: { context: m.context, ...(m.output !== undefined ? { output: m.output } : {}) },
202
+ cost: { input: m.cost.input, output: m.cost.output, ...(m.cost.cacheRead !== undefined ? { cache_read: m.cost.cacheRead } : {}), ...(m.cost.cacheWrite !== undefined ? { cache_write: m.cost.cacheWrite } : {}) },
203
+ } as unknown as Model;
204
+ }
205
+
206
+ function isProviderMap(value: unknown): value is ProviderMap {
207
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
208
+ for (const entry of Object.values(value as Record<string, unknown>)) {
209
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return false;
210
+ const models = (entry as { models?: unknown }).models;
211
+ if (typeof models !== "object" || models === null || Array.isArray(models)) return false;
212
+ }
213
+ return true;
214
+ }
215
+
216
+ /** exact match, then case-insensitive, then match after stripping a single leading "vendor/" prefix. */
217
+ function findModelKey(models: Record<string, Model>, modelId: string): string | undefined {
218
+ if (modelId in models) return modelId;
219
+ const lower = modelId.toLowerCase();
220
+ for (const key of Object.keys(models)) {
221
+ if (key.toLowerCase() === lower) return key;
222
+ }
223
+ const slashIndex = modelId.indexOf("/");
224
+ if (slashIndex === -1) return undefined;
225
+ const stripped = modelId.slice(slashIndex + 1);
226
+ if (stripped in models) return stripped;
227
+ const strippedLower = stripped.toLowerCase();
228
+ for (const key of Object.keys(models)) {
229
+ if (key.toLowerCase() === strippedLower) return key;
230
+ }
231
+ return undefined;
232
+ }
233
+
234
+ function toModelInfo(providerId: string, modelId: string, model: Model): ModelInfo {
235
+ const info: ModelInfo = { provider: providerId, model: modelId };
236
+ if (model.limit?.context !== undefined) info.contextWindow = model.limit.context;
237
+ if (model.limit?.output !== undefined) info.maxOutput = model.limit.output;
238
+ const cost = model.cost;
239
+ if (cost) {
240
+ const pricing: NonNullable<ModelInfo["pricing"]> = {};
241
+ if (cost.input !== undefined) pricing.inputPerMTok = cost.input;
242
+ if (cost.output !== undefined) pricing.outputPerMTok = cost.output;
243
+ if (cost.cache_read !== undefined) pricing.cacheReadPerMTok = cost.cache_read;
244
+ if (cost.cache_write !== undefined) pricing.cacheWritePerMTok = cost.cache_write;
245
+ if (Object.keys(pricing).length > 0) info.pricing = pricing;
246
+ }
247
+ if (model.tool_call !== undefined) info.supportsTools = model.tool_call;
248
+ if (model.reasoning !== undefined) info.supportsReasoning = model.reasoning;
249
+ return info;
250
+ }
251
+
252
+ export class ModelCatalog {
253
+ private readonly fetchFn: typeof fetch | null;
254
+ private readonly cacheDir: string | undefined;
255
+ private readonly local: Readonly<Record<string, Readonly<Record<string, LocalModel>>>>;
256
+ private liveProviders: ProviderMap | null = null;
257
+ private triedDiskCache = false;
258
+
259
+ constructor(opts: CatalogOptions = {}) {
260
+ this.fetchFn = opts.fetchFn ?? null;
261
+ this.cacheDir = opts.cacheDir;
262
+ this.local = opts.local ?? LOCAL_MODELS;
263
+ }
264
+
265
+ /** offline snapshot first; live cache layered on top when enabled.
266
+ * Resolution order: (1) the rovecode provider's own models.dev entry (PROVIDER_MAP), then
267
+ * (2) the model id's vendor prefix (VENDOR_PREFIX_MAP) with the prefix stripped — the
268
+ * path that makes aggregator providers like kaesra (default model zai-org/glm-5.3-flash)
269
+ * priceable. The vendor hit reports the vendor as `provider`, naming the pricing source. */
270
+ lookup(providerId: string, modelId: string): ModelInfo | undefined {
271
+ const hit = this.resolve(providerId, modelId);
272
+ if (!hit) return undefined;
273
+ const info = toModelInfo(hit.as, hit.key, hit.model);
274
+ info.source = hit.local ? "local" : "models.dev";
275
+ if (hit.local) info.sourceNote = `${hit.local.source}, checked ${hit.local.checked}`;
276
+ const tier = tierFor(providerId, modelId) ?? tierFor(providerId, hit.key);
277
+ if (tier) info.tier = tier;
278
+ const note = noteFor(providerId, modelId) ?? noteFor(providerId, hit.key);
279
+ if (note) info.priceNote = note;
280
+ return info;
281
+ }
282
+
283
+ /** port #34: does the model accept image input? models.dev `modalities.input` (every entry of
284
+ * the bundled snapshot carries it — 7479/7479 at @opencode-ai/models 0.0.61). undefined = the
285
+ * model is not in the catalog (custom base URLs, local servers, aggregator ids without a
286
+ * vendor prefix): callers treat unknown as "send the image" — a wrong guess then fails loudly
287
+ * at the provider (HTTP 400 → error turn) instead of silently turning the user's image into
288
+ * text. Same resolution order as lookup(). */
289
+ supportsImages(providerId: string, modelId: string): boolean | undefined {
290
+ const hit = this.resolve(providerId, modelId);
291
+ const input: unknown = hit?.model.modalities?.input;
292
+ return Array.isArray(input) ? input.includes("image") : undefined;
293
+ }
294
+
295
+ /** The catalog entry behind lookup()/supportsImages(): provider key candidates in order, live
296
+ * layer over snapshot. `as` = the provider name reported (the vendor for a prefix hit). */
297
+ private resolve(providerId: string, modelId: string): { as: string; key: string; model: Model; local?: LocalModel } | undefined {
298
+ this.loadDiskCacheOnce();
299
+
300
+ const candidates: { key: string; as: string; model: string }[] = [];
301
+ const direct = PROVIDER_MAP[providerId];
302
+ if (direct) candidates.push({ key: direct, as: providerId, model: modelId });
303
+ const slash = modelId.indexOf("/");
304
+ if (slash > 0) {
305
+ const vendorKey = VENDOR_PREFIX_MAP[modelId.slice(0, slash).toLowerCase()];
306
+ if (vendorKey && vendorKey !== direct) candidates.push({ key: vendorKey, as: vendorKey, model: modelId.slice(slash + 1) });
307
+ }
308
+
309
+ for (const c of candidates) {
310
+ const hit = this.findIn(c.key, c.model);
311
+ if (hit) return { as: c.as, ...hit };
312
+ }
313
+ return undefined;
314
+ }
315
+
316
+ /** one provider key: live layer, then the snapshot, then rovecode's own table (catalog-local.ts). The
317
+ * overlay does not shadow a models.dev entry, so a later snapshot that adds a model wins by construction
318
+ * — EXCEPT for a row marked `override: true`, which is how a vendor page that disagrees with the
319
+ * snapshot wins (today: DeepSeek's v4 family, where models.dev carries about a third of the published
320
+ * price). An override says which page and which day in its `source`, and `model show` prints it. */
321
+ private findIn(key: string, modelId: string): { key: string; model: Model; local?: LocalModel } | undefined {
322
+ const overriding = this.local[key];
323
+ if (overriding) {
324
+ const found = findModelKey(overriding as unknown as Record<string, Model>, modelId);
325
+ const row = found === undefined ? undefined : overriding[found];
326
+ if (found !== undefined && row?.override === true) return { key: found, model: localAsModel(found, row), local: row };
327
+ }
328
+
329
+ const live = this.liveProviders?.[key];
330
+ if (live) {
331
+ const found = findModelKey(live.models, modelId);
332
+ if (found !== undefined) {
333
+ const model = live.models[found];
334
+ if (model) return { key: found, model };
335
+ }
336
+ }
337
+
338
+ const snap = snapshotProviders()[key];
339
+ if (snap) {
340
+ const found = findModelKey(snap.models, modelId);
341
+ if (found !== undefined) {
342
+ const model = snap.models[found];
343
+ if (model) return { key: found, model };
344
+ }
345
+ }
346
+
347
+ const table = this.local[key];
348
+ if (!table) return undefined;
349
+ const found = findModelKey(table as unknown as Record<string, Model>, modelId);
350
+ if (found === undefined) return undefined;
351
+ const local = table[found]!;
352
+ return { key: found, model: localAsModel(found, local), local };
353
+ }
354
+
355
+ /** live fetch https://models.dev/api.json when fetchFn set; false on failure, never throws. */
356
+ async refresh(): Promise<boolean> {
357
+ if (!this.fetchFn) return false;
358
+ try {
359
+ const res = await this.fetchFn(LIVE_URL);
360
+ if (!res.ok) return false;
361
+ const json: unknown = await res.json();
362
+ if (!isProviderMap(json)) return false;
363
+ this.liveProviders = json;
364
+ this.triedDiskCache = true;
365
+ this.writeDiskCache(json);
366
+ return true;
367
+ } catch {
368
+ return false;
369
+ }
370
+ }
371
+
372
+ /** Load a still-fresh (<24h) on-disk cache at most once per instance. Corrupted/stale/missing
373
+ * cache is silently ignored — offline snapshot remains the fallback. Only consulted when a
374
+ * fetchFn is configured, matching "live fetch is OPT-IN" (a purely-offline catalog never
375
+ * touches disk). */
376
+ private loadDiskCacheOnce(): void {
377
+ if (this.triedDiskCache) return;
378
+ this.triedDiskCache = true;
379
+ if (!this.fetchFn || !this.cacheDir) return;
380
+ try {
381
+ const path = join(this.cacheDir, CACHE_FILE);
382
+ const age = Date.now() - statSync(path).mtimeMs;
383
+ if (age > CACHE_TTL_MS) return;
384
+ const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
385
+ if (isProviderMap(parsed)) this.liveProviders = parsed;
386
+ } catch {
387
+ // missing file, unreadable, or malformed JSON/shape -> treated as "no cache"
388
+ }
389
+ }
390
+
391
+ private writeDiskCache(data: ProviderMap): void {
392
+ if (!this.cacheDir) return;
393
+ try {
394
+ mkdirSync(this.cacheDir, { recursive: true });
395
+ writeFileSync(join(this.cacheDir, CACHE_FILE), JSON.stringify(data));
396
+ } catch {
397
+ // best-effort cache write; a failed write must not fail refresh()
398
+ }
399
+ }
400
+ }
401
+
402
+ let offlineCatalog: ModelCatalog | null = null;
403
+
404
+ /** Port #34 adapter hook: image capability of a ModelRef from the offline snapshot (no fetchFn →
405
+ * never touches network or disk). undefined = unknown model (see ModelCatalog.supportsImages). */
406
+ export function supportsImages(ref: { provider: string; model: string }, catalog?: ModelCatalog): boolean | undefined {
407
+ return (catalog ?? (offlineCatalog ??= new ModelCatalog())).supportsImages(ref.provider, ref.model);
408
+ }
@@ -0,0 +1,86 @@
1
+ /** Context lowering for non-native tool calling (MED-2 of port #7).
2
+ *
3
+ * Port of senpi's transformContext (research/source_snapshots/code-yeongyu-senpi @ a0f26a6,
4
+ * packages/ai/src/tool-call-middleware/context-transformer.ts:165-181, 209-261): once the
5
+ * middleware is driving a conversation, follow-up requests must not contain native tool
6
+ * artifacts — a genuinely non-native model cannot consume `tool_calls` parts, `role:"tool"`
7
+ * messages, or a `tools` array. So, per upstream:
8
+ *
9
+ * - assistant tool_call parts → protocol TEXT via formatToolCall (hermes; json-mix.ts:635-641)
10
+ * - tool-result messages → user-role text via formatToolResponse (hermes.ts:46-60)
11
+ * - options.tools → stripped (provider sees a tool-free request;
12
+ * context-transformer.ts:170)
13
+ *
14
+ * Rovecode split vs upstream: transformContext also injects the tools system prompt; rovecode does
15
+ * that in runtime.buildDef (toolPromptBlock), so this module only lowers messages + options.
16
+ *
17
+ * Activation: upstream applies transformContext whenever a text protocol is configured for
18
+ * the model. Rovecode wraps every provider stream, so default is AUTO-DETECT — middleware mode is
19
+ * observable exactly when history contains a tool call the middleware itself minted (ids are
20
+ * prefixed "textcall_"; native providers never produce that prefix because native turns pass
21
+ * through byte-identical). `lowerContext: true|false` in MiddlewareOptions overrides. */
22
+
23
+ import type { Message, MessagePart, StreamOptions } from "../core/types.ts";
24
+
25
+ /** Prefix of middleware-minted tool-call ids (withToolCallParsing). */
26
+ export const TEXTCALL_ID_PREFIX = "textcall_";
27
+
28
+ /** Hermes formatToolCall — the exact markup parseToolCalls accepts (json-mix.ts:635-641). */
29
+ export function formatToolCallText(tool: string, args: unknown): string {
30
+ return `<tool_call>\n${JSON.stringify({ name: tool, arguments: args })}\n</tool_call>`;
31
+ }
32
+
33
+ /** Hermes formatToolResponse (hermes.ts:46-60): tool NAME + content, call id dropped. */
34
+ export function formatToolResponseText(tool: string, output: string): string {
35
+ return `<tool_response>${JSON.stringify({ name: tool, content: output })}</tool_response>`;
36
+ }
37
+
38
+ function middlewareMintedCallsIn(messages: readonly Message[]): boolean {
39
+ return messages.some(
40
+ (m) => m.role === "assistant" && m.parts.some((p) => p.kind === "tool_call" && p.id.startsWith(TEXTCALL_ID_PREFIX)),
41
+ );
42
+ }
43
+
44
+ /** Lower native tool history to text protocol. No-op (same references) unless middleware mode
45
+ * is active: `mode` true forces, false disables, undefined auto-detects minted ids (above).
46
+ * Never mutates inputs — lowered messages/options are fresh objects (upstream: "original is
47
+ * not mutated", context-transformer.ts:163). */
48
+ export function lowerNonNativeContext(
49
+ messages: Message[],
50
+ options: StreamOptions | undefined,
51
+ mode?: boolean,
52
+ ): { messages: Message[]; options: StreamOptions | undefined } {
53
+ const active = mode ?? middlewareMintedCallsIn(messages);
54
+ if (!active) return { messages, options };
55
+
56
+ // callId → tool name, so tool results can be rendered under the NAME hermes expects
57
+ // (rovecode ToolResultPart carries only callId; upstream ToolResultMessage carries toolName).
58
+ const toolNames = new Map<string, string>();
59
+ for (const m of messages) {
60
+ for (const p of m.parts) if (p.kind === "tool_call") toolNames.set(p.id, p.tool);
61
+ }
62
+
63
+ const lowered = messages.map((m): Message => {
64
+ if (m.role === "assistant" && m.parts.some((p) => p.kind === "tool_call")) {
65
+ // transformAssistantMessage (:209-246): text/thinking kept, toolCall → text.
66
+ const parts: MessagePart[] = m.parts.map((p) =>
67
+ p.kind === "tool_call" ? { kind: "text", text: formatToolCallText(p.tool, p.args) } : p,
68
+ );
69
+ return { ...m, parts };
70
+ }
71
+ if (m.role === "tool") {
72
+ // transformToolResultMessage (:251-261): tool result → user message with text content.
73
+ const parts: MessagePart[] = m.parts.map((p) =>
74
+ p.kind === "tool_result"
75
+ ? { kind: "text", text: formatToolResponseText(toolNames.get(p.callId) ?? p.callId, p.output) }
76
+ : p,
77
+ );
78
+ return { ...m, role: "user", parts };
79
+ }
80
+ return m;
81
+ });
82
+
83
+ if (options === undefined || options.tools === undefined) return { messages: lowered, options };
84
+ const { tools: _stripped, ...toolFree } = options; // strip tools key entirely (:170)
85
+ return { messages: lowered, options: toolFree };
86
+ }