alchemylab-opencode 1.0.7 → 1.0.8

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 (358) hide show
  1. package/package.json +2 -15
  2. package/build.mjs +0 -192
  3. package/packages/opencode/package.json +0 -138
  4. package/packages/opencode/src/account/account.sql.ts +0 -39
  5. package/packages/opencode/src/account/index.ts +0 -463
  6. package/packages/opencode/src/account/repo.ts +0 -163
  7. package/packages/opencode/src/account/schema.ts +0 -91
  8. package/packages/opencode/src/acp/README.md +0 -174
  9. package/packages/opencode/src/acp/agent.ts +0 -1847
  10. package/packages/opencode/src/acp/session.ts +0 -116
  11. package/packages/opencode/src/acp/types.ts +0 -24
  12. package/packages/opencode/src/agent/agent.ts +0 -420
  13. package/packages/opencode/src/agent/generate.txt +0 -75
  14. package/packages/opencode/src/agent/prompt/compaction.txt +0 -15
  15. package/packages/opencode/src/agent/prompt/explore.txt +0 -18
  16. package/packages/opencode/src/agent/prompt/summary.txt +0 -11
  17. package/packages/opencode/src/agent/prompt/title.txt +0 -44
  18. package/packages/opencode/src/auth/index.ts +0 -110
  19. package/packages/opencode/src/bus/bus-event.ts +0 -40
  20. package/packages/opencode/src/bus/global.ts +0 -10
  21. package/packages/opencode/src/bus/index.ts +0 -185
  22. package/packages/opencode/src/cli/bootstrap.ts +0 -17
  23. package/packages/opencode/src/cli/cmd/cmd.ts +0 -7
  24. package/packages/opencode/src/cli/cmd/run.ts +0 -689
  25. package/packages/opencode/src/cli/cmd/serve.ts +0 -24
  26. package/packages/opencode/src/cli/cmd/tui/event.ts +0 -49
  27. package/packages/opencode/src/cli/effect/prompt.ts +0 -25
  28. package/packages/opencode/src/cli/error.ts +0 -46
  29. package/packages/opencode/src/cli/heap.ts +0 -59
  30. package/packages/opencode/src/cli/logo.ts +0 -6
  31. package/packages/opencode/src/cli/network.ts +0 -60
  32. package/packages/opencode/src/cli/ui.ts +0 -133
  33. package/packages/opencode/src/cli/upgrade.ts +0 -31
  34. package/packages/opencode/src/command/index.ts +0 -195
  35. package/packages/opencode/src/command/template/initialize.txt +0 -66
  36. package/packages/opencode/src/command/template/review.txt +0 -101
  37. package/packages/opencode/src/config/config.ts +0 -1599
  38. package/packages/opencode/src/config/console-state.ts +0 -15
  39. package/packages/opencode/src/config/markdown.ts +0 -99
  40. package/packages/opencode/src/config/paths.ts +0 -167
  41. package/packages/opencode/src/config/tui-migrate.ts +0 -155
  42. package/packages/opencode/src/config/tui-schema.ts +0 -37
  43. package/packages/opencode/src/config/tui.ts +0 -179
  44. package/packages/opencode/src/control-plane/adaptors/index.ts +0 -20
  45. package/packages/opencode/src/control-plane/adaptors/worktree.ts +0 -42
  46. package/packages/opencode/src/control-plane/schema.ts +0 -17
  47. package/packages/opencode/src/control-plane/sse.ts +0 -66
  48. package/packages/opencode/src/control-plane/types.ts +0 -32
  49. package/packages/opencode/src/control-plane/workspace.sql.ts +0 -17
  50. package/packages/opencode/src/control-plane/workspace.ts +0 -168
  51. package/packages/opencode/src/effect/cross-spawn-spawner.ts +0 -502
  52. package/packages/opencode/src/effect/instance-ref.ts +0 -6
  53. package/packages/opencode/src/effect/instance-registry.ts +0 -12
  54. package/packages/opencode/src/effect/instance-state.ts +0 -82
  55. package/packages/opencode/src/effect/run-service.ts +0 -33
  56. package/packages/opencode/src/effect/runner.ts +0 -216
  57. package/packages/opencode/src/env/index.ts +0 -28
  58. package/packages/opencode/src/file/ignore.ts +0 -82
  59. package/packages/opencode/src/file/index.ts +0 -686
  60. package/packages/opencode/src/file/protected.ts +0 -59
  61. package/packages/opencode/src/file/ripgrep.ts +0 -376
  62. package/packages/opencode/src/file/time.ts +0 -133
  63. package/packages/opencode/src/file/watcher.ts +0 -173
  64. package/packages/opencode/src/filesystem/index.ts +0 -236
  65. package/packages/opencode/src/flag/flag.ts +0 -156
  66. package/packages/opencode/src/format/formatter.ts +0 -413
  67. package/packages/opencode/src/format/index.ts +0 -203
  68. package/packages/opencode/src/git/index.ts +0 -303
  69. package/packages/opencode/src/global/index.ts +0 -56
  70. package/packages/opencode/src/id/id.ts +0 -85
  71. package/packages/opencode/src/ide/index.ts +0 -74
  72. package/packages/opencode/src/installation/index.ts +0 -355
  73. package/packages/opencode/src/installation/meta.ts +0 -7
  74. package/packages/opencode/src/lsp/client.ts +0 -252
  75. package/packages/opencode/src/lsp/index.ts +0 -558
  76. package/packages/opencode/src/lsp/language.ts +0 -120
  77. package/packages/opencode/src/lsp/launch.ts +0 -21
  78. package/packages/opencode/src/lsp/server.ts +0 -1968
  79. package/packages/opencode/src/mcp/auth.ts +0 -173
  80. package/packages/opencode/src/mcp/index.ts +0 -931
  81. package/packages/opencode/src/mcp/oauth-callback.ts +0 -216
  82. package/packages/opencode/src/mcp/oauth-provider.ts +0 -185
  83. package/packages/opencode/src/node-loader.mjs +0 -104
  84. package/packages/opencode/src/node-preload.mjs +0 -39
  85. package/packages/opencode/src/node.ts +0 -1
  86. package/packages/opencode/src/npm/index.ts +0 -188
  87. package/packages/opencode/src/patch/index.ts +0 -680
  88. package/packages/opencode/src/permission/arity.ts +0 -163
  89. package/packages/opencode/src/permission/evaluate.ts +0 -15
  90. package/packages/opencode/src/permission/index.ts +0 -325
  91. package/packages/opencode/src/permission/schema.ts +0 -17
  92. package/packages/opencode/src/plugin/cloudflare.ts +0 -67
  93. package/packages/opencode/src/plugin/codex.ts +0 -608
  94. package/packages/opencode/src/plugin/github-copilot/copilot.ts +0 -361
  95. package/packages/opencode/src/plugin/github-copilot/models.ts +0 -144
  96. package/packages/opencode/src/plugin/index.ts +0 -299
  97. package/packages/opencode/src/plugin/install.ts +0 -439
  98. package/packages/opencode/src/plugin/loader.ts +0 -174
  99. package/packages/opencode/src/plugin/meta.ts +0 -188
  100. package/packages/opencode/src/plugin/shared.ts +0 -323
  101. package/packages/opencode/src/project/bootstrap.ts +0 -31
  102. package/packages/opencode/src/project/instance.ts +0 -175
  103. package/packages/opencode/src/project/project.sql.ts +0 -16
  104. package/packages/opencode/src/project/project.ts +0 -519
  105. package/packages/opencode/src/project/schema.ts +0 -16
  106. package/packages/opencode/src/project/state.ts +0 -70
  107. package/packages/opencode/src/project/vcs.ts +0 -240
  108. package/packages/opencode/src/provider/auth.ts +0 -253
  109. package/packages/opencode/src/provider/error.ts +0 -197
  110. package/packages/opencode/src/provider/models.ts +0 -162
  111. package/packages/opencode/src/provider/provider.ts +0 -1709
  112. package/packages/opencode/src/provider/schema.ts +0 -38
  113. package/packages/opencode/src/provider/sdk/copilot/README.md +0 -5
  114. package/packages/opencode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +0 -170
  115. package/packages/opencode/src/provider/sdk/copilot/chat/get-response-metadata.ts +0 -15
  116. package/packages/opencode/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +0 -19
  117. package/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +0 -64
  118. package/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +0 -815
  119. package/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +0 -28
  120. package/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +0 -44
  121. package/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +0 -83
  122. package/packages/opencode/src/provider/sdk/copilot/copilot-provider.ts +0 -100
  123. package/packages/opencode/src/provider/sdk/copilot/index.ts +0 -2
  124. package/packages/opencode/src/provider/sdk/copilot/openai-compatible-error.ts +0 -27
  125. package/packages/opencode/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +0 -335
  126. package/packages/opencode/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +0 -22
  127. package/packages/opencode/src/provider/sdk/copilot/responses/openai-config.ts +0 -18
  128. package/packages/opencode/src/provider/sdk/copilot/responses/openai-error.ts +0 -22
  129. package/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +0 -214
  130. package/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +0 -1769
  131. package/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +0 -173
  132. package/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-settings.ts +0 -1
  133. package/packages/opencode/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +0 -87
  134. package/packages/opencode/src/provider/sdk/copilot/responses/tool/file-search.ts +0 -127
  135. package/packages/opencode/src/provider/sdk/copilot/responses/tool/image-generation.ts +0 -114
  136. package/packages/opencode/src/provider/sdk/copilot/responses/tool/local-shell.ts +0 -64
  137. package/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +0 -103
  138. package/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search.ts +0 -102
  139. package/packages/opencode/src/provider/transform.ts +0 -1050
  140. package/packages/opencode/src/pty/index.ts +0 -397
  141. package/packages/opencode/src/pty/pty.node.ts +0 -27
  142. package/packages/opencode/src/pty/pty.ts +0 -25
  143. package/packages/opencode/src/pty/schema.ts +0 -17
  144. package/packages/opencode/src/question/index.ts +0 -224
  145. package/packages/opencode/src/question/schema.ts +0 -17
  146. package/packages/opencode/src/run.ts +0 -28
  147. package/packages/opencode/src/serve.ts +0 -45
  148. package/packages/opencode/src/server/error.ts +0 -36
  149. package/packages/opencode/src/server/event.ts +0 -7
  150. package/packages/opencode/src/server/instance.ts +0 -324
  151. package/packages/opencode/src/server/mdns.ts +0 -60
  152. package/packages/opencode/src/server/middleware.ts +0 -33
  153. package/packages/opencode/src/server/projectors.ts +0 -28
  154. package/packages/opencode/src/server/proxy.ts +0 -131
  155. package/packages/opencode/src/server/router.ts +0 -105
  156. package/packages/opencode/src/server/routes/config.ts +0 -92
  157. package/packages/opencode/src/server/routes/event.ts +0 -83
  158. package/packages/opencode/src/server/routes/experimental.ts +0 -374
  159. package/packages/opencode/src/server/routes/file.ts +0 -197
  160. package/packages/opencode/src/server/routes/global.ts +0 -312
  161. package/packages/opencode/src/server/routes/mcp.ts +0 -225
  162. package/packages/opencode/src/server/routes/permission.ts +0 -69
  163. package/packages/opencode/src/server/routes/project.ts +0 -118
  164. package/packages/opencode/src/server/routes/provider.ts +0 -171
  165. package/packages/opencode/src/server/routes/pty.ts +0 -210
  166. package/packages/opencode/src/server/routes/question.ts +0 -99
  167. package/packages/opencode/src/server/routes/session.ts +0 -1030
  168. package/packages/opencode/src/server/routes/tui.ts +0 -379
  169. package/packages/opencode/src/server/routes/workspace.ts +0 -94
  170. package/packages/opencode/src/server/server.ts +0 -353
  171. package/packages/opencode/src/session/compaction.ts +0 -428
  172. package/packages/opencode/src/session/index.ts +0 -887
  173. package/packages/opencode/src/session/instruction.ts +0 -258
  174. package/packages/opencode/src/session/llm.ts +0 -354
  175. package/packages/opencode/src/session/message-v2.ts +0 -1031
  176. package/packages/opencode/src/session/message.ts +0 -191
  177. package/packages/opencode/src/session/overflow.ts +0 -22
  178. package/packages/opencode/src/session/processor.ts +0 -523
  179. package/packages/opencode/src/session/projectors.ts +0 -135
  180. package/packages/opencode/src/session/prompt/anthropic.txt +0 -105
  181. package/packages/opencode/src/session/prompt/beast.txt +0 -147
  182. package/packages/opencode/src/session/prompt/build-switch.txt +0 -5
  183. package/packages/opencode/src/session/prompt/codex.txt +0 -79
  184. package/packages/opencode/src/session/prompt/copilot-gpt-5.txt +0 -143
  185. package/packages/opencode/src/session/prompt/default.txt +0 -105
  186. package/packages/opencode/src/session/prompt/gemini.txt +0 -155
  187. package/packages/opencode/src/session/prompt/gpt.txt +0 -107
  188. package/packages/opencode/src/session/prompt/kimi.txt +0 -95
  189. package/packages/opencode/src/session/prompt/max-steps.txt +0 -16
  190. package/packages/opencode/src/session/prompt/plan-reminder-anthropic.txt +0 -67
  191. package/packages/opencode/src/session/prompt/plan.txt +0 -26
  192. package/packages/opencode/src/session/prompt/trinity.txt +0 -97
  193. package/packages/opencode/src/session/prompt.ts +0 -1911
  194. package/packages/opencode/src/session/retry.ts +0 -106
  195. package/packages/opencode/src/session/revert.ts +0 -176
  196. package/packages/opencode/src/session/schema.ts +0 -38
  197. package/packages/opencode/src/session/session.sql.ts +0 -103
  198. package/packages/opencode/src/session/status.ts +0 -102
  199. package/packages/opencode/src/session/summary.ts +0 -177
  200. package/packages/opencode/src/session/system.ts +0 -76
  201. package/packages/opencode/src/session/todo.ts +0 -95
  202. package/packages/opencode/src/share/share-next.ts +0 -369
  203. package/packages/opencode/src/share/share.sql.ts +0 -13
  204. package/packages/opencode/src/shell/shell.ts +0 -110
  205. package/packages/opencode/src/skill/discovery.ts +0 -116
  206. package/packages/opencode/src/skill/index.ts +0 -277
  207. package/packages/opencode/src/snapshot/index.ts +0 -726
  208. package/packages/opencode/src/sql.d.ts +0 -4
  209. package/packages/opencode/src/storage/db.node.ts +0 -8
  210. package/packages/opencode/src/storage/db.ts +0 -177
  211. package/packages/opencode/src/storage/json-migration.ts +0 -425
  212. package/packages/opencode/src/storage/schema.sql.ts +0 -10
  213. package/packages/opencode/src/storage/schema.ts +0 -5
  214. package/packages/opencode/src/storage/storage.ts +0 -353
  215. package/packages/opencode/src/sync/README.md +0 -179
  216. package/packages/opencode/src/sync/event.sql.ts +0 -16
  217. package/packages/opencode/src/sync/index.ts +0 -263
  218. package/packages/opencode/src/sync/schema.ts +0 -14
  219. package/packages/opencode/src/tool/apply_patch.ts +0 -281
  220. package/packages/opencode/src/tool/apply_patch.txt +0 -33
  221. package/packages/opencode/src/tool/bash.ts +0 -496
  222. package/packages/opencode/src/tool/bash.txt +0 -117
  223. package/packages/opencode/src/tool/batch.ts +0 -183
  224. package/packages/opencode/src/tool/batch.txt +0 -24
  225. package/packages/opencode/src/tool/codesearch.ts +0 -132
  226. package/packages/opencode/src/tool/codesearch.txt +0 -12
  227. package/packages/opencode/src/tool/edit.ts +0 -667
  228. package/packages/opencode/src/tool/edit.txt +0 -10
  229. package/packages/opencode/src/tool/external-directory.ts +0 -46
  230. package/packages/opencode/src/tool/glob.ts +0 -78
  231. package/packages/opencode/src/tool/glob.txt +0 -6
  232. package/packages/opencode/src/tool/grep.ts +0 -156
  233. package/packages/opencode/src/tool/grep.txt +0 -8
  234. package/packages/opencode/src/tool/invalid.ts +0 -17
  235. package/packages/opencode/src/tool/ls.ts +0 -121
  236. package/packages/opencode/src/tool/ls.txt +0 -1
  237. package/packages/opencode/src/tool/lsp.ts +0 -97
  238. package/packages/opencode/src/tool/lsp.txt +0 -19
  239. package/packages/opencode/src/tool/multiedit.ts +0 -46
  240. package/packages/opencode/src/tool/multiedit.txt +0 -41
  241. package/packages/opencode/src/tool/plan-enter.txt +0 -14
  242. package/packages/opencode/src/tool/plan-exit.txt +0 -13
  243. package/packages/opencode/src/tool/plan.ts +0 -131
  244. package/packages/opencode/src/tool/question.ts +0 -46
  245. package/packages/opencode/src/tool/question.txt +0 -10
  246. package/packages/opencode/src/tool/read.ts +0 -332
  247. package/packages/opencode/src/tool/read.txt +0 -14
  248. package/packages/opencode/src/tool/registry.ts +0 -265
  249. package/packages/opencode/src/tool/schema.ts +0 -17
  250. package/packages/opencode/src/tool/skill.ts +0 -105
  251. package/packages/opencode/src/tool/task.ts +0 -166
  252. package/packages/opencode/src/tool/task.txt +0 -60
  253. package/packages/opencode/src/tool/todo.ts +0 -48
  254. package/packages/opencode/src/tool/todowrite.txt +0 -167
  255. package/packages/opencode/src/tool/tool.ts +0 -112
  256. package/packages/opencode/src/tool/truncate.ts +0 -144
  257. package/packages/opencode/src/tool/truncation-dir.ts +0 -4
  258. package/packages/opencode/src/tool/webfetch.ts +0 -206
  259. package/packages/opencode/src/tool/webfetch.txt +0 -13
  260. package/packages/opencode/src/tool/websearch.ts +0 -150
  261. package/packages/opencode/src/tool/websearch.txt +0 -14
  262. package/packages/opencode/src/tool/write.ts +0 -84
  263. package/packages/opencode/src/tool/write.txt +0 -8
  264. package/packages/opencode/src/util/abort.ts +0 -35
  265. package/packages/opencode/src/util/archive.ts +0 -17
  266. package/packages/opencode/src/util/color.ts +0 -19
  267. package/packages/opencode/src/util/context.ts +0 -25
  268. package/packages/opencode/src/util/data-url.ts +0 -9
  269. package/packages/opencode/src/util/defer.ts +0 -12
  270. package/packages/opencode/src/util/effect-http-client.ts +0 -11
  271. package/packages/opencode/src/util/effect-zod.ts +0 -98
  272. package/packages/opencode/src/util/error.ts +0 -77
  273. package/packages/opencode/src/util/filesystem.ts +0 -245
  274. package/packages/opencode/src/util/flock.ts +0 -333
  275. package/packages/opencode/src/util/fn.ts +0 -21
  276. package/packages/opencode/src/util/format.ts +0 -20
  277. package/packages/opencode/src/util/glob.ts +0 -34
  278. package/packages/opencode/src/util/hash.ts +0 -7
  279. package/packages/opencode/src/util/iife.ts +0 -3
  280. package/packages/opencode/src/util/keybind.ts +0 -103
  281. package/packages/opencode/src/util/lazy.ts +0 -23
  282. package/packages/opencode/src/util/locale.ts +0 -81
  283. package/packages/opencode/src/util/lock.ts +0 -98
  284. package/packages/opencode/src/util/log.ts +0 -182
  285. package/packages/opencode/src/util/network.ts +0 -9
  286. package/packages/opencode/src/util/process.ts +0 -176
  287. package/packages/opencode/src/util/queue.ts +0 -32
  288. package/packages/opencode/src/util/record.ts +0 -3
  289. package/packages/opencode/src/util/rpc.ts +0 -66
  290. package/packages/opencode/src/util/schema.ts +0 -53
  291. package/packages/opencode/src/util/scrap.ts +0 -10
  292. package/packages/opencode/src/util/signal.ts +0 -12
  293. package/packages/opencode/src/util/timeout.ts +0 -14
  294. package/packages/opencode/src/util/token.ts +0 -7
  295. package/packages/opencode/src/util/update-schema.ts +0 -13
  296. package/packages/opencode/src/util/which.ts +0 -14
  297. package/packages/opencode/src/util/wildcard.ts +0 -59
  298. package/packages/opencode/src/worktree/index.ts +0 -612
  299. package/packages/plugin/package.json +0 -38
  300. package/packages/plugin/src/example.ts +0 -18
  301. package/packages/plugin/src/index.ts +0 -282
  302. package/packages/plugin/src/shell.ts +0 -136
  303. package/packages/plugin/src/tool.ts +0 -38
  304. package/packages/plugin/src/tui.ts +0 -508
  305. package/packages/script/package.json +0 -16
  306. package/packages/script/src/index.ts +0 -77
  307. package/packages/sdk/package.json +0 -33
  308. package/packages/sdk/src/client.ts +0 -55
  309. package/packages/sdk/src/gen/client/client.gen.ts +0 -212
  310. package/packages/sdk/src/gen/client/index.ts +0 -25
  311. package/packages/sdk/src/gen/client/types.gen.ts +0 -222
  312. package/packages/sdk/src/gen/client/utils.gen.ts +0 -287
  313. package/packages/sdk/src/gen/client.gen.ts +0 -22
  314. package/packages/sdk/src/gen/core/auth.gen.ts +0 -41
  315. package/packages/sdk/src/gen/core/bodySerializer.gen.ts +0 -74
  316. package/packages/sdk/src/gen/core/params.gen.ts +0 -144
  317. package/packages/sdk/src/gen/core/pathSerializer.gen.ts +0 -167
  318. package/packages/sdk/src/gen/core/queryKeySerializer.gen.ts +0 -111
  319. package/packages/sdk/src/gen/core/serverSentEvents.gen.ts +0 -210
  320. package/packages/sdk/src/gen/core/types.gen.ts +0 -91
  321. package/packages/sdk/src/gen/core/utils.gen.ts +0 -109
  322. package/packages/sdk/src/gen/sdk.gen.ts +0 -1197
  323. package/packages/sdk/src/gen/types.gen.ts +0 -3904
  324. package/packages/sdk/src/index.ts +0 -21
  325. package/packages/sdk/src/process.ts +0 -31
  326. package/packages/sdk/src/server.ts +0 -134
  327. package/packages/sdk/src/v2/client.ts +0 -81
  328. package/packages/sdk/src/v2/gen/client/client.gen.ts +0 -285
  329. package/packages/sdk/src/v2/gen/client/index.ts +0 -25
  330. package/packages/sdk/src/v2/gen/client/types.gen.ts +0 -202
  331. package/packages/sdk/src/v2/gen/client/utils.gen.ts +0 -289
  332. package/packages/sdk/src/v2/gen/client.gen.ts +0 -18
  333. package/packages/sdk/src/v2/gen/core/auth.gen.ts +0 -41
  334. package/packages/sdk/src/v2/gen/core/bodySerializer.gen.ts +0 -82
  335. package/packages/sdk/src/v2/gen/core/params.gen.ts +0 -169
  336. package/packages/sdk/src/v2/gen/core/pathSerializer.gen.ts +0 -167
  337. package/packages/sdk/src/v2/gen/core/queryKeySerializer.gen.ts +0 -111
  338. package/packages/sdk/src/v2/gen/core/serverSentEvents.gen.ts +0 -239
  339. package/packages/sdk/src/v2/gen/core/types.gen.ts +0 -86
  340. package/packages/sdk/src/v2/gen/core/utils.gen.ts +0 -137
  341. package/packages/sdk/src/v2/gen/sdk.gen.ts +0 -4223
  342. package/packages/sdk/src/v2/gen/types.gen.ts +0 -5265
  343. package/packages/sdk/src/v2/index.ts +0 -21
  344. package/packages/sdk/src/v2/server.ts +0 -134
  345. package/packages/util/package.json +0 -20
  346. package/packages/util/src/array.ts +0 -10
  347. package/packages/util/src/binary.ts +0 -41
  348. package/packages/util/src/encode.ts +0 -51
  349. package/packages/util/src/error.ts +0 -54
  350. package/packages/util/src/fn.ts +0 -11
  351. package/packages/util/src/identifier.ts +0 -48
  352. package/packages/util/src/iife.ts +0 -3
  353. package/packages/util/src/lazy.ts +0 -11
  354. package/packages/util/src/module.ts +0 -10
  355. package/packages/util/src/path.ts +0 -37
  356. package/packages/util/src/retry.ts +0 -41
  357. package/packages/util/src/slug.ts +0 -74
  358. package/postinstall.cjs +0 -55
@@ -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
- }