alchemylab-opencode 1.0.7 → 1.0.9

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 (391) hide show
  1. package/dist/{chunk-B3FN2B2N.js → chunk-3PZZ2D23.js} +7 -3
  2. package/dist/chunk-3PZZ2D23.js.map +7 -0
  3. package/dist/{chunk-LU5XVQUD.js → chunk-EGZMJTLE.js} +7 -7
  4. package/dist/{chunk-LU5XVQUD.js.map → chunk-EGZMJTLE.js.map} +2 -2
  5. package/dist/{chunk-23UYOGZG.js → chunk-ORHZOVWK.js} +6 -6
  6. package/dist/{chunk-TASPMBUL.js → chunk-SV5NEV33.js} +5 -5
  7. package/dist/{chunk-SM6WZDN6.js → chunk-UKQIL5XA.js} +18 -12
  8. package/dist/chunk-UKQIL5XA.js.map +7 -0
  9. package/dist/{chunk-GSE2T5UY.js → chunk-ZFHDPNFZ.js} +4 -4
  10. package/dist/{mcp-XFGN3FWW.js → mcp-66BBNSWA.js} +3 -3
  11. package/dist/{registry-W4MBVBYD.js → registry-YVL3V6Z6.js} +4 -4
  12. package/dist/{run-73HUV4H3.js → run-65PIGE2A.js} +7 -7
  13. package/dist/{run-service-LHEVFBIU.js → run-service-AYATGPA6.js} +2 -2
  14. package/dist/run.js +1 -1
  15. package/dist/serve.js +1 -1
  16. package/dist/{server-2CPOG3LS.js → server-KCG3B4WQ.js} +7 -7
  17. package/dist/{session-M6HOZ56B.js → session-BR5MTI3X.js} +4 -4
  18. package/dist/{share-next-QRBVSD37.js → share-next-EPYQDJ32.js} +5 -5
  19. package/dist/{worktree-3GUHT2P7.js → worktree-PIVTZBUE.js} +6 -6
  20. package/package.json +2 -15
  21. package/packages/opencode/migration/20260409000000_add_session_metadata/migration.sql +1 -0
  22. package/build.mjs +0 -192
  23. package/dist/chunk-B3FN2B2N.js.map +0 -7
  24. package/dist/chunk-SM6WZDN6.js.map +0 -7
  25. package/packages/opencode/package.json +0 -138
  26. package/packages/opencode/src/account/account.sql.ts +0 -39
  27. package/packages/opencode/src/account/index.ts +0 -463
  28. package/packages/opencode/src/account/repo.ts +0 -163
  29. package/packages/opencode/src/account/schema.ts +0 -91
  30. package/packages/opencode/src/acp/README.md +0 -174
  31. package/packages/opencode/src/acp/agent.ts +0 -1847
  32. package/packages/opencode/src/acp/session.ts +0 -116
  33. package/packages/opencode/src/acp/types.ts +0 -24
  34. package/packages/opencode/src/agent/agent.ts +0 -420
  35. package/packages/opencode/src/agent/generate.txt +0 -75
  36. package/packages/opencode/src/agent/prompt/compaction.txt +0 -15
  37. package/packages/opencode/src/agent/prompt/explore.txt +0 -18
  38. package/packages/opencode/src/agent/prompt/summary.txt +0 -11
  39. package/packages/opencode/src/agent/prompt/title.txt +0 -44
  40. package/packages/opencode/src/auth/index.ts +0 -110
  41. package/packages/opencode/src/bus/bus-event.ts +0 -40
  42. package/packages/opencode/src/bus/global.ts +0 -10
  43. package/packages/opencode/src/bus/index.ts +0 -185
  44. package/packages/opencode/src/cli/bootstrap.ts +0 -17
  45. package/packages/opencode/src/cli/cmd/cmd.ts +0 -7
  46. package/packages/opencode/src/cli/cmd/run.ts +0 -689
  47. package/packages/opencode/src/cli/cmd/serve.ts +0 -24
  48. package/packages/opencode/src/cli/cmd/tui/event.ts +0 -49
  49. package/packages/opencode/src/cli/effect/prompt.ts +0 -25
  50. package/packages/opencode/src/cli/error.ts +0 -46
  51. package/packages/opencode/src/cli/heap.ts +0 -59
  52. package/packages/opencode/src/cli/logo.ts +0 -6
  53. package/packages/opencode/src/cli/network.ts +0 -60
  54. package/packages/opencode/src/cli/ui.ts +0 -133
  55. package/packages/opencode/src/cli/upgrade.ts +0 -31
  56. package/packages/opencode/src/command/index.ts +0 -195
  57. package/packages/opencode/src/command/template/initialize.txt +0 -66
  58. package/packages/opencode/src/command/template/review.txt +0 -101
  59. package/packages/opencode/src/config/config.ts +0 -1599
  60. package/packages/opencode/src/config/console-state.ts +0 -15
  61. package/packages/opencode/src/config/markdown.ts +0 -99
  62. package/packages/opencode/src/config/paths.ts +0 -167
  63. package/packages/opencode/src/config/tui-migrate.ts +0 -155
  64. package/packages/opencode/src/config/tui-schema.ts +0 -37
  65. package/packages/opencode/src/config/tui.ts +0 -179
  66. package/packages/opencode/src/control-plane/adaptors/index.ts +0 -20
  67. package/packages/opencode/src/control-plane/adaptors/worktree.ts +0 -42
  68. package/packages/opencode/src/control-plane/schema.ts +0 -17
  69. package/packages/opencode/src/control-plane/sse.ts +0 -66
  70. package/packages/opencode/src/control-plane/types.ts +0 -32
  71. package/packages/opencode/src/control-plane/workspace.sql.ts +0 -17
  72. package/packages/opencode/src/control-plane/workspace.ts +0 -168
  73. package/packages/opencode/src/effect/cross-spawn-spawner.ts +0 -502
  74. package/packages/opencode/src/effect/instance-ref.ts +0 -6
  75. package/packages/opencode/src/effect/instance-registry.ts +0 -12
  76. package/packages/opencode/src/effect/instance-state.ts +0 -82
  77. package/packages/opencode/src/effect/run-service.ts +0 -33
  78. package/packages/opencode/src/effect/runner.ts +0 -216
  79. package/packages/opencode/src/env/index.ts +0 -28
  80. package/packages/opencode/src/file/ignore.ts +0 -82
  81. package/packages/opencode/src/file/index.ts +0 -686
  82. package/packages/opencode/src/file/protected.ts +0 -59
  83. package/packages/opencode/src/file/ripgrep.ts +0 -376
  84. package/packages/opencode/src/file/time.ts +0 -133
  85. package/packages/opencode/src/file/watcher.ts +0 -173
  86. package/packages/opencode/src/filesystem/index.ts +0 -236
  87. package/packages/opencode/src/flag/flag.ts +0 -156
  88. package/packages/opencode/src/format/formatter.ts +0 -413
  89. package/packages/opencode/src/format/index.ts +0 -203
  90. package/packages/opencode/src/git/index.ts +0 -303
  91. package/packages/opencode/src/global/index.ts +0 -56
  92. package/packages/opencode/src/id/id.ts +0 -85
  93. package/packages/opencode/src/ide/index.ts +0 -74
  94. package/packages/opencode/src/installation/index.ts +0 -355
  95. package/packages/opencode/src/installation/meta.ts +0 -7
  96. package/packages/opencode/src/lsp/client.ts +0 -252
  97. package/packages/opencode/src/lsp/index.ts +0 -558
  98. package/packages/opencode/src/lsp/language.ts +0 -120
  99. package/packages/opencode/src/lsp/launch.ts +0 -21
  100. package/packages/opencode/src/lsp/server.ts +0 -1968
  101. package/packages/opencode/src/mcp/auth.ts +0 -173
  102. package/packages/opencode/src/mcp/index.ts +0 -931
  103. package/packages/opencode/src/mcp/oauth-callback.ts +0 -216
  104. package/packages/opencode/src/mcp/oauth-provider.ts +0 -185
  105. package/packages/opencode/src/node-loader.mjs +0 -104
  106. package/packages/opencode/src/node-preload.mjs +0 -39
  107. package/packages/opencode/src/node.ts +0 -1
  108. package/packages/opencode/src/npm/index.ts +0 -188
  109. package/packages/opencode/src/patch/index.ts +0 -680
  110. package/packages/opencode/src/permission/arity.ts +0 -163
  111. package/packages/opencode/src/permission/evaluate.ts +0 -15
  112. package/packages/opencode/src/permission/index.ts +0 -325
  113. package/packages/opencode/src/permission/schema.ts +0 -17
  114. package/packages/opencode/src/plugin/cloudflare.ts +0 -67
  115. package/packages/opencode/src/plugin/codex.ts +0 -608
  116. package/packages/opencode/src/plugin/github-copilot/copilot.ts +0 -361
  117. package/packages/opencode/src/plugin/github-copilot/models.ts +0 -144
  118. package/packages/opencode/src/plugin/index.ts +0 -299
  119. package/packages/opencode/src/plugin/install.ts +0 -439
  120. package/packages/opencode/src/plugin/loader.ts +0 -174
  121. package/packages/opencode/src/plugin/meta.ts +0 -188
  122. package/packages/opencode/src/plugin/shared.ts +0 -323
  123. package/packages/opencode/src/project/bootstrap.ts +0 -31
  124. package/packages/opencode/src/project/instance.ts +0 -175
  125. package/packages/opencode/src/project/project.sql.ts +0 -16
  126. package/packages/opencode/src/project/project.ts +0 -519
  127. package/packages/opencode/src/project/schema.ts +0 -16
  128. package/packages/opencode/src/project/state.ts +0 -70
  129. package/packages/opencode/src/project/vcs.ts +0 -240
  130. package/packages/opencode/src/provider/auth.ts +0 -253
  131. package/packages/opencode/src/provider/error.ts +0 -197
  132. package/packages/opencode/src/provider/models.ts +0 -162
  133. package/packages/opencode/src/provider/provider.ts +0 -1709
  134. package/packages/opencode/src/provider/schema.ts +0 -38
  135. package/packages/opencode/src/provider/sdk/copilot/README.md +0 -5
  136. package/packages/opencode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +0 -170
  137. package/packages/opencode/src/provider/sdk/copilot/chat/get-response-metadata.ts +0 -15
  138. package/packages/opencode/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +0 -19
  139. package/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +0 -64
  140. package/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +0 -815
  141. package/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +0 -28
  142. package/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +0 -44
  143. package/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +0 -83
  144. package/packages/opencode/src/provider/sdk/copilot/copilot-provider.ts +0 -100
  145. package/packages/opencode/src/provider/sdk/copilot/index.ts +0 -2
  146. package/packages/opencode/src/provider/sdk/copilot/openai-compatible-error.ts +0 -27
  147. package/packages/opencode/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +0 -335
  148. package/packages/opencode/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +0 -22
  149. package/packages/opencode/src/provider/sdk/copilot/responses/openai-config.ts +0 -18
  150. package/packages/opencode/src/provider/sdk/copilot/responses/openai-error.ts +0 -22
  151. package/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +0 -214
  152. package/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +0 -1769
  153. package/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +0 -173
  154. package/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-settings.ts +0 -1
  155. package/packages/opencode/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +0 -87
  156. package/packages/opencode/src/provider/sdk/copilot/responses/tool/file-search.ts +0 -127
  157. package/packages/opencode/src/provider/sdk/copilot/responses/tool/image-generation.ts +0 -114
  158. package/packages/opencode/src/provider/sdk/copilot/responses/tool/local-shell.ts +0 -64
  159. package/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +0 -103
  160. package/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search.ts +0 -102
  161. package/packages/opencode/src/provider/transform.ts +0 -1050
  162. package/packages/opencode/src/pty/index.ts +0 -397
  163. package/packages/opencode/src/pty/pty.node.ts +0 -27
  164. package/packages/opencode/src/pty/pty.ts +0 -25
  165. package/packages/opencode/src/pty/schema.ts +0 -17
  166. package/packages/opencode/src/question/index.ts +0 -224
  167. package/packages/opencode/src/question/schema.ts +0 -17
  168. package/packages/opencode/src/run.ts +0 -28
  169. package/packages/opencode/src/serve.ts +0 -45
  170. package/packages/opencode/src/server/error.ts +0 -36
  171. package/packages/opencode/src/server/event.ts +0 -7
  172. package/packages/opencode/src/server/instance.ts +0 -324
  173. package/packages/opencode/src/server/mdns.ts +0 -60
  174. package/packages/opencode/src/server/middleware.ts +0 -33
  175. package/packages/opencode/src/server/projectors.ts +0 -28
  176. package/packages/opencode/src/server/proxy.ts +0 -131
  177. package/packages/opencode/src/server/router.ts +0 -105
  178. package/packages/opencode/src/server/routes/config.ts +0 -92
  179. package/packages/opencode/src/server/routes/event.ts +0 -83
  180. package/packages/opencode/src/server/routes/experimental.ts +0 -374
  181. package/packages/opencode/src/server/routes/file.ts +0 -197
  182. package/packages/opencode/src/server/routes/global.ts +0 -312
  183. package/packages/opencode/src/server/routes/mcp.ts +0 -225
  184. package/packages/opencode/src/server/routes/permission.ts +0 -69
  185. package/packages/opencode/src/server/routes/project.ts +0 -118
  186. package/packages/opencode/src/server/routes/provider.ts +0 -171
  187. package/packages/opencode/src/server/routes/pty.ts +0 -210
  188. package/packages/opencode/src/server/routes/question.ts +0 -99
  189. package/packages/opencode/src/server/routes/session.ts +0 -1030
  190. package/packages/opencode/src/server/routes/tui.ts +0 -379
  191. package/packages/opencode/src/server/routes/workspace.ts +0 -94
  192. package/packages/opencode/src/server/server.ts +0 -353
  193. package/packages/opencode/src/session/compaction.ts +0 -428
  194. package/packages/opencode/src/session/index.ts +0 -887
  195. package/packages/opencode/src/session/instruction.ts +0 -258
  196. package/packages/opencode/src/session/llm.ts +0 -354
  197. package/packages/opencode/src/session/message-v2.ts +0 -1031
  198. package/packages/opencode/src/session/message.ts +0 -191
  199. package/packages/opencode/src/session/overflow.ts +0 -22
  200. package/packages/opencode/src/session/processor.ts +0 -523
  201. package/packages/opencode/src/session/projectors.ts +0 -135
  202. package/packages/opencode/src/session/prompt/anthropic.txt +0 -105
  203. package/packages/opencode/src/session/prompt/beast.txt +0 -147
  204. package/packages/opencode/src/session/prompt/build-switch.txt +0 -5
  205. package/packages/opencode/src/session/prompt/codex.txt +0 -79
  206. package/packages/opencode/src/session/prompt/copilot-gpt-5.txt +0 -143
  207. package/packages/opencode/src/session/prompt/default.txt +0 -105
  208. package/packages/opencode/src/session/prompt/gemini.txt +0 -155
  209. package/packages/opencode/src/session/prompt/gpt.txt +0 -107
  210. package/packages/opencode/src/session/prompt/kimi.txt +0 -95
  211. package/packages/opencode/src/session/prompt/max-steps.txt +0 -16
  212. package/packages/opencode/src/session/prompt/plan-reminder-anthropic.txt +0 -67
  213. package/packages/opencode/src/session/prompt/plan.txt +0 -26
  214. package/packages/opencode/src/session/prompt/trinity.txt +0 -97
  215. package/packages/opencode/src/session/prompt.ts +0 -1911
  216. package/packages/opencode/src/session/retry.ts +0 -106
  217. package/packages/opencode/src/session/revert.ts +0 -176
  218. package/packages/opencode/src/session/schema.ts +0 -38
  219. package/packages/opencode/src/session/session.sql.ts +0 -103
  220. package/packages/opencode/src/session/status.ts +0 -102
  221. package/packages/opencode/src/session/summary.ts +0 -177
  222. package/packages/opencode/src/session/system.ts +0 -76
  223. package/packages/opencode/src/session/todo.ts +0 -95
  224. package/packages/opencode/src/share/share-next.ts +0 -369
  225. package/packages/opencode/src/share/share.sql.ts +0 -13
  226. package/packages/opencode/src/shell/shell.ts +0 -110
  227. package/packages/opencode/src/skill/discovery.ts +0 -116
  228. package/packages/opencode/src/skill/index.ts +0 -277
  229. package/packages/opencode/src/snapshot/index.ts +0 -726
  230. package/packages/opencode/src/sql.d.ts +0 -4
  231. package/packages/opencode/src/storage/db.node.ts +0 -8
  232. package/packages/opencode/src/storage/db.ts +0 -177
  233. package/packages/opencode/src/storage/json-migration.ts +0 -425
  234. package/packages/opencode/src/storage/schema.sql.ts +0 -10
  235. package/packages/opencode/src/storage/schema.ts +0 -5
  236. package/packages/opencode/src/storage/storage.ts +0 -353
  237. package/packages/opencode/src/sync/README.md +0 -179
  238. package/packages/opencode/src/sync/event.sql.ts +0 -16
  239. package/packages/opencode/src/sync/index.ts +0 -263
  240. package/packages/opencode/src/sync/schema.ts +0 -14
  241. package/packages/opencode/src/tool/apply_patch.ts +0 -281
  242. package/packages/opencode/src/tool/apply_patch.txt +0 -33
  243. package/packages/opencode/src/tool/bash.ts +0 -496
  244. package/packages/opencode/src/tool/bash.txt +0 -117
  245. package/packages/opencode/src/tool/batch.ts +0 -183
  246. package/packages/opencode/src/tool/batch.txt +0 -24
  247. package/packages/opencode/src/tool/codesearch.ts +0 -132
  248. package/packages/opencode/src/tool/codesearch.txt +0 -12
  249. package/packages/opencode/src/tool/edit.ts +0 -667
  250. package/packages/opencode/src/tool/edit.txt +0 -10
  251. package/packages/opencode/src/tool/external-directory.ts +0 -46
  252. package/packages/opencode/src/tool/glob.ts +0 -78
  253. package/packages/opencode/src/tool/glob.txt +0 -6
  254. package/packages/opencode/src/tool/grep.ts +0 -156
  255. package/packages/opencode/src/tool/grep.txt +0 -8
  256. package/packages/opencode/src/tool/invalid.ts +0 -17
  257. package/packages/opencode/src/tool/ls.ts +0 -121
  258. package/packages/opencode/src/tool/ls.txt +0 -1
  259. package/packages/opencode/src/tool/lsp.ts +0 -97
  260. package/packages/opencode/src/tool/lsp.txt +0 -19
  261. package/packages/opencode/src/tool/multiedit.ts +0 -46
  262. package/packages/opencode/src/tool/multiedit.txt +0 -41
  263. package/packages/opencode/src/tool/plan-enter.txt +0 -14
  264. package/packages/opencode/src/tool/plan-exit.txt +0 -13
  265. package/packages/opencode/src/tool/plan.ts +0 -131
  266. package/packages/opencode/src/tool/question.ts +0 -46
  267. package/packages/opencode/src/tool/question.txt +0 -10
  268. package/packages/opencode/src/tool/read.ts +0 -332
  269. package/packages/opencode/src/tool/read.txt +0 -14
  270. package/packages/opencode/src/tool/registry.ts +0 -265
  271. package/packages/opencode/src/tool/schema.ts +0 -17
  272. package/packages/opencode/src/tool/skill.ts +0 -105
  273. package/packages/opencode/src/tool/task.ts +0 -166
  274. package/packages/opencode/src/tool/task.txt +0 -60
  275. package/packages/opencode/src/tool/todo.ts +0 -48
  276. package/packages/opencode/src/tool/todowrite.txt +0 -167
  277. package/packages/opencode/src/tool/tool.ts +0 -112
  278. package/packages/opencode/src/tool/truncate.ts +0 -144
  279. package/packages/opencode/src/tool/truncation-dir.ts +0 -4
  280. package/packages/opencode/src/tool/webfetch.ts +0 -206
  281. package/packages/opencode/src/tool/webfetch.txt +0 -13
  282. package/packages/opencode/src/tool/websearch.ts +0 -150
  283. package/packages/opencode/src/tool/websearch.txt +0 -14
  284. package/packages/opencode/src/tool/write.ts +0 -84
  285. package/packages/opencode/src/tool/write.txt +0 -8
  286. package/packages/opencode/src/util/abort.ts +0 -35
  287. package/packages/opencode/src/util/archive.ts +0 -17
  288. package/packages/opencode/src/util/color.ts +0 -19
  289. package/packages/opencode/src/util/context.ts +0 -25
  290. package/packages/opencode/src/util/data-url.ts +0 -9
  291. package/packages/opencode/src/util/defer.ts +0 -12
  292. package/packages/opencode/src/util/effect-http-client.ts +0 -11
  293. package/packages/opencode/src/util/effect-zod.ts +0 -98
  294. package/packages/opencode/src/util/error.ts +0 -77
  295. package/packages/opencode/src/util/filesystem.ts +0 -245
  296. package/packages/opencode/src/util/flock.ts +0 -333
  297. package/packages/opencode/src/util/fn.ts +0 -21
  298. package/packages/opencode/src/util/format.ts +0 -20
  299. package/packages/opencode/src/util/glob.ts +0 -34
  300. package/packages/opencode/src/util/hash.ts +0 -7
  301. package/packages/opencode/src/util/iife.ts +0 -3
  302. package/packages/opencode/src/util/keybind.ts +0 -103
  303. package/packages/opencode/src/util/lazy.ts +0 -23
  304. package/packages/opencode/src/util/locale.ts +0 -81
  305. package/packages/opencode/src/util/lock.ts +0 -98
  306. package/packages/opencode/src/util/log.ts +0 -182
  307. package/packages/opencode/src/util/network.ts +0 -9
  308. package/packages/opencode/src/util/process.ts +0 -176
  309. package/packages/opencode/src/util/queue.ts +0 -32
  310. package/packages/opencode/src/util/record.ts +0 -3
  311. package/packages/opencode/src/util/rpc.ts +0 -66
  312. package/packages/opencode/src/util/schema.ts +0 -53
  313. package/packages/opencode/src/util/scrap.ts +0 -10
  314. package/packages/opencode/src/util/signal.ts +0 -12
  315. package/packages/opencode/src/util/timeout.ts +0 -14
  316. package/packages/opencode/src/util/token.ts +0 -7
  317. package/packages/opencode/src/util/update-schema.ts +0 -13
  318. package/packages/opencode/src/util/which.ts +0 -14
  319. package/packages/opencode/src/util/wildcard.ts +0 -59
  320. package/packages/opencode/src/worktree/index.ts +0 -612
  321. package/packages/plugin/package.json +0 -38
  322. package/packages/plugin/src/example.ts +0 -18
  323. package/packages/plugin/src/index.ts +0 -282
  324. package/packages/plugin/src/shell.ts +0 -136
  325. package/packages/plugin/src/tool.ts +0 -38
  326. package/packages/plugin/src/tui.ts +0 -508
  327. package/packages/script/package.json +0 -16
  328. package/packages/script/src/index.ts +0 -77
  329. package/packages/sdk/package.json +0 -33
  330. package/packages/sdk/src/client.ts +0 -55
  331. package/packages/sdk/src/gen/client/client.gen.ts +0 -212
  332. package/packages/sdk/src/gen/client/index.ts +0 -25
  333. package/packages/sdk/src/gen/client/types.gen.ts +0 -222
  334. package/packages/sdk/src/gen/client/utils.gen.ts +0 -287
  335. package/packages/sdk/src/gen/client.gen.ts +0 -22
  336. package/packages/sdk/src/gen/core/auth.gen.ts +0 -41
  337. package/packages/sdk/src/gen/core/bodySerializer.gen.ts +0 -74
  338. package/packages/sdk/src/gen/core/params.gen.ts +0 -144
  339. package/packages/sdk/src/gen/core/pathSerializer.gen.ts +0 -167
  340. package/packages/sdk/src/gen/core/queryKeySerializer.gen.ts +0 -111
  341. package/packages/sdk/src/gen/core/serverSentEvents.gen.ts +0 -210
  342. package/packages/sdk/src/gen/core/types.gen.ts +0 -91
  343. package/packages/sdk/src/gen/core/utils.gen.ts +0 -109
  344. package/packages/sdk/src/gen/sdk.gen.ts +0 -1197
  345. package/packages/sdk/src/gen/types.gen.ts +0 -3904
  346. package/packages/sdk/src/index.ts +0 -21
  347. package/packages/sdk/src/process.ts +0 -31
  348. package/packages/sdk/src/server.ts +0 -134
  349. package/packages/sdk/src/v2/client.ts +0 -81
  350. package/packages/sdk/src/v2/gen/client/client.gen.ts +0 -285
  351. package/packages/sdk/src/v2/gen/client/index.ts +0 -25
  352. package/packages/sdk/src/v2/gen/client/types.gen.ts +0 -202
  353. package/packages/sdk/src/v2/gen/client/utils.gen.ts +0 -289
  354. package/packages/sdk/src/v2/gen/client.gen.ts +0 -18
  355. package/packages/sdk/src/v2/gen/core/auth.gen.ts +0 -41
  356. package/packages/sdk/src/v2/gen/core/bodySerializer.gen.ts +0 -82
  357. package/packages/sdk/src/v2/gen/core/params.gen.ts +0 -169
  358. package/packages/sdk/src/v2/gen/core/pathSerializer.gen.ts +0 -167
  359. package/packages/sdk/src/v2/gen/core/queryKeySerializer.gen.ts +0 -111
  360. package/packages/sdk/src/v2/gen/core/serverSentEvents.gen.ts +0 -239
  361. package/packages/sdk/src/v2/gen/core/types.gen.ts +0 -86
  362. package/packages/sdk/src/v2/gen/core/utils.gen.ts +0 -137
  363. package/packages/sdk/src/v2/gen/sdk.gen.ts +0 -4223
  364. package/packages/sdk/src/v2/gen/types.gen.ts +0 -5265
  365. package/packages/sdk/src/v2/index.ts +0 -21
  366. package/packages/sdk/src/v2/server.ts +0 -134
  367. package/packages/util/package.json +0 -20
  368. package/packages/util/src/array.ts +0 -10
  369. package/packages/util/src/binary.ts +0 -41
  370. package/packages/util/src/encode.ts +0 -51
  371. package/packages/util/src/error.ts +0 -54
  372. package/packages/util/src/fn.ts +0 -11
  373. package/packages/util/src/identifier.ts +0 -48
  374. package/packages/util/src/iife.ts +0 -3
  375. package/packages/util/src/lazy.ts +0 -11
  376. package/packages/util/src/module.ts +0 -10
  377. package/packages/util/src/path.ts +0 -37
  378. package/packages/util/src/retry.ts +0 -41
  379. package/packages/util/src/slug.ts +0 -74
  380. package/postinstall.cjs +0 -55
  381. /package/dist/{chunk-23UYOGZG.js.map → chunk-ORHZOVWK.js.map} +0 -0
  382. /package/dist/{chunk-TASPMBUL.js.map → chunk-SV5NEV33.js.map} +0 -0
  383. /package/dist/{chunk-GSE2T5UY.js.map → chunk-ZFHDPNFZ.js.map} +0 -0
  384. /package/dist/{mcp-XFGN3FWW.js.map → mcp-66BBNSWA.js.map} +0 -0
  385. /package/dist/{registry-W4MBVBYD.js.map → registry-YVL3V6Z6.js.map} +0 -0
  386. /package/dist/{run-73HUV4H3.js.map → run-65PIGE2A.js.map} +0 -0
  387. /package/dist/{run-service-LHEVFBIU.js.map → run-service-AYATGPA6.js.map} +0 -0
  388. /package/dist/{server-2CPOG3LS.js.map → server-KCG3B4WQ.js.map} +0 -0
  389. /package/dist/{session-M6HOZ56B.js.map → session-BR5MTI3X.js.map} +0 -0
  390. /package/dist/{share-next-QRBVSD37.js.map → share-next-EPYQDJ32.js.map} +0 -0
  391. /package/dist/{worktree-3GUHT2P7.js.map → worktree-PIVTZBUE.js.map} +0 -0
@@ -1,1847 +0,0 @@
1
- import {
2
- RequestError,
3
- type Agent as ACPAgent,
4
- type AgentSideConnection,
5
- type AuthenticateRequest,
6
- type AuthMethod,
7
- type CancelNotification,
8
- type ForkSessionRequest,
9
- type ForkSessionResponse,
10
- type InitializeRequest,
11
- type InitializeResponse,
12
- type ListSessionsRequest,
13
- type ListSessionsResponse,
14
- type LoadSessionRequest,
15
- type NewSessionRequest,
16
- type PermissionOption,
17
- type PlanEntry,
18
- type PromptRequest,
19
- type ResumeSessionRequest,
20
- type ResumeSessionResponse,
21
- type Role,
22
- type SessionInfo,
23
- type SetSessionModelRequest,
24
- type SessionConfigOption,
25
- type SetSessionConfigOptionRequest,
26
- type SetSessionConfigOptionResponse,
27
- type SetSessionModeRequest,
28
- type SetSessionModeResponse,
29
- type ToolCallContent,
30
- type ToolKind,
31
- type Usage,
32
- } from "@agentclientprotocol/sdk"
33
-
34
- import { Log } from "../util/log"
35
- import { pathToFileURL } from "url"
36
- import { Filesystem } from "../util/filesystem"
37
- import { Hash } from "../util/hash"
38
- import { ACPSessionManager } from "./session"
39
- import type { ACPConfig } from "./types"
40
- import { Provider } from "../provider/provider"
41
- import { ModelID, ProviderID } from "../provider/schema"
42
- import { Agent as AgentModule } from "../agent/agent"
43
- import { Installation } from "@/installation"
44
- import { MessageV2 } from "@/session/message-v2"
45
- import { Config } from "@/config/config"
46
- import { Todo } from "@/session/todo"
47
- import { z } from "zod"
48
- import { LoadAPIKeyError } from "ai"
49
- import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
50
- import { applyPatch } from "diff"
51
-
52
- type ModeOption = { id: string; name: string; description?: string }
53
- type ModelOption = { modelId: string; name: string }
54
-
55
- const DEFAULT_VARIANT_VALUE = "default"
56
-
57
- export namespace ACP {
58
- const log = Log.create({ service: "acp-agent" })
59
-
60
- async function getContextLimit(
61
- sdk: OpencodeClient,
62
- providerID: ProviderID,
63
- modelID: ModelID,
64
- directory: string,
65
- ): Promise<number | null> {
66
- const providers = await sdk.config
67
- .providers({ directory })
68
- .then((x) => x.data?.providers ?? [])
69
- .catch((error) => {
70
- log.error("failed to get providers for context limit", { error })
71
- return []
72
- })
73
-
74
- const provider = providers.find((p) => p.id === providerID)
75
- const model = provider?.models[modelID]
76
- return model?.limit.context ?? null
77
- }
78
-
79
- async function sendUsageUpdate(
80
- connection: AgentSideConnection,
81
- sdk: OpencodeClient,
82
- sessionID: string,
83
- directory: string,
84
- ): Promise<void> {
85
- const messages = await sdk.session
86
- .messages({ sessionID, directory }, { throwOnError: true })
87
- .then((x) => x.data)
88
- .catch((error) => {
89
- log.error("failed to fetch messages for usage update", { error })
90
- return undefined
91
- })
92
-
93
- if (!messages) return
94
-
95
- const assistantMessages = messages.filter(
96
- (m): m is { info: AssistantMessage; parts: SessionMessageResponse["parts"] } => m.info.role === "assistant",
97
- )
98
-
99
- const lastAssistant = assistantMessages[assistantMessages.length - 1]
100
- if (!lastAssistant) return
101
-
102
- const msg = lastAssistant.info
103
- if (!msg.providerID || !msg.modelID) return
104
- const size = await getContextLimit(sdk, ProviderID.make(msg.providerID), ModelID.make(msg.modelID), directory)
105
-
106
- if (!size) {
107
- // Cannot calculate usage without known context size
108
- return
109
- }
110
-
111
- const used = msg.tokens.input + (msg.tokens.cache?.read ?? 0)
112
- const totalCost = assistantMessages.reduce((sum, m) => sum + m.info.cost, 0)
113
-
114
- await connection
115
- .sessionUpdate({
116
- sessionId: sessionID,
117
- update: {
118
- sessionUpdate: "usage_update",
119
- used,
120
- size,
121
- cost: { amount: totalCost, currency: "USD" },
122
- },
123
- })
124
- .catch((error) => {
125
- log.error("failed to send usage update", { error })
126
- })
127
- }
128
-
129
- export async function init({ sdk: _sdk }: { sdk: OpencodeClient }) {
130
- return {
131
- create: (connection: AgentSideConnection, fullConfig: ACPConfig) => {
132
- return new Agent(connection, fullConfig)
133
- },
134
- }
135
- }
136
-
137
- export class Agent implements ACPAgent {
138
- private connection: AgentSideConnection
139
- private config: ACPConfig
140
- private sdk: OpencodeClient
141
- private sessionManager: ACPSessionManager
142
- private eventAbort = new AbortController()
143
- private eventStarted = false
144
- private bashSnapshots = new Map<string, string>()
145
- private toolStarts = new Set<string>()
146
- private permissionQueues = new Map<string, Promise<void>>()
147
- private permissionOptions: PermissionOption[] = [
148
- { optionId: "once", kind: "allow_once", name: "Allow once" },
149
- { optionId: "always", kind: "allow_always", name: "Always allow" },
150
- { optionId: "reject", kind: "reject_once", name: "Reject" },
151
- ]
152
-
153
- constructor(connection: AgentSideConnection, config: ACPConfig) {
154
- this.connection = connection
155
- this.config = config
156
- this.sdk = config.sdk
157
- this.sessionManager = new ACPSessionManager(this.sdk)
158
- this.startEventSubscription()
159
- }
160
-
161
- private startEventSubscription() {
162
- if (this.eventStarted) return
163
- this.eventStarted = true
164
- this.runEventSubscription().catch((error) => {
165
- if (this.eventAbort.signal.aborted) return
166
- log.error("event subscription failed", { error })
167
- })
168
- }
169
-
170
- private async runEventSubscription() {
171
- while (true) {
172
- if (this.eventAbort.signal.aborted) return
173
- const events = await this.sdk.global.event({
174
- signal: this.eventAbort.signal,
175
- })
176
- for await (const event of events.stream) {
177
- if (this.eventAbort.signal.aborted) return
178
- const payload = (event as any)?.payload
179
- if (!payload) continue
180
- await this.handleEvent(payload as Event).catch((error) => {
181
- log.error("failed to handle event", { error, type: payload.type })
182
- })
183
- }
184
- }
185
- }
186
-
187
- private async handleEvent(event: Event) {
188
- switch (event.type) {
189
- case "permission.asked": {
190
- const permission = event.properties
191
- const session = this.sessionManager.tryGet(permission.sessionID)
192
- if (!session) return
193
-
194
- const prev = this.permissionQueues.get(permission.sessionID) ?? Promise.resolve()
195
- const next = prev
196
- .then(async () => {
197
- const directory = session.cwd
198
-
199
- const res = await this.connection
200
- .requestPermission({
201
- sessionId: permission.sessionID,
202
- toolCall: {
203
- toolCallId: permission.tool?.callID ?? permission.id,
204
- status: "pending",
205
- title: permission.permission,
206
- rawInput: permission.metadata,
207
- kind: toToolKind(permission.permission),
208
- locations: toLocations(permission.permission, permission.metadata),
209
- },
210
- options: this.permissionOptions,
211
- })
212
- .catch(async (error) => {
213
- log.error("failed to request permission from ACP", {
214
- error,
215
- permissionID: permission.id,
216
- sessionID: permission.sessionID,
217
- })
218
- await this.sdk.permission.reply({
219
- requestID: permission.id,
220
- reply: "reject",
221
- directory,
222
- })
223
- return undefined
224
- })
225
-
226
- if (!res) return
227
- if (res.outcome.outcome !== "selected") {
228
- await this.sdk.permission.reply({
229
- requestID: permission.id,
230
- reply: "reject",
231
- directory,
232
- })
233
- return
234
- }
235
-
236
- if (res.outcome.optionId !== "reject" && permission.permission == "edit") {
237
- const metadata = permission.metadata || {}
238
- const filepath = typeof metadata["filepath"] === "string" ? metadata["filepath"] : ""
239
- const diff = typeof metadata["diff"] === "string" ? metadata["diff"] : ""
240
- const content = (await Filesystem.exists(filepath)) ? await Filesystem.readText(filepath) : ""
241
- const newContent = getNewContent(content, diff)
242
-
243
- if (newContent) {
244
- this.connection.writeTextFile({
245
- sessionId: session.id,
246
- path: filepath,
247
- content: newContent,
248
- })
249
- }
250
- }
251
-
252
- await this.sdk.permission.reply({
253
- requestID: permission.id,
254
- reply: res.outcome.optionId as "once" | "always" | "reject",
255
- directory,
256
- })
257
- })
258
- .catch((error) => {
259
- log.error("failed to handle permission", { error, permissionID: permission.id })
260
- })
261
- .finally(() => {
262
- if (this.permissionQueues.get(permission.sessionID) === next) {
263
- this.permissionQueues.delete(permission.sessionID)
264
- }
265
- })
266
- this.permissionQueues.set(permission.sessionID, next)
267
- return
268
- }
269
-
270
- case "message.part.updated": {
271
- log.info("message part updated", { event: event.properties })
272
- const props = event.properties
273
- const part = props.part
274
- const session = this.sessionManager.tryGet(part.sessionID)
275
- if (!session) return
276
- const sessionId = session.id
277
-
278
- if (part.type === "tool") {
279
- await this.toolStart(sessionId, part)
280
-
281
- switch (part.state.status) {
282
- case "pending":
283
- this.bashSnapshots.delete(part.callID)
284
- return
285
-
286
- case "running":
287
- const output = this.bashOutput(part)
288
- const content: ToolCallContent[] = []
289
- if (output) {
290
- const hash = Hash.fast(output)
291
- if (part.tool === "bash") {
292
- if (this.bashSnapshots.get(part.callID) === hash) {
293
- await this.connection
294
- .sessionUpdate({
295
- sessionId,
296
- update: {
297
- sessionUpdate: "tool_call_update",
298
- toolCallId: part.callID,
299
- status: "in_progress",
300
- kind: toToolKind(part.tool),
301
- title: part.tool,
302
- locations: toLocations(part.tool, part.state.input),
303
- rawInput: part.state.input,
304
- },
305
- })
306
- .catch((error) => {
307
- log.error("failed to send tool in_progress to ACP", { error })
308
- })
309
- return
310
- }
311
- this.bashSnapshots.set(part.callID, hash)
312
- }
313
- content.push({
314
- type: "content",
315
- content: {
316
- type: "text",
317
- text: output,
318
- },
319
- })
320
- }
321
- await this.connection
322
- .sessionUpdate({
323
- sessionId,
324
- update: {
325
- sessionUpdate: "tool_call_update",
326
- toolCallId: part.callID,
327
- status: "in_progress",
328
- kind: toToolKind(part.tool),
329
- title: part.tool,
330
- locations: toLocations(part.tool, part.state.input),
331
- rawInput: part.state.input,
332
- ...(content.length > 0 && { content }),
333
- },
334
- })
335
- .catch((error) => {
336
- log.error("failed to send tool in_progress to ACP", { error })
337
- })
338
- return
339
-
340
- case "completed": {
341
- this.toolStarts.delete(part.callID)
342
- this.bashSnapshots.delete(part.callID)
343
- const kind = toToolKind(part.tool)
344
- const content: ToolCallContent[] = [
345
- {
346
- type: "content",
347
- content: {
348
- type: "text",
349
- text: part.state.output,
350
- },
351
- },
352
- ]
353
-
354
- if (kind === "edit") {
355
- const input = part.state.input
356
- const filePath = typeof input["filePath"] === "string" ? input["filePath"] : ""
357
- const oldText = typeof input["oldString"] === "string" ? input["oldString"] : ""
358
- const newText =
359
- typeof input["newString"] === "string"
360
- ? input["newString"]
361
- : typeof input["content"] === "string"
362
- ? input["content"]
363
- : ""
364
- content.push({
365
- type: "diff",
366
- path: filePath,
367
- oldText,
368
- newText,
369
- })
370
- }
371
-
372
- if (part.tool === "todowrite") {
373
- const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output))
374
- if (parsedTodos.success) {
375
- await this.connection
376
- .sessionUpdate({
377
- sessionId,
378
- update: {
379
- sessionUpdate: "plan",
380
- entries: parsedTodos.data.map((todo) => {
381
- const status: PlanEntry["status"] =
382
- todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
383
- return {
384
- priority: "medium",
385
- status,
386
- content: todo.content,
387
- }
388
- }),
389
- },
390
- })
391
- .catch((error) => {
392
- log.error("failed to send session update for todo", { error })
393
- })
394
- } else {
395
- log.error("failed to parse todo output", { error: parsedTodos.error })
396
- }
397
- }
398
-
399
- await this.connection
400
- .sessionUpdate({
401
- sessionId,
402
- update: {
403
- sessionUpdate: "tool_call_update",
404
- toolCallId: part.callID,
405
- status: "completed",
406
- kind,
407
- content,
408
- title: part.state.title,
409
- rawInput: part.state.input,
410
- rawOutput: {
411
- output: part.state.output,
412
- metadata: part.state.metadata,
413
- },
414
- },
415
- })
416
- .catch((error) => {
417
- log.error("failed to send tool completed to ACP", { error })
418
- })
419
- return
420
- }
421
- case "error":
422
- this.toolStarts.delete(part.callID)
423
- this.bashSnapshots.delete(part.callID)
424
- await this.connection
425
- .sessionUpdate({
426
- sessionId,
427
- update: {
428
- sessionUpdate: "tool_call_update",
429
- toolCallId: part.callID,
430
- status: "failed",
431
- kind: toToolKind(part.tool),
432
- title: part.tool,
433
- rawInput: part.state.input,
434
- content: [
435
- {
436
- type: "content",
437
- content: {
438
- type: "text",
439
- text: part.state.error,
440
- },
441
- },
442
- ],
443
- rawOutput: {
444
- error: part.state.error,
445
- metadata: part.state.metadata,
446
- },
447
- },
448
- })
449
- .catch((error) => {
450
- log.error("failed to send tool error to ACP", { error })
451
- })
452
- return
453
- }
454
- }
455
- if (part.type !== "text" && part.type !== "file") return
456
- const msg = await this.sdk.session
457
- .message(
458
- { sessionID: part.sessionID, messageID: part.messageID, directory: session.cwd },
459
- { throwOnError: true },
460
- )
461
- .then((x) => x.data)
462
- .catch((err) => {
463
- log.error("failed to fetch message for user chunk", { error: err })
464
- return undefined
465
- })
466
- if (!msg || msg.info.role !== "user") return
467
- await this.processMessage({ info: msg.info, parts: [part] })
468
- return
469
- }
470
-
471
- case "message.part.delta": {
472
- const props = event.properties
473
- const session = this.sessionManager.tryGet(props.sessionID)
474
- if (!session) return
475
- const sessionId = session.id
476
-
477
- const message = await this.sdk.session
478
- .message(
479
- {
480
- sessionID: props.sessionID,
481
- messageID: props.messageID,
482
- directory: session.cwd,
483
- },
484
- { throwOnError: true },
485
- )
486
- .then((x) => x.data)
487
- .catch((error) => {
488
- log.error("unexpected error when fetching message", { error })
489
- return undefined
490
- })
491
-
492
- if (!message || message.info.role !== "assistant") return
493
-
494
- const part = message.parts.find((p) => p.id === props.partID)
495
- if (!part) return
496
-
497
- if (part.type === "text" && props.field === "text" && part.ignored !== true) {
498
- await this.connection
499
- .sessionUpdate({
500
- sessionId,
501
- update: {
502
- sessionUpdate: "agent_message_chunk",
503
- messageId: props.messageID,
504
- content: {
505
- type: "text",
506
- text: props.delta,
507
- },
508
- },
509
- })
510
- .catch((error) => {
511
- log.error("failed to send text delta to ACP", { error })
512
- })
513
- return
514
- }
515
-
516
- if (part.type === "reasoning" && props.field === "text") {
517
- await this.connection
518
- .sessionUpdate({
519
- sessionId,
520
- update: {
521
- sessionUpdate: "agent_thought_chunk",
522
- messageId: props.messageID,
523
- content: {
524
- type: "text",
525
- text: props.delta,
526
- },
527
- },
528
- })
529
- .catch((error) => {
530
- log.error("failed to send reasoning delta to ACP", { error })
531
- })
532
- }
533
- return
534
- }
535
- }
536
- }
537
-
538
- async initialize(params: InitializeRequest): Promise<InitializeResponse> {
539
- log.info("initialize", { protocolVersion: params.protocolVersion })
540
-
541
- const authMethod: AuthMethod = {
542
- description: "Run `opencode auth login` in the terminal",
543
- name: "Login with opencode",
544
- id: "opencode-login",
545
- }
546
-
547
- // If client supports terminal-auth capability, use that instead.
548
- if (params.clientCapabilities?._meta?.["terminal-auth"] === true) {
549
- authMethod._meta = {
550
- "terminal-auth": {
551
- command: "opencode",
552
- args: ["auth", "login"],
553
- label: "OpenCode Login",
554
- },
555
- }
556
- }
557
-
558
- return {
559
- protocolVersion: 1,
560
- agentCapabilities: {
561
- loadSession: true,
562
- mcpCapabilities: {
563
- http: true,
564
- sse: true,
565
- },
566
- promptCapabilities: {
567
- embeddedContext: true,
568
- image: true,
569
- },
570
- sessionCapabilities: {
571
- fork: {},
572
- list: {},
573
- resume: {},
574
- },
575
- },
576
- authMethods: [authMethod],
577
- agentInfo: {
578
- name: "OpenCode",
579
- version: Installation.VERSION,
580
- },
581
- }
582
- }
583
-
584
- async authenticate(_params: AuthenticateRequest) {
585
- throw new Error("Authentication not implemented")
586
- }
587
-
588
- async newSession(params: NewSessionRequest) {
589
- const directory = params.cwd
590
- try {
591
- const model = await defaultModel(this.config, directory)
592
-
593
- // Store ACP session state
594
- const state = await this.sessionManager.create(params.cwd, params.mcpServers, model)
595
- const sessionId = state.id
596
-
597
- log.info("creating_session", { sessionId, mcpServers: params.mcpServers.length })
598
-
599
- const load = await this.loadSessionMode({
600
- cwd: directory,
601
- mcpServers: params.mcpServers,
602
- sessionId,
603
- })
604
-
605
- return {
606
- sessionId,
607
- configOptions: load.configOptions,
608
- models: load.models,
609
- modes: load.modes,
610
- _meta: load._meta,
611
- }
612
- } catch (e) {
613
- const error = MessageV2.fromError(e, {
614
- providerID: ProviderID.make(this.config.defaultModel?.providerID ?? "unknown"),
615
- })
616
- if (LoadAPIKeyError.isInstance(error)) {
617
- throw RequestError.authRequired()
618
- }
619
- throw e
620
- }
621
- }
622
-
623
- async loadSession(params: LoadSessionRequest) {
624
- const directory = params.cwd
625
- const sessionId = params.sessionId
626
-
627
- try {
628
- const model = await defaultModel(this.config, directory)
629
-
630
- // Store ACP session state
631
- await this.sessionManager.load(sessionId, params.cwd, params.mcpServers, model)
632
-
633
- log.info("load_session", { sessionId, mcpServers: params.mcpServers.length })
634
-
635
- const result = await this.loadSessionMode({
636
- cwd: directory,
637
- mcpServers: params.mcpServers,
638
- sessionId,
639
- })
640
-
641
- // Replay session history
642
- const messages = await this.sdk.session
643
- .messages(
644
- {
645
- sessionID: sessionId,
646
- directory,
647
- },
648
- { throwOnError: true },
649
- )
650
- .then((x) => x.data)
651
- .catch((err) => {
652
- log.error("unexpected error when fetching message", { error: err })
653
- return undefined
654
- })
655
-
656
- const lastUser = messages?.findLast((m) => m.info.role === "user")?.info
657
- if (lastUser?.role === "user") {
658
- result.models.currentModelId = `${lastUser.model.providerID}/${lastUser.model.modelID}`
659
- this.sessionManager.setModel(sessionId, {
660
- providerID: ProviderID.make(lastUser.model.providerID),
661
- modelID: ModelID.make(lastUser.model.modelID),
662
- })
663
- if (result.modes?.availableModes.some((m) => m.id === lastUser.agent)) {
664
- result.modes.currentModeId = lastUser.agent
665
- this.sessionManager.setMode(sessionId, lastUser.agent)
666
- }
667
- result.configOptions = buildConfigOptions({
668
- currentModelId: result.models.currentModelId,
669
- availableModels: result.models.availableModels,
670
- modes: result.modes,
671
- })
672
- }
673
-
674
- for (const msg of messages ?? []) {
675
- log.debug("replay message", msg)
676
- await this.processMessage(msg)
677
- }
678
-
679
- await sendUsageUpdate(this.connection, this.sdk, sessionId, directory)
680
-
681
- return result
682
- } catch (e) {
683
- const error = MessageV2.fromError(e, {
684
- providerID: ProviderID.make(this.config.defaultModel?.providerID ?? "unknown"),
685
- })
686
- if (LoadAPIKeyError.isInstance(error)) {
687
- throw RequestError.authRequired()
688
- }
689
- throw e
690
- }
691
- }
692
-
693
- async listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse> {
694
- try {
695
- const cursor = params.cursor ? Number(params.cursor) : undefined
696
- const limit = 100
697
-
698
- const sessions = await this.sdk.session
699
- .list(
700
- {
701
- directory: params.cwd ?? undefined,
702
- roots: true,
703
- },
704
- { throwOnError: true },
705
- )
706
- .then((x) => x.data ?? [])
707
-
708
- const sorted = sessions.toSorted((a, b) => b.time.updated - a.time.updated)
709
- const filtered = cursor ? sorted.filter((s) => s.time.updated < cursor) : sorted
710
- const page = filtered.slice(0, limit)
711
-
712
- const entries: SessionInfo[] = page.map((session) => ({
713
- sessionId: session.id,
714
- cwd: session.directory,
715
- title: session.title,
716
- updatedAt: new Date(session.time.updated).toISOString(),
717
- }))
718
-
719
- const last = page[page.length - 1]
720
- const next = filtered.length > limit && last ? String(last.time.updated) : undefined
721
-
722
- const response: ListSessionsResponse = {
723
- sessions: entries,
724
- }
725
- if (next) response.nextCursor = next
726
- return response
727
- } catch (e) {
728
- const error = MessageV2.fromError(e, {
729
- providerID: ProviderID.make(this.config.defaultModel?.providerID ?? "unknown"),
730
- })
731
- if (LoadAPIKeyError.isInstance(error)) {
732
- throw RequestError.authRequired()
733
- }
734
- throw e
735
- }
736
- }
737
-
738
- async unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse> {
739
- const directory = params.cwd
740
- const mcpServers = params.mcpServers ?? []
741
-
742
- try {
743
- const model = await defaultModel(this.config, directory)
744
-
745
- const forked = await this.sdk.session
746
- .fork(
747
- {
748
- sessionID: params.sessionId,
749
- directory,
750
- },
751
- { throwOnError: true },
752
- )
753
- .then((x) => x.data)
754
-
755
- if (!forked) {
756
- throw new Error("Fork session returned no data")
757
- }
758
-
759
- const sessionId = forked.id
760
- await this.sessionManager.load(sessionId, directory, mcpServers, model)
761
-
762
- log.info("fork_session", { sessionId, mcpServers: mcpServers.length })
763
-
764
- const mode = await this.loadSessionMode({
765
- cwd: directory,
766
- mcpServers,
767
- sessionId,
768
- })
769
-
770
- const messages = await this.sdk.session
771
- .messages(
772
- {
773
- sessionID: sessionId,
774
- directory,
775
- },
776
- { throwOnError: true },
777
- )
778
- .then((x) => x.data)
779
- .catch((err) => {
780
- log.error("unexpected error when fetching message", { error: err })
781
- return undefined
782
- })
783
-
784
- for (const msg of messages ?? []) {
785
- log.debug("replay message", msg)
786
- await this.processMessage(msg)
787
- }
788
-
789
- await sendUsageUpdate(this.connection, this.sdk, sessionId, directory)
790
-
791
- return mode
792
- } catch (e) {
793
- const error = MessageV2.fromError(e, {
794
- providerID: ProviderID.make(this.config.defaultModel?.providerID ?? "unknown"),
795
- })
796
- if (LoadAPIKeyError.isInstance(error)) {
797
- throw RequestError.authRequired()
798
- }
799
- throw e
800
- }
801
- }
802
-
803
- async unstable_resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
804
- const directory = params.cwd
805
- const sessionId = params.sessionId
806
- const mcpServers = params.mcpServers ?? []
807
-
808
- try {
809
- const model = await defaultModel(this.config, directory)
810
- await this.sessionManager.load(sessionId, directory, mcpServers, model)
811
-
812
- log.info("resume_session", { sessionId, mcpServers: mcpServers.length })
813
-
814
- const result = await this.loadSessionMode({
815
- cwd: directory,
816
- mcpServers,
817
- sessionId,
818
- })
819
-
820
- await sendUsageUpdate(this.connection, this.sdk, sessionId, directory)
821
-
822
- return result
823
- } catch (e) {
824
- const error = MessageV2.fromError(e, {
825
- providerID: ProviderID.make(this.config.defaultModel?.providerID ?? "unknown"),
826
- })
827
- if (LoadAPIKeyError.isInstance(error)) {
828
- throw RequestError.authRequired()
829
- }
830
- throw e
831
- }
832
- }
833
-
834
- private async processMessage(message: SessionMessageResponse) {
835
- log.debug("process message", message)
836
- if (message.info.role !== "assistant" && message.info.role !== "user") return
837
- const sessionId = message.info.sessionID
838
-
839
- for (const part of message.parts) {
840
- if (part.type === "tool") {
841
- await this.toolStart(sessionId, part)
842
- switch (part.state.status) {
843
- case "pending":
844
- this.bashSnapshots.delete(part.callID)
845
- break
846
- case "running":
847
- const output = this.bashOutput(part)
848
- const runningContent: ToolCallContent[] = []
849
- if (output) {
850
- runningContent.push({
851
- type: "content",
852
- content: {
853
- type: "text",
854
- text: output,
855
- },
856
- })
857
- }
858
- await this.connection
859
- .sessionUpdate({
860
- sessionId,
861
- update: {
862
- sessionUpdate: "tool_call_update",
863
- toolCallId: part.callID,
864
- status: "in_progress",
865
- kind: toToolKind(part.tool),
866
- title: part.tool,
867
- locations: toLocations(part.tool, part.state.input),
868
- rawInput: part.state.input,
869
- ...(runningContent.length > 0 && { content: runningContent }),
870
- },
871
- })
872
- .catch((err) => {
873
- log.error("failed to send tool in_progress to ACP", { error: err })
874
- })
875
- break
876
- case "completed":
877
- this.toolStarts.delete(part.callID)
878
- this.bashSnapshots.delete(part.callID)
879
- const kind = toToolKind(part.tool)
880
- const content: ToolCallContent[] = [
881
- {
882
- type: "content",
883
- content: {
884
- type: "text",
885
- text: part.state.output,
886
- },
887
- },
888
- ]
889
-
890
- if (kind === "edit") {
891
- const input = part.state.input
892
- const filePath = typeof input["filePath"] === "string" ? input["filePath"] : ""
893
- const oldText = typeof input["oldString"] === "string" ? input["oldString"] : ""
894
- const newText =
895
- typeof input["newString"] === "string"
896
- ? input["newString"]
897
- : typeof input["content"] === "string"
898
- ? input["content"]
899
- : ""
900
- content.push({
901
- type: "diff",
902
- path: filePath,
903
- oldText,
904
- newText,
905
- })
906
- }
907
-
908
- if (part.tool === "todowrite") {
909
- const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output))
910
- if (parsedTodos.success) {
911
- await this.connection
912
- .sessionUpdate({
913
- sessionId,
914
- update: {
915
- sessionUpdate: "plan",
916
- entries: parsedTodos.data.map((todo) => {
917
- const status: PlanEntry["status"] =
918
- todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
919
- return {
920
- priority: "medium",
921
- status,
922
- content: todo.content,
923
- }
924
- }),
925
- },
926
- })
927
- .catch((err) => {
928
- log.error("failed to send session update for todo", { error: err })
929
- })
930
- } else {
931
- log.error("failed to parse todo output", { error: parsedTodos.error })
932
- }
933
- }
934
-
935
- await this.connection
936
- .sessionUpdate({
937
- sessionId,
938
- update: {
939
- sessionUpdate: "tool_call_update",
940
- toolCallId: part.callID,
941
- status: "completed",
942
- kind,
943
- content,
944
- title: part.state.title,
945
- rawInput: part.state.input,
946
- rawOutput: {
947
- output: part.state.output,
948
- metadata: part.state.metadata,
949
- },
950
- },
951
- })
952
- .catch((err) => {
953
- log.error("failed to send tool completed to ACP", { error: err })
954
- })
955
- break
956
- case "error":
957
- this.toolStarts.delete(part.callID)
958
- this.bashSnapshots.delete(part.callID)
959
- await this.connection
960
- .sessionUpdate({
961
- sessionId,
962
- update: {
963
- sessionUpdate: "tool_call_update",
964
- toolCallId: part.callID,
965
- status: "failed",
966
- kind: toToolKind(part.tool),
967
- title: part.tool,
968
- rawInput: part.state.input,
969
- content: [
970
- {
971
- type: "content",
972
- content: {
973
- type: "text",
974
- text: part.state.error,
975
- },
976
- },
977
- ],
978
- rawOutput: {
979
- error: part.state.error,
980
- metadata: part.state.metadata,
981
- },
982
- },
983
- })
984
- .catch((err) => {
985
- log.error("failed to send tool error to ACP", { error: err })
986
- })
987
- break
988
- }
989
- } else if (part.type === "text") {
990
- if (part.text) {
991
- const audience: Role[] | undefined = part.synthetic ? ["assistant"] : part.ignored ? ["user"] : undefined
992
- await this.connection
993
- .sessionUpdate({
994
- sessionId,
995
- update: {
996
- sessionUpdate: message.info.role === "user" ? "user_message_chunk" : "agent_message_chunk",
997
- messageId: message.info.id,
998
- content: {
999
- type: "text",
1000
- text: part.text,
1001
- ...(audience && { annotations: { audience } }),
1002
- },
1003
- },
1004
- })
1005
- .catch((err) => {
1006
- log.error("failed to send text to ACP", { error: err })
1007
- })
1008
- }
1009
- } else if (part.type === "file") {
1010
- // Replay file attachments as appropriate ACP content blocks.
1011
- // OpenCode stores files internally as { type: "file", url, filename, mime }.
1012
- // We convert these back to ACP blocks based on the URL scheme and MIME type:
1013
- // - file:// URLs → resource_link
1014
- // - data: URLs with image/* → image block
1015
- // - data: URLs with text/* or application/json → resource with text
1016
- // - data: URLs with other types → resource with blob
1017
- const url = part.url
1018
- const filename = part.filename ?? "file"
1019
- const mime = part.mime || "application/octet-stream"
1020
- const messageChunk = message.info.role === "user" ? "user_message_chunk" : "agent_message_chunk"
1021
-
1022
- if (url.startsWith("file://")) {
1023
- // Local file reference - send as resource_link
1024
- await this.connection
1025
- .sessionUpdate({
1026
- sessionId,
1027
- update: {
1028
- sessionUpdate: messageChunk,
1029
- messageId: message.info.id,
1030
- content: { type: "resource_link", uri: url, name: filename, mimeType: mime },
1031
- },
1032
- })
1033
- .catch((err) => {
1034
- log.error("failed to send resource_link to ACP", { error: err })
1035
- })
1036
- } else if (url.startsWith("data:")) {
1037
- // Embedded content - parse data URL and send as appropriate block type
1038
- const base64Match = url.match(/^data:([^;]+);base64,(.*)$/)
1039
- const dataMime = base64Match?.[1]
1040
- const base64Data = base64Match?.[2] ?? ""
1041
-
1042
- const effectiveMime = dataMime || mime
1043
-
1044
- if (effectiveMime.startsWith("image/")) {
1045
- // Image - send as image block
1046
- await this.connection
1047
- .sessionUpdate({
1048
- sessionId,
1049
- update: {
1050
- sessionUpdate: messageChunk,
1051
- messageId: message.info.id,
1052
- content: {
1053
- type: "image",
1054
- mimeType: effectiveMime,
1055
- data: base64Data,
1056
- uri: pathToFileURL(filename).href,
1057
- },
1058
- },
1059
- })
1060
- .catch((err) => {
1061
- log.error("failed to send image to ACP", { error: err })
1062
- })
1063
- } else {
1064
- // Non-image: text types get decoded, binary types stay as blob
1065
- const isText = effectiveMime.startsWith("text/") || effectiveMime === "application/json"
1066
- const fileUri = pathToFileURL(filename).href
1067
- const resource = isText
1068
- ? {
1069
- uri: fileUri,
1070
- mimeType: effectiveMime,
1071
- text: Buffer.from(base64Data, "base64").toString("utf-8"),
1072
- }
1073
- : { uri: fileUri, mimeType: effectiveMime, blob: base64Data }
1074
-
1075
- await this.connection
1076
- .sessionUpdate({
1077
- sessionId,
1078
- update: {
1079
- sessionUpdate: messageChunk,
1080
- messageId: message.info.id,
1081
- content: { type: "resource", resource },
1082
- },
1083
- })
1084
- .catch((err) => {
1085
- log.error("failed to send resource to ACP", { error: err })
1086
- })
1087
- }
1088
- }
1089
- // URLs that don't match file:// or data: are skipped (unsupported)
1090
- } else if (part.type === "reasoning") {
1091
- if (part.text) {
1092
- await this.connection
1093
- .sessionUpdate({
1094
- sessionId,
1095
- update: {
1096
- sessionUpdate: "agent_thought_chunk",
1097
- messageId: message.info.id,
1098
- content: {
1099
- type: "text",
1100
- text: part.text,
1101
- },
1102
- },
1103
- })
1104
- .catch((err) => {
1105
- log.error("failed to send reasoning to ACP", { error: err })
1106
- })
1107
- }
1108
- }
1109
- }
1110
- }
1111
-
1112
- private bashOutput(part: ToolPart) {
1113
- if (part.tool !== "bash") return
1114
- if (!("metadata" in part.state) || !part.state.metadata || typeof part.state.metadata !== "object") return
1115
- const output = part.state.metadata["output"]
1116
- if (typeof output !== "string") return
1117
- return output
1118
- }
1119
-
1120
- private async toolStart(sessionId: string, part: ToolPart) {
1121
- if (this.toolStarts.has(part.callID)) return
1122
- this.toolStarts.add(part.callID)
1123
- await this.connection
1124
- .sessionUpdate({
1125
- sessionId,
1126
- update: {
1127
- sessionUpdate: "tool_call",
1128
- toolCallId: part.callID,
1129
- title: part.tool,
1130
- kind: toToolKind(part.tool),
1131
- status: "pending",
1132
- locations: [],
1133
- rawInput: {},
1134
- },
1135
- })
1136
- .catch((error) => {
1137
- log.error("failed to send tool pending to ACP", { error })
1138
- })
1139
- }
1140
-
1141
- private async loadAvailableModes(directory: string): Promise<ModeOption[]> {
1142
- const agents = await this.config.sdk.app
1143
- .agents(
1144
- {
1145
- directory,
1146
- },
1147
- { throwOnError: true },
1148
- )
1149
- .then((resp) => resp.data!)
1150
-
1151
- return agents
1152
- .filter((agent) => agent.mode !== "subagent" && !agent.hidden)
1153
- .map((agent) => ({
1154
- id: agent.name,
1155
- name: agent.name,
1156
- description: agent.description,
1157
- }))
1158
- }
1159
-
1160
- private async resolveModeState(
1161
- directory: string,
1162
- sessionId: string,
1163
- ): Promise<{ availableModes: ModeOption[]; currentModeId?: string }> {
1164
- const availableModes = await this.loadAvailableModes(directory)
1165
- const currentModeId =
1166
- this.sessionManager.get(sessionId).modeId ||
1167
- (await (async () => {
1168
- if (!availableModes.length) return undefined
1169
- const defaultAgentName = await AgentModule.defaultAgent()
1170
- const resolvedModeId =
1171
- availableModes.find((mode) => mode.name === defaultAgentName)?.id ?? availableModes[0].id
1172
- this.sessionManager.setMode(sessionId, resolvedModeId)
1173
- return resolvedModeId
1174
- })())
1175
-
1176
- return { availableModes, currentModeId }
1177
- }
1178
-
1179
- private async loadSessionMode(params: LoadSessionRequest) {
1180
- const directory = params.cwd
1181
- const model = await defaultModel(this.config, directory)
1182
- const sessionId = params.sessionId
1183
-
1184
- const providers = await this.sdk.config.providers({ directory }).then((x) => x.data!.providers)
1185
- const entries = sortProvidersByName(providers)
1186
- const availableVariants = modelVariantsFromProviders(entries, model)
1187
- const currentVariant = this.sessionManager.getVariant(sessionId)
1188
- if (currentVariant && !availableVariants.includes(currentVariant)) {
1189
- this.sessionManager.setVariant(sessionId, undefined)
1190
- }
1191
- const availableModels = buildAvailableModels(entries, { includeVariants: true })
1192
- const modeState = await this.resolveModeState(directory, sessionId)
1193
- const currentModeId = modeState.currentModeId
1194
- const modes = currentModeId
1195
- ? {
1196
- availableModes: modeState.availableModes,
1197
- currentModeId,
1198
- }
1199
- : undefined
1200
-
1201
- const commands = await this.config.sdk.command
1202
- .list(
1203
- {
1204
- directory,
1205
- },
1206
- { throwOnError: true },
1207
- )
1208
- .then((resp) => resp.data!)
1209
-
1210
- const availableCommands = commands.map((command) => ({
1211
- name: command.name,
1212
- description: command.description ?? "",
1213
- }))
1214
- const names = new Set(availableCommands.map((c) => c.name))
1215
- if (!names.has("compact"))
1216
- availableCommands.push({
1217
- name: "compact",
1218
- description: "compact the session",
1219
- })
1220
-
1221
- const mcpServers: Record<string, Config.Mcp> = {}
1222
- for (const server of params.mcpServers) {
1223
- if ("type" in server) {
1224
- mcpServers[server.name] = {
1225
- url: server.url,
1226
- headers: server.headers.reduce<Record<string, string>>((acc, { name, value }) => {
1227
- acc[name] = value
1228
- return acc
1229
- }, {}),
1230
- type: "remote",
1231
- }
1232
- } else {
1233
- mcpServers[server.name] = {
1234
- type: "local",
1235
- command: [server.command, ...server.args],
1236
- environment: server.env.reduce<Record<string, string>>((acc, { name, value }) => {
1237
- acc[name] = value
1238
- return acc
1239
- }, {}),
1240
- }
1241
- }
1242
- }
1243
-
1244
- await Promise.all(
1245
- Object.entries(mcpServers).map(async ([key, mcp]) => {
1246
- await this.sdk.mcp
1247
- .add(
1248
- {
1249
- directory,
1250
- name: key,
1251
- config: mcp,
1252
- },
1253
- { throwOnError: true },
1254
- )
1255
- .catch((error) => {
1256
- log.error("failed to add mcp server", { name: key, error })
1257
- })
1258
- }),
1259
- )
1260
-
1261
- setTimeout(() => {
1262
- this.connection.sessionUpdate({
1263
- sessionId,
1264
- update: {
1265
- sessionUpdate: "available_commands_update",
1266
- availableCommands,
1267
- },
1268
- })
1269
- }, 0)
1270
-
1271
- return {
1272
- sessionId,
1273
- models: {
1274
- currentModelId: formatModelIdWithVariant(model, currentVariant, availableVariants, true),
1275
- availableModels,
1276
- },
1277
- modes,
1278
- configOptions: buildConfigOptions({
1279
- currentModelId: formatModelIdWithVariant(model, currentVariant, availableVariants, true),
1280
- availableModels,
1281
- modes,
1282
- }),
1283
- _meta: buildVariantMeta({
1284
- model,
1285
- variant: this.sessionManager.getVariant(sessionId),
1286
- availableVariants,
1287
- }),
1288
- }
1289
- }
1290
-
1291
- async unstable_setSessionModel(params: SetSessionModelRequest) {
1292
- const session = this.sessionManager.get(params.sessionId)
1293
- const providers = await this.sdk.config
1294
- .providers({ directory: session.cwd }, { throwOnError: true })
1295
- .then((x) => x.data!.providers)
1296
-
1297
- const selection = parseModelSelection(params.modelId, providers)
1298
- this.sessionManager.setModel(session.id, selection.model)
1299
- this.sessionManager.setVariant(session.id, selection.variant)
1300
-
1301
- const entries = sortProvidersByName(providers)
1302
- const availableVariants = modelVariantsFromProviders(entries, selection.model)
1303
-
1304
- return {
1305
- _meta: buildVariantMeta({
1306
- model: selection.model,
1307
- variant: selection.variant,
1308
- availableVariants,
1309
- }),
1310
- }
1311
- }
1312
-
1313
- async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse | void> {
1314
- const session = this.sessionManager.get(params.sessionId)
1315
- const availableModes = await this.loadAvailableModes(session.cwd)
1316
- if (!availableModes.some((mode) => mode.id === params.modeId)) {
1317
- throw new Error(`Agent not found: ${params.modeId}`)
1318
- }
1319
- this.sessionManager.setMode(params.sessionId, params.modeId)
1320
- }
1321
-
1322
- async setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse> {
1323
- const session = this.sessionManager.get(params.sessionId)
1324
- const providers = await this.sdk.config
1325
- .providers({ directory: session.cwd }, { throwOnError: true })
1326
- .then((x) => x.data!.providers)
1327
- const entries = sortProvidersByName(providers)
1328
-
1329
- if (params.configId === "model") {
1330
- if (typeof params.value !== "string") throw RequestError.invalidParams("model value must be a string")
1331
- const selection = parseModelSelection(params.value, providers)
1332
- this.sessionManager.setModel(session.id, selection.model)
1333
- this.sessionManager.setVariant(session.id, selection.variant)
1334
- } else if (params.configId === "mode") {
1335
- if (typeof params.value !== "string") throw RequestError.invalidParams("mode value must be a string")
1336
- const availableModes = await this.loadAvailableModes(session.cwd)
1337
- if (!availableModes.some((mode) => mode.id === params.value)) {
1338
- throw RequestError.invalidParams(JSON.stringify({ error: `Mode not found: ${params.value}` }))
1339
- }
1340
- this.sessionManager.setMode(session.id, params.value)
1341
- } else {
1342
- throw RequestError.invalidParams(JSON.stringify({ error: `Unknown config option: ${params.configId}` }))
1343
- }
1344
-
1345
- const updatedSession = this.sessionManager.get(session.id)
1346
- const model = updatedSession.model ?? (await defaultModel(this.config, session.cwd))
1347
- const availableVariants = modelVariantsFromProviders(entries, model)
1348
- const currentModelId = formatModelIdWithVariant(model, updatedSession.variant, availableVariants, true)
1349
- const availableModels = buildAvailableModels(entries, { includeVariants: true })
1350
- const modeState = await this.resolveModeState(session.cwd, session.id)
1351
- const modes = modeState.currentModeId
1352
- ? { availableModes: modeState.availableModes, currentModeId: modeState.currentModeId }
1353
- : undefined
1354
-
1355
- return {
1356
- configOptions: buildConfigOptions({ currentModelId, availableModels, modes }),
1357
- }
1358
- }
1359
-
1360
- async prompt(params: PromptRequest) {
1361
- const sessionID = params.sessionId
1362
- const session = this.sessionManager.get(sessionID)
1363
- const directory = session.cwd
1364
-
1365
- const current = session.model
1366
- const model = current ?? (await defaultModel(this.config, directory))
1367
- if (!current) {
1368
- this.sessionManager.setModel(session.id, model)
1369
- }
1370
- const agent = session.modeId ?? (await AgentModule.defaultAgent())
1371
-
1372
- const parts: Array<
1373
- | { type: "text"; text: string; synthetic?: boolean; ignored?: boolean }
1374
- | { type: "file"; url: string; filename: string; mime: string }
1375
- > = []
1376
- for (const part of params.prompt) {
1377
- switch (part.type) {
1378
- case "text":
1379
- const audience = part.annotations?.audience
1380
- const forAssistant = audience?.length === 1 && audience[0] === "assistant"
1381
- const forUser = audience?.length === 1 && audience[0] === "user"
1382
- parts.push({
1383
- type: "text" as const,
1384
- text: part.text,
1385
- ...(forAssistant && { synthetic: true }),
1386
- ...(forUser && { ignored: true }),
1387
- })
1388
- break
1389
- case "image": {
1390
- const parsed = parseUri(part.uri ?? "")
1391
- const filename = parsed.type === "file" ? parsed.filename : "image"
1392
- if (part.data) {
1393
- parts.push({
1394
- type: "file",
1395
- url: `data:${part.mimeType};base64,${part.data}`,
1396
- filename,
1397
- mime: part.mimeType,
1398
- })
1399
- } else if (part.uri && part.uri.startsWith("http:")) {
1400
- parts.push({
1401
- type: "file",
1402
- url: part.uri,
1403
- filename,
1404
- mime: part.mimeType,
1405
- })
1406
- }
1407
- break
1408
- }
1409
-
1410
- case "resource_link":
1411
- const parsed = parseUri(part.uri)
1412
- // Use the name from resource_link if available
1413
- if (part.name && parsed.type === "file") {
1414
- parsed.filename = part.name
1415
- }
1416
- parts.push(parsed)
1417
-
1418
- break
1419
-
1420
- case "resource": {
1421
- const resource = part.resource
1422
- if ("text" in resource && resource.text) {
1423
- parts.push({
1424
- type: "text",
1425
- text: resource.text,
1426
- })
1427
- } else if ("blob" in resource && resource.blob && resource.mimeType) {
1428
- // Binary resource (PDFs, etc.): store as file part with data URL
1429
- const parsed = parseUri(resource.uri ?? "")
1430
- const filename = parsed.type === "file" ? parsed.filename : "file"
1431
- parts.push({
1432
- type: "file",
1433
- url: `data:${resource.mimeType};base64,${resource.blob}`,
1434
- filename,
1435
- mime: resource.mimeType,
1436
- })
1437
- }
1438
- break
1439
- }
1440
-
1441
- default:
1442
- break
1443
- }
1444
- }
1445
-
1446
- log.info("parts", { parts })
1447
-
1448
- const cmd = (() => {
1449
- const text = parts
1450
- .filter((p): p is { type: "text"; text: string } => p.type === "text")
1451
- .map((p) => p.text)
1452
- .join("")
1453
- .trim()
1454
-
1455
- if (!text.startsWith("/")) return
1456
-
1457
- const [name, ...rest] = text.slice(1).split(/\s+/)
1458
- return { name, args: rest.join(" ").trim() }
1459
- })()
1460
-
1461
- const buildUsage = (msg: AssistantMessage): Usage => ({
1462
- totalTokens:
1463
- msg.tokens.input +
1464
- msg.tokens.output +
1465
- msg.tokens.reasoning +
1466
- (msg.tokens.cache?.read ?? 0) +
1467
- (msg.tokens.cache?.write ?? 0),
1468
- inputTokens: msg.tokens.input,
1469
- outputTokens: msg.tokens.output,
1470
- thoughtTokens: msg.tokens.reasoning || undefined,
1471
- cachedReadTokens: msg.tokens.cache?.read || undefined,
1472
- cachedWriteTokens: msg.tokens.cache?.write || undefined,
1473
- })
1474
-
1475
- if (!cmd) {
1476
- const response = await this.sdk.session.prompt({
1477
- sessionID,
1478
- model: {
1479
- providerID: model.providerID,
1480
- modelID: model.modelID,
1481
- },
1482
- variant: this.sessionManager.getVariant(sessionID),
1483
- parts,
1484
- agent,
1485
- directory,
1486
- })
1487
- const msg = response.data?.info
1488
-
1489
- await sendUsageUpdate(this.connection, this.sdk, sessionID, directory)
1490
-
1491
- return {
1492
- stopReason: "end_turn" as const,
1493
- usage: msg ? buildUsage(msg) : undefined,
1494
- _meta: {},
1495
- }
1496
- }
1497
-
1498
- const command = await this.config.sdk.command
1499
- .list({ directory }, { throwOnError: true })
1500
- .then((x) => x.data!.find((c) => c.name === cmd.name))
1501
- if (command) {
1502
- const response = await this.sdk.session.command({
1503
- sessionID,
1504
- command: command.name,
1505
- arguments: cmd.args,
1506
- model: model.providerID + "/" + model.modelID,
1507
- agent,
1508
- directory,
1509
- })
1510
- const msg = response.data?.info
1511
-
1512
- await sendUsageUpdate(this.connection, this.sdk, sessionID, directory)
1513
-
1514
- return {
1515
- stopReason: "end_turn" as const,
1516
- usage: msg ? buildUsage(msg) : undefined,
1517
- _meta: {},
1518
- }
1519
- }
1520
-
1521
- switch (cmd.name) {
1522
- case "compact":
1523
- await this.config.sdk.session.summarize(
1524
- {
1525
- sessionID,
1526
- directory,
1527
- providerID: model.providerID,
1528
- modelID: model.modelID,
1529
- },
1530
- { throwOnError: true },
1531
- )
1532
- break
1533
- }
1534
-
1535
- await sendUsageUpdate(this.connection, this.sdk, sessionID, directory)
1536
-
1537
- return {
1538
- stopReason: "end_turn" as const,
1539
- _meta: {},
1540
- }
1541
- }
1542
-
1543
- async cancel(params: CancelNotification) {
1544
- const session = this.sessionManager.get(params.sessionId)
1545
- await this.config.sdk.session.abort(
1546
- {
1547
- sessionID: params.sessionId,
1548
- directory: session.cwd,
1549
- },
1550
- { throwOnError: true },
1551
- )
1552
- }
1553
- }
1554
-
1555
- function toToolKind(toolName: string): ToolKind {
1556
- const tool = toolName.toLocaleLowerCase()
1557
- switch (tool) {
1558
- case "bash":
1559
- return "execute"
1560
- case "webfetch":
1561
- return "fetch"
1562
-
1563
- case "edit":
1564
- case "patch":
1565
- case "write":
1566
- return "edit"
1567
-
1568
- case "grep":
1569
- case "glob":
1570
- case "context7_resolve_library_id":
1571
- case "context7_get_library_docs":
1572
- return "search"
1573
-
1574
- case "list":
1575
- case "read":
1576
- return "read"
1577
-
1578
- default:
1579
- return "other"
1580
- }
1581
- }
1582
-
1583
- function toLocations(toolName: string, input: Record<string, any>): { path: string }[] {
1584
- const tool = toolName.toLocaleLowerCase()
1585
- switch (tool) {
1586
- case "read":
1587
- case "edit":
1588
- case "write":
1589
- return input["filePath"] ? [{ path: input["filePath"] }] : []
1590
- case "glob":
1591
- case "grep":
1592
- return input["path"] ? [{ path: input["path"] }] : []
1593
- case "bash":
1594
- return []
1595
- case "list":
1596
- return input["path"] ? [{ path: input["path"] }] : []
1597
- default:
1598
- return []
1599
- }
1600
- }
1601
-
1602
- async function defaultModel(config: ACPConfig, cwd?: string): Promise<{ providerID: ProviderID; modelID: ModelID }> {
1603
- const sdk = config.sdk
1604
- const configured = config.defaultModel
1605
- if (configured) return configured
1606
-
1607
- const directory = cwd ?? process.cwd()
1608
-
1609
- const specified = await sdk.config
1610
- .get({ directory }, { throwOnError: true })
1611
- .then((resp) => {
1612
- const cfg = resp.data
1613
- if (!cfg || !cfg.model) return undefined
1614
- return Provider.parseModel(cfg.model)
1615
- })
1616
- .catch((error) => {
1617
- log.error("failed to load user config for default model", { error })
1618
- return undefined
1619
- })
1620
-
1621
- const providers = await sdk.config
1622
- .providers({ directory }, { throwOnError: true })
1623
- .then((x) => x.data?.providers ?? [])
1624
- .catch((error) => {
1625
- log.error("failed to list providers for default model", { error })
1626
- return []
1627
- })
1628
-
1629
- if (specified && providers.length) {
1630
- const provider = providers.find((p) => p.id === specified.providerID)
1631
- if (provider && provider.models[specified.modelID]) return specified
1632
- }
1633
-
1634
- if (specified && !providers.length) return specified
1635
-
1636
- const opencodeProvider = providers.find((p) => p.id === "opencode")
1637
- if (opencodeProvider) {
1638
- if (opencodeProvider.models["big-pickle"]) {
1639
- return { providerID: ProviderID.opencode, modelID: ModelID.make("big-pickle") }
1640
- }
1641
- const [best] = Provider.sort(Object.values(opencodeProvider.models))
1642
- if (best) {
1643
- return {
1644
- providerID: ProviderID.make(best.providerID),
1645
- modelID: ModelID.make(best.id),
1646
- }
1647
- }
1648
- }
1649
-
1650
- const models = providers.flatMap((p) => Object.values(p.models))
1651
- const [best] = Provider.sort(models)
1652
- if (best) {
1653
- return {
1654
- providerID: ProviderID.make(best.providerID),
1655
- modelID: ModelID.make(best.id),
1656
- }
1657
- }
1658
-
1659
- if (specified) return specified
1660
-
1661
- return { providerID: ProviderID.opencode, modelID: ModelID.make("big-pickle") }
1662
- }
1663
-
1664
- function parseUri(
1665
- uri: string,
1666
- ): { type: "file"; url: string; filename: string; mime: string } | { type: "text"; text: string } {
1667
- try {
1668
- if (uri.startsWith("file://")) {
1669
- const path = uri.slice(7)
1670
- const name = path.split("/").pop() || path
1671
- return {
1672
- type: "file",
1673
- url: uri,
1674
- filename: name,
1675
- mime: "text/plain",
1676
- }
1677
- }
1678
- if (uri.startsWith("zed://")) {
1679
- const url = new URL(uri)
1680
- const path = url.searchParams.get("path")
1681
- if (path) {
1682
- const name = path.split("/").pop() || path
1683
- return {
1684
- type: "file",
1685
- url: pathToFileURL(path).href,
1686
- filename: name,
1687
- mime: "text/plain",
1688
- }
1689
- }
1690
- }
1691
- return {
1692
- type: "text",
1693
- text: uri,
1694
- }
1695
- } catch {
1696
- return {
1697
- type: "text",
1698
- text: uri,
1699
- }
1700
- }
1701
- }
1702
-
1703
- function getNewContent(fileOriginal: string, unifiedDiff: string): string | undefined {
1704
- const result = applyPatch(fileOriginal, unifiedDiff)
1705
- if (result === false) {
1706
- log.error("Failed to apply unified diff (context mismatch)")
1707
- return undefined
1708
- }
1709
- return result
1710
- }
1711
-
1712
- function sortProvidersByName<T extends { name: string }>(providers: T[]): T[] {
1713
- return [...providers].sort((a, b) => {
1714
- const nameA = a.name.toLowerCase()
1715
- const nameB = b.name.toLowerCase()
1716
- if (nameA < nameB) return -1
1717
- if (nameA > nameB) return 1
1718
- return 0
1719
- })
1720
- }
1721
-
1722
- function modelVariantsFromProviders(
1723
- providers: Array<{ id: string; models: Record<string, { variants?: Record<string, any> }> }>,
1724
- model: { providerID: ProviderID; modelID: ModelID },
1725
- ): string[] {
1726
- const provider = providers.find((entry) => entry.id === model.providerID)
1727
- if (!provider) return []
1728
- const modelInfo = provider.models[model.modelID]
1729
- if (!modelInfo?.variants) return []
1730
- return Object.keys(modelInfo.variants)
1731
- }
1732
-
1733
- function buildAvailableModels(
1734
- providers: Array<{ id: string; name: string; models: Record<string, any> }>,
1735
- options: { includeVariants?: boolean } = {},
1736
- ): ModelOption[] {
1737
- const includeVariants = options.includeVariants ?? false
1738
- return providers.flatMap((provider) => {
1739
- const unsorted: Array<{ id: string; name: string; variants?: Record<string, any> }> = Object.values(
1740
- provider.models,
1741
- )
1742
- const models = Provider.sort(unsorted)
1743
- return models.flatMap((model) => {
1744
- const base: ModelOption = {
1745
- modelId: `${provider.id}/${model.id}`,
1746
- name: `${provider.name}/${model.name}`,
1747
- }
1748
- if (!includeVariants || !model.variants) return [base]
1749
- const variants = Object.keys(model.variants).filter((variant) => variant !== DEFAULT_VARIANT_VALUE)
1750
- const variantOptions = variants.map((variant) => ({
1751
- modelId: `${provider.id}/${model.id}/${variant}`,
1752
- name: `${provider.name}/${model.name} (${variant})`,
1753
- }))
1754
- return [base, ...variantOptions]
1755
- })
1756
- })
1757
- }
1758
-
1759
- function formatModelIdWithVariant(
1760
- model: { providerID: ProviderID; modelID: ModelID },
1761
- variant: string | undefined,
1762
- availableVariants: string[],
1763
- includeVariant: boolean,
1764
- ) {
1765
- const base = `${model.providerID}/${model.modelID}`
1766
- if (!includeVariant || !variant || !availableVariants.includes(variant)) return base
1767
- return `${base}/${variant}`
1768
- }
1769
-
1770
- function buildVariantMeta(input: {
1771
- model: { providerID: ProviderID; modelID: ModelID }
1772
- variant?: string
1773
- availableVariants: string[]
1774
- }) {
1775
- return {
1776
- opencode: {
1777
- modelId: `${input.model.providerID}/${input.model.modelID}`,
1778
- variant: input.variant ?? null,
1779
- availableVariants: input.availableVariants,
1780
- },
1781
- }
1782
- }
1783
-
1784
- function parseModelSelection(
1785
- modelId: string,
1786
- providers: Array<{ id: string; models: Record<string, { variants?: Record<string, any> }> }>,
1787
- ): { model: { providerID: ProviderID; modelID: ModelID }; variant?: string } {
1788
- const parsed = Provider.parseModel(modelId)
1789
- const provider = providers.find((p) => p.id === parsed.providerID)
1790
- if (!provider) {
1791
- return { model: parsed, variant: undefined }
1792
- }
1793
-
1794
- // Check if modelID exists directly
1795
- if (provider.models[parsed.modelID]) {
1796
- return { model: parsed, variant: undefined }
1797
- }
1798
-
1799
- // Try to extract variant from end of modelID (e.g., "claude-sonnet-4/high" -> model: "claude-sonnet-4", variant: "high")
1800
- const segments = parsed.modelID.split("/")
1801
- if (segments.length > 1) {
1802
- const candidateVariant = segments[segments.length - 1]
1803
- const baseModelId = segments.slice(0, -1).join("/")
1804
- const baseModelInfo = provider.models[baseModelId]
1805
- if (baseModelInfo?.variants && candidateVariant in baseModelInfo.variants) {
1806
- return {
1807
- model: { providerID: parsed.providerID, modelID: ModelID.make(baseModelId) },
1808
- variant: candidateVariant,
1809
- }
1810
- }
1811
- }
1812
-
1813
- return { model: parsed, variant: undefined }
1814
- }
1815
-
1816
- function buildConfigOptions(input: {
1817
- currentModelId: string
1818
- availableModels: ModelOption[]
1819
- modes?: { availableModes: ModeOption[]; currentModeId: string } | undefined
1820
- }): SessionConfigOption[] {
1821
- const options: SessionConfigOption[] = [
1822
- {
1823
- id: "model",
1824
- name: "Model",
1825
- category: "model",
1826
- type: "select",
1827
- currentValue: input.currentModelId,
1828
- options: input.availableModels.map((m) => ({ value: m.modelId, name: m.name })),
1829
- },
1830
- ]
1831
- if (input.modes) {
1832
- options.push({
1833
- id: "mode",
1834
- name: "Session Mode",
1835
- category: "mode",
1836
- type: "select",
1837
- currentValue: input.modes.currentModeId,
1838
- options: input.modes.availableModes.map((m) => ({
1839
- value: m.id,
1840
- name: m.name,
1841
- ...(m.description ? { description: m.description } : {}),
1842
- })),
1843
- })
1844
- }
1845
- return options
1846
- }
1847
- }