rird 1.0.200

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 (350) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +18 -0
  3. package/README.md +15 -0
  4. package/bin/opencode +336 -0
  5. package/bin/pty-wrapper.js +285 -0
  6. package/bunfig.toml +4 -0
  7. package/facebook_ads_library.png +0 -0
  8. package/nul`nif +0 -0
  9. package/package.json +111 -0
  10. package/parsers-config.ts +239 -0
  11. package/rird-1.0.199.tgz +0 -0
  12. package/script/build-windows.ts +54 -0
  13. package/script/build.ts +167 -0
  14. package/script/postinstall.mjs +544 -0
  15. package/script/publish-registries.ts +187 -0
  16. package/script/publish.ts +72 -0
  17. package/script/schema.ts +47 -0
  18. package/src/acp/README.md +164 -0
  19. package/src/acp/agent.ts +1063 -0
  20. package/src/acp/session.ts +101 -0
  21. package/src/acp/types.ts +22 -0
  22. package/src/agent/agent.ts +367 -0
  23. package/src/agent/generate.txt +75 -0
  24. package/src/agent/prompt/compaction.txt +12 -0
  25. package/src/agent/prompt/explore.txt +18 -0
  26. package/src/agent/prompt/summary.txt +10 -0
  27. package/src/agent/prompt/title.txt +36 -0
  28. package/src/auth/index.ts +70 -0
  29. package/src/bun/index.ts +114 -0
  30. package/src/bus/bus-event.ts +43 -0
  31. package/src/bus/global.ts +10 -0
  32. package/src/bus/index.ts +105 -0
  33. package/src/cli/bootstrap.ts +17 -0
  34. package/src/cli/cmd/acp.ts +88 -0
  35. package/src/cli/cmd/agent.ts +256 -0
  36. package/src/cli/cmd/auth.ts +391 -0
  37. package/src/cli/cmd/cmd.ts +7 -0
  38. package/src/cli/cmd/debug/config.ts +15 -0
  39. package/src/cli/cmd/debug/file.ts +91 -0
  40. package/src/cli/cmd/debug/index.ts +43 -0
  41. package/src/cli/cmd/debug/lsp.ts +48 -0
  42. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  43. package/src/cli/cmd/debug/scrap.ts +15 -0
  44. package/src/cli/cmd/debug/skill.ts +15 -0
  45. package/src/cli/cmd/debug/snapshot.ts +48 -0
  46. package/src/cli/cmd/export.ts +88 -0
  47. package/src/cli/cmd/generate.ts +38 -0
  48. package/src/cli/cmd/github.ts +1400 -0
  49. package/src/cli/cmd/import.ts +98 -0
  50. package/src/cli/cmd/mcp.ts +654 -0
  51. package/src/cli/cmd/models.ts +77 -0
  52. package/src/cli/cmd/pr.ts +112 -0
  53. package/src/cli/cmd/run.ts +368 -0
  54. package/src/cli/cmd/serve.ts +31 -0
  55. package/src/cli/cmd/session.ts +106 -0
  56. package/src/cli/cmd/stats.ts +298 -0
  57. package/src/cli/cmd/tui/app.tsx +696 -0
  58. package/src/cli/cmd/tui/attach.ts +30 -0
  59. package/src/cli/cmd/tui/component/border.tsx +21 -0
  60. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  61. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  62. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  63. package/src/cli/cmd/tui/component/dialog-model.tsx +245 -0
  64. package/src/cli/cmd/tui/component/dialog-provider.tsx +224 -0
  65. package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
  66. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  67. package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
  68. package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
  69. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  70. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  71. package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
  72. package/src/cli/cmd/tui/component/logo.tsx +35 -0
  73. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +574 -0
  74. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  75. package/src/cli/cmd/tui/component/prompt/index.tsx +1090 -0
  76. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  77. package/src/cli/cmd/tui/component/tips.ts +27 -0
  78. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  79. package/src/cli/cmd/tui/context/args.tsx +14 -0
  80. package/src/cli/cmd/tui/context/directory.ts +13 -0
  81. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  82. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  83. package/src/cli/cmd/tui/context/keybind.tsx +101 -0
  84. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  85. package/src/cli/cmd/tui/context/local.tsx +354 -0
  86. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  87. package/src/cli/cmd/tui/context/route.tsx +46 -0
  88. package/src/cli/cmd/tui/context/sdk.tsx +74 -0
  89. package/src/cli/cmd/tui/context/sync.tsx +372 -0
  90. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  91. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  92. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  93. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  94. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  95. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  96. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  97. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  98. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  99. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  100. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  101. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  102. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  103. package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
  104. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  105. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  106. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  107. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  108. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  109. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  110. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  111. package/src/cli/cmd/tui/context/theme/orng.json +245 -0
  112. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  113. package/src/cli/cmd/tui/context/theme/rird.json +245 -0
  114. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  115. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  116. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  117. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  118. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  119. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  120. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  121. package/src/cli/cmd/tui/context/theme.tsx +1109 -0
  122. package/src/cli/cmd/tui/event.ts +40 -0
  123. package/src/cli/cmd/tui/routes/home.tsx +138 -0
  124. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  125. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  126. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  127. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  128. package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
  129. package/src/cli/cmd/tui/routes/session/header.tsx +125 -0
  130. package/src/cli/cmd/tui/routes/session/index.tsx +1864 -0
  131. package/src/cli/cmd/tui/routes/session/sidebar.tsx +318 -0
  132. package/src/cli/cmd/tui/spawn.ts +60 -0
  133. package/src/cli/cmd/tui/thread.ts +142 -0
  134. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  135. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  136. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  137. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  138. package/src/cli/cmd/tui/ui/dialog-select.tsx +332 -0
  139. package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
  140. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  141. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  142. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  143. package/src/cli/cmd/tui/util/editor.ts +32 -0
  144. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  145. package/src/cli/cmd/tui/worker.ts +63 -0
  146. package/src/cli/cmd/uninstall.ts +344 -0
  147. package/src/cli/cmd/upgrade.ts +100 -0
  148. package/src/cli/cmd/web.ts +84 -0
  149. package/src/cli/error.ts +56 -0
  150. package/src/cli/ui.ts +84 -0
  151. package/src/cli/upgrade.ts +25 -0
  152. package/src/command/index.ts +80 -0
  153. package/src/command/template/initialize.txt +10 -0
  154. package/src/command/template/review.txt +97 -0
  155. package/src/config/config.ts +995 -0
  156. package/src/config/markdown.ts +41 -0
  157. package/src/env/index.ts +26 -0
  158. package/src/file/ignore.ts +83 -0
  159. package/src/file/index.ts +328 -0
  160. package/src/file/ripgrep.ts +393 -0
  161. package/src/file/time.ts +64 -0
  162. package/src/file/watcher.ts +103 -0
  163. package/src/flag/flag.ts +46 -0
  164. package/src/format/formatter.ts +315 -0
  165. package/src/format/index.ts +137 -0
  166. package/src/global/index.ts +52 -0
  167. package/src/id/id.ts +73 -0
  168. package/src/ide/index.ts +76 -0
  169. package/src/index.ts +240 -0
  170. package/src/installation/index.ts +239 -0
  171. package/src/lsp/client.ts +229 -0
  172. package/src/lsp/index.ts +485 -0
  173. package/src/lsp/language.ts +116 -0
  174. package/src/lsp/server.ts +1895 -0
  175. package/src/mcp/auth.ts +135 -0
  176. package/src/mcp/index.ts +690 -0
  177. package/src/mcp/oauth-callback.ts +200 -0
  178. package/src/mcp/oauth-provider.ts +154 -0
  179. package/src/patch/index.ts +622 -0
  180. package/src/permission/index.ts +199 -0
  181. package/src/plugin/index.ts +91 -0
  182. package/src/project/bootstrap.ts +31 -0
  183. package/src/project/instance.ts +78 -0
  184. package/src/project/project.ts +221 -0
  185. package/src/project/state.ts +65 -0
  186. package/src/project/vcs.ts +76 -0
  187. package/src/provider/auth.ts +143 -0
  188. package/src/provider/models-macro.ts +11 -0
  189. package/src/provider/models.ts +106 -0
  190. package/src/provider/provider.ts +1071 -0
  191. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  192. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  193. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  194. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  195. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  196. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  197. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  198. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  199. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  200. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  201. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  202. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  203. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  204. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  205. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  206. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  207. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  208. package/src/provider/transform.ts +455 -0
  209. package/src/pty/index.ts +231 -0
  210. package/src/security/guardrails.test.ts +341 -0
  211. package/src/security/guardrails.ts +558 -0
  212. package/src/security/index.ts +19 -0
  213. package/src/server/error.ts +36 -0
  214. package/src/server/project.ts +79 -0
  215. package/src/server/server.ts +2642 -0
  216. package/src/server/tui.ts +71 -0
  217. package/src/session/compaction.ts +223 -0
  218. package/src/session/index.ts +461 -0
  219. package/src/session/llm.ts +201 -0
  220. package/src/session/message-v2.ts +690 -0
  221. package/src/session/message.ts +189 -0
  222. package/src/session/processor.ts +409 -0
  223. package/src/session/prompt/act-switch.txt +5 -0
  224. package/src/session/prompt/anthropic-20250930.txt +166 -0
  225. package/src/session/prompt/anthropic.txt +85 -0
  226. package/src/session/prompt/anthropic_spoof.txt +1 -0
  227. package/src/session/prompt/beast.txt +103 -0
  228. package/src/session/prompt/codex.txt +304 -0
  229. package/src/session/prompt/copilot-gpt-5.txt +138 -0
  230. package/src/session/prompt/gemini.txt +85 -0
  231. package/src/session/prompt/max-steps.txt +16 -0
  232. package/src/session/prompt/plan-reminder-anthropic.txt +35 -0
  233. package/src/session/prompt/plan.txt +24 -0
  234. package/src/session/prompt/polaris.txt +84 -0
  235. package/src/session/prompt/qwen.txt +106 -0
  236. package/src/session/prompt.ts +1509 -0
  237. package/src/session/retry.ts +86 -0
  238. package/src/session/revert.ts +108 -0
  239. package/src/session/sensitive-filter.test.ts +327 -0
  240. package/src/session/sensitive-filter.ts +466 -0
  241. package/src/session/status.ts +76 -0
  242. package/src/session/summary.ts +194 -0
  243. package/src/session/system.ts +120 -0
  244. package/src/session/todo.ts +37 -0
  245. package/src/share/share-next.ts +194 -0
  246. package/src/share/share.ts +87 -0
  247. package/src/shell/shell.ts +67 -0
  248. package/src/skill/index.ts +1 -0
  249. package/src/skill/skill.ts +83 -0
  250. package/src/snapshot/index.ts +197 -0
  251. package/src/storage/storage.ts +226 -0
  252. package/src/tests/agent.test.ts +308 -0
  253. package/src/tests/build-guards.test.ts +267 -0
  254. package/src/tests/config.test.ts +664 -0
  255. package/src/tests/tool-registry.test.ts +589 -0
  256. package/src/tool/bash.ts +317 -0
  257. package/src/tool/bash.txt +158 -0
  258. package/src/tool/batch.ts +175 -0
  259. package/src/tool/batch.txt +24 -0
  260. package/src/tool/codesearch.ts +168 -0
  261. package/src/tool/codesearch.txt +12 -0
  262. package/src/tool/edit.ts +675 -0
  263. package/src/tool/edit.txt +10 -0
  264. package/src/tool/glob.ts +65 -0
  265. package/src/tool/glob.txt +6 -0
  266. package/src/tool/grep.ts +121 -0
  267. package/src/tool/grep.txt +8 -0
  268. package/src/tool/invalid.ts +17 -0
  269. package/src/tool/ls.ts +110 -0
  270. package/src/tool/ls.txt +1 -0
  271. package/src/tool/lsp-diagnostics.ts +26 -0
  272. package/src/tool/lsp-diagnostics.txt +1 -0
  273. package/src/tool/lsp-hover.ts +31 -0
  274. package/src/tool/lsp-hover.txt +1 -0
  275. package/src/tool/lsp.ts +87 -0
  276. package/src/tool/lsp.txt +19 -0
  277. package/src/tool/multiedit.ts +46 -0
  278. package/src/tool/multiedit.txt +41 -0
  279. package/src/tool/patch.ts +233 -0
  280. package/src/tool/patch.txt +1 -0
  281. package/src/tool/read.ts +219 -0
  282. package/src/tool/read.txt +12 -0
  283. package/src/tool/registry.ts +162 -0
  284. package/src/tool/skill.ts +100 -0
  285. package/src/tool/task.ts +136 -0
  286. package/src/tool/task.txt +51 -0
  287. package/src/tool/todo.ts +39 -0
  288. package/src/tool/todoread.txt +14 -0
  289. package/src/tool/todowrite.txt +167 -0
  290. package/src/tool/tool.ts +71 -0
  291. package/src/tool/webfetch.ts +198 -0
  292. package/src/tool/webfetch.txt +13 -0
  293. package/src/tool/websearch.ts +180 -0
  294. package/src/tool/websearch.txt +11 -0
  295. package/src/tool/write.ts +110 -0
  296. package/src/tool/write.txt +8 -0
  297. package/src/util/archive.ts +16 -0
  298. package/src/util/color.ts +19 -0
  299. package/src/util/context.ts +25 -0
  300. package/src/util/defer.ts +12 -0
  301. package/src/util/eventloop.ts +20 -0
  302. package/src/util/filesystem.ts +83 -0
  303. package/src/util/fn.ts +11 -0
  304. package/src/util/iife.ts +3 -0
  305. package/src/util/keybind.ts +102 -0
  306. package/src/util/lazy.ts +11 -0
  307. package/src/util/license.ts +325 -0
  308. package/src/util/locale.ts +81 -0
  309. package/src/util/lock.ts +98 -0
  310. package/src/util/log.ts +180 -0
  311. package/src/util/queue.ts +32 -0
  312. package/src/util/rpc.ts +42 -0
  313. package/src/util/scrap.ts +10 -0
  314. package/src/util/signal.ts +12 -0
  315. package/src/util/timeout.ts +14 -0
  316. package/src/util/token.ts +7 -0
  317. package/src/util/wildcard.ts +54 -0
  318. package/sst-env.d.ts +9 -0
  319. package/test/agent/agent.test.ts +146 -0
  320. package/test/bun.test.ts +53 -0
  321. package/test/cli/github-remote.test.ts +80 -0
  322. package/test/config/agent-color.test.ts +66 -0
  323. package/test/config/config.test.ts +535 -0
  324. package/test/config/markdown.test.ts +89 -0
  325. package/test/file/ignore.test.ts +10 -0
  326. package/test/fixture/fixture.ts +36 -0
  327. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  328. package/test/ide/ide.test.ts +82 -0
  329. package/test/keybind.test.ts +421 -0
  330. package/test/lsp/client.test.ts +95 -0
  331. package/test/mcp/headers.test.ts +153 -0
  332. package/test/patch/patch.test.ts +348 -0
  333. package/test/preload.ts +57 -0
  334. package/test/project/project.test.ts +72 -0
  335. package/test/provider/provider.test.ts +1809 -0
  336. package/test/provider/transform.test.ts +411 -0
  337. package/test/session/retry.test.ts +111 -0
  338. package/test/session/session.test.ts +71 -0
  339. package/test/skill/skill.test.ts +131 -0
  340. package/test/snapshot/snapshot.test.ts +939 -0
  341. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  342. package/test/tool/bash.test.ts +434 -0
  343. package/test/tool/grep.test.ts +108 -0
  344. package/test/tool/patch.test.ts +259 -0
  345. package/test/tool/read.test.ts +42 -0
  346. package/test/util/iife.test.ts +36 -0
  347. package/test/util/lazy.test.ts +50 -0
  348. package/test/util/timeout.test.ts +21 -0
  349. package/test/util/wildcard.test.ts +55 -0
  350. package/tsconfig.json +16 -0
@@ -0,0 +1,1063 @@
1
+ import {
2
+ RequestError,
3
+ type Agent as ACPAgent,
4
+ type AgentSideConnection,
5
+ type AuthenticateRequest,
6
+ type AuthMethod,
7
+ type CancelNotification,
8
+ type InitializeRequest,
9
+ type InitializeResponse,
10
+ type LoadSessionRequest,
11
+ type NewSessionRequest,
12
+ type PermissionOption,
13
+ type PlanEntry,
14
+ type PromptRequest,
15
+ type SetSessionModelRequest,
16
+ type SetSessionModeRequest,
17
+ type SetSessionModeResponse,
18
+ type ToolCallContent,
19
+ type ToolKind,
20
+ } from "@agentclientprotocol/sdk"
21
+ import { Log } from "../util/log"
22
+ import { ACPSessionManager } from "./session"
23
+ import type { ACPConfig, ACPSessionState } from "./types"
24
+ import { Provider } from "../provider/provider"
25
+ import { Agent as AgentModule } from "../agent/agent"
26
+ import { Installation } from "@/installation"
27
+ import { MessageV2 } from "@/session/message-v2"
28
+ import { Config } from "@/config/config"
29
+ import { Todo } from "@/session/todo"
30
+ import { z } from "zod"
31
+ import { LoadAPIKeyError } from "ai"
32
+ import type { OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
33
+
34
+ export namespace ACP {
35
+ const log = Log.create({ service: "acp-agent" })
36
+
37
+ // Map ALL model IDs to RIRD-branded display names
38
+ function getRirdModelName(modelId: string): string {
39
+ const lower = modelId.toLowerCase()
40
+ if (lower.includes("vision") || lower.includes("-vl") || lower.includes("image")) {
41
+ return "RIRD Vision"
42
+ }
43
+ if (lower.includes("-mini") || lower.includes("nano") || lower.includes("small") || lower.includes("fast") || lower.includes("flash")) {
44
+ return "RIRD Act"
45
+ }
46
+ return "RIRD Brain"
47
+ }
48
+
49
+ export async function init({ sdk }: { sdk: OpencodeClient }) {
50
+ const model = await defaultModel({ sdk })
51
+ return {
52
+ create: (connection: AgentSideConnection, fullConfig: ACPConfig) => {
53
+ if (!fullConfig.defaultModel) {
54
+ fullConfig.defaultModel = model
55
+ }
56
+ return new Agent(connection, fullConfig)
57
+ },
58
+ }
59
+ }
60
+
61
+ export class Agent implements ACPAgent {
62
+ private connection: AgentSideConnection
63
+ private config: ACPConfig
64
+ private sdk: OpencodeClient
65
+ private sessionManager
66
+
67
+ constructor(connection: AgentSideConnection, config: ACPConfig) {
68
+ this.connection = connection
69
+ this.config = config
70
+ this.sdk = config.sdk
71
+ this.sessionManager = new ACPSessionManager(this.sdk)
72
+ }
73
+
74
+ private setupEventSubscriptions(session: ACPSessionState) {
75
+ const sessionId = session.id
76
+ const directory = session.cwd
77
+
78
+ const options: PermissionOption[] = [
79
+ { optionId: "once", kind: "allow_once", name: "Allow once" },
80
+ { optionId: "always", kind: "allow_always", name: "Always allow" },
81
+ { optionId: "reject", kind: "reject_once", name: "Reject" },
82
+ ]
83
+ this.config.sdk.event.subscribe({ directory }).then(async (events) => {
84
+ for await (const event of events.stream) {
85
+ switch (event.type) {
86
+ case "permission.updated":
87
+ try {
88
+ const permission = event.properties
89
+ const res = await this.connection
90
+ .requestPermission({
91
+ sessionId,
92
+ toolCall: {
93
+ toolCallId: permission.callID ?? permission.id,
94
+ status: "pending",
95
+ title: permission.title,
96
+ rawInput: permission.metadata,
97
+ kind: toToolKind(permission.type),
98
+ locations: toLocations(permission.type, permission.metadata),
99
+ },
100
+ options,
101
+ })
102
+ .catch(async (error) => {
103
+ log.error("failed to request permission from ACP", {
104
+ error,
105
+ permissionID: permission.id,
106
+ sessionID: permission.sessionID,
107
+ })
108
+ await this.config.sdk.permission.respond({
109
+ sessionID: permission.sessionID,
110
+ permissionID: permission.id,
111
+ response: "reject",
112
+ directory,
113
+ })
114
+ return
115
+ })
116
+ if (!res) return
117
+ if (res.outcome.outcome !== "selected") {
118
+ await this.config.sdk.permission.respond({
119
+ sessionID: permission.sessionID,
120
+ permissionID: permission.id,
121
+ response: "reject",
122
+ directory,
123
+ })
124
+ return
125
+ }
126
+ await this.config.sdk.permission.respond({
127
+ sessionID: permission.sessionID,
128
+ permissionID: permission.id,
129
+ response: res.outcome.optionId as "once" | "always" | "reject",
130
+ directory,
131
+ })
132
+ } catch (err) {
133
+ log.error("unexpected error when handling permission", { error: err })
134
+ } finally {
135
+ break
136
+ }
137
+
138
+ case "message.part.updated":
139
+ log.info("message part updated", { event: event.properties })
140
+ try {
141
+ const props = event.properties
142
+ const { part } = props
143
+
144
+ const message = await this.config.sdk.session
145
+ .message(
146
+ {
147
+ sessionID: part.sessionID,
148
+ messageID: part.messageID,
149
+ directory,
150
+ },
151
+ { throwOnError: true },
152
+ )
153
+ .then((x) => x.data)
154
+ .catch((err) => {
155
+ log.error("unexpected error when fetching message", { error: err })
156
+ return undefined
157
+ })
158
+
159
+ if (!message || message.info.role !== "assistant") return
160
+
161
+ if (part.type === "tool") {
162
+ switch (part.state.status) {
163
+ case "pending":
164
+ await this.connection
165
+ .sessionUpdate({
166
+ sessionId,
167
+ update: {
168
+ sessionUpdate: "tool_call",
169
+ toolCallId: part.callID,
170
+ title: part.tool,
171
+ kind: toToolKind(part.tool),
172
+ status: "pending",
173
+ locations: [],
174
+ rawInput: {},
175
+ },
176
+ })
177
+ .catch((err) => {
178
+ log.error("failed to send tool pending to ACP", { error: err })
179
+ })
180
+ break
181
+ case "running":
182
+ await this.connection
183
+ .sessionUpdate({
184
+ sessionId,
185
+ update: {
186
+ sessionUpdate: "tool_call_update",
187
+ toolCallId: part.callID,
188
+ status: "in_progress",
189
+ locations: toLocations(part.tool, part.state.input),
190
+ rawInput: part.state.input,
191
+ },
192
+ })
193
+ .catch((err) => {
194
+ log.error("failed to send tool in_progress to ACP", { error: err })
195
+ })
196
+ break
197
+ case "completed":
198
+ const kind = toToolKind(part.tool)
199
+ const content: ToolCallContent[] = [
200
+ {
201
+ type: "content",
202
+ content: {
203
+ type: "text",
204
+ text: part.state.output,
205
+ },
206
+ },
207
+ ]
208
+
209
+ if (kind === "edit") {
210
+ const input = part.state.input
211
+ const filePath = typeof input["filePath"] === "string" ? input["filePath"] : ""
212
+ const oldText = typeof input["oldString"] === "string" ? input["oldString"] : ""
213
+ const newText =
214
+ typeof input["newString"] === "string"
215
+ ? input["newString"]
216
+ : typeof input["content"] === "string"
217
+ ? input["content"]
218
+ : ""
219
+ content.push({
220
+ type: "diff",
221
+ path: filePath,
222
+ oldText,
223
+ newText,
224
+ })
225
+ }
226
+
227
+ if (part.tool === "todowrite") {
228
+ const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output))
229
+ if (parsedTodos.success) {
230
+ await this.connection
231
+ .sessionUpdate({
232
+ sessionId,
233
+ update: {
234
+ sessionUpdate: "plan",
235
+ entries: parsedTodos.data.map((todo) => {
236
+ const status: PlanEntry["status"] =
237
+ todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
238
+ return {
239
+ priority: "medium",
240
+ status,
241
+ content: todo.content,
242
+ }
243
+ }),
244
+ },
245
+ })
246
+ .catch((err) => {
247
+ log.error("failed to send session update for todo", { error: err })
248
+ })
249
+ } else {
250
+ log.error("failed to parse todo output", { error: parsedTodos.error })
251
+ }
252
+ }
253
+
254
+ await this.connection
255
+ .sessionUpdate({
256
+ sessionId,
257
+ update: {
258
+ sessionUpdate: "tool_call_update",
259
+ toolCallId: part.callID,
260
+ status: "completed",
261
+ kind,
262
+ content,
263
+ title: part.state.title,
264
+ rawOutput: {
265
+ output: part.state.output,
266
+ metadata: part.state.metadata,
267
+ },
268
+ },
269
+ })
270
+ .catch((err) => {
271
+ log.error("failed to send tool completed to ACP", { error: err })
272
+ })
273
+ break
274
+ case "error":
275
+ await this.connection
276
+ .sessionUpdate({
277
+ sessionId,
278
+ update: {
279
+ sessionUpdate: "tool_call_update",
280
+ toolCallId: part.callID,
281
+ status: "failed",
282
+ content: [
283
+ {
284
+ type: "content",
285
+ content: {
286
+ type: "text",
287
+ text: part.state.error,
288
+ },
289
+ },
290
+ ],
291
+ rawOutput: {
292
+ error: part.state.error,
293
+ },
294
+ },
295
+ })
296
+ .catch((err) => {
297
+ log.error("failed to send tool error to ACP", { error: err })
298
+ })
299
+ break
300
+ }
301
+ } else if (part.type === "text") {
302
+ const delta = props.delta
303
+ if (delta && part.synthetic !== true) {
304
+ await this.connection
305
+ .sessionUpdate({
306
+ sessionId,
307
+ update: {
308
+ sessionUpdate: "agent_message_chunk",
309
+ content: {
310
+ type: "text",
311
+ text: delta,
312
+ },
313
+ },
314
+ })
315
+ .catch((err) => {
316
+ log.error("failed to send text to ACP", { error: err })
317
+ })
318
+ }
319
+ } else if (part.type === "reasoning") {
320
+ const delta = props.delta
321
+ if (delta) {
322
+ await this.connection
323
+ .sessionUpdate({
324
+ sessionId,
325
+ update: {
326
+ sessionUpdate: "agent_thought_chunk",
327
+ content: {
328
+ type: "text",
329
+ text: delta,
330
+ },
331
+ },
332
+ })
333
+ .catch((err) => {
334
+ log.error("failed to send reasoning to ACP", { error: err })
335
+ })
336
+ }
337
+ }
338
+ } finally {
339
+ break
340
+ }
341
+ }
342
+ }
343
+ })
344
+ }
345
+
346
+ async initialize(params: InitializeRequest): Promise<InitializeResponse> {
347
+ log.info("initialize", { protocolVersion: params.protocolVersion })
348
+
349
+ const authMethod: AuthMethod = {
350
+ description: "Run `rird auth login` in the terminal",
351
+ name: "Login with RIRD",
352
+ id: "rird-login",
353
+ }
354
+
355
+ // If client supports terminal-auth capability, use that instead.
356
+ if (params.clientCapabilities?._meta?.["terminal-auth"] === true) {
357
+ authMethod._meta = {
358
+ "terminal-auth": {
359
+ command: "rird",
360
+ args: ["auth", "login"],
361
+ label: "RIRD Login",
362
+ },
363
+ }
364
+ }
365
+
366
+ return {
367
+ protocolVersion: 1,
368
+ agentCapabilities: {
369
+ loadSession: true,
370
+ mcpCapabilities: {
371
+ http: true,
372
+ sse: true,
373
+ },
374
+ promptCapabilities: {
375
+ embeddedContext: true,
376
+ image: true,
377
+ },
378
+ },
379
+ authMethods: [authMethod],
380
+ agentInfo: {
381
+ name: "RIRD",
382
+ version: Installation.VERSION,
383
+ },
384
+ }
385
+ }
386
+
387
+ async authenticate(_params: AuthenticateRequest) {
388
+ throw new Error("Authentication not implemented")
389
+ }
390
+
391
+ async newSession(params: NewSessionRequest) {
392
+ const directory = params.cwd
393
+ try {
394
+ const model = await defaultModel(this.config, directory)
395
+
396
+ // Store ACP session state
397
+ const state = await this.sessionManager.create(params.cwd, params.mcpServers, model)
398
+ const sessionId = state.id
399
+
400
+ log.info("creating_session", { sessionId, mcpServers: params.mcpServers.length })
401
+
402
+ const load = await this.loadSessionMode({
403
+ cwd: directory,
404
+ mcpServers: params.mcpServers,
405
+ sessionId,
406
+ })
407
+
408
+ this.setupEventSubscriptions(state)
409
+
410
+ return {
411
+ sessionId,
412
+ models: load.models,
413
+ modes: load.modes,
414
+ _meta: {},
415
+ }
416
+ } catch (e) {
417
+ const error = MessageV2.fromError(e, {
418
+ providerID: this.config.defaultModel?.providerID ?? "unknown",
419
+ })
420
+ if (LoadAPIKeyError.isInstance(error)) {
421
+ throw RequestError.authRequired()
422
+ }
423
+ throw e
424
+ }
425
+ }
426
+
427
+ async loadSession(params: LoadSessionRequest) {
428
+ const directory = params.cwd
429
+ const sessionId = params.sessionId
430
+
431
+ try {
432
+ const model = await defaultModel(this.config, directory)
433
+
434
+ // Store ACP session state
435
+ const state = await this.sessionManager.load(sessionId, params.cwd, params.mcpServers, model)
436
+
437
+ log.info("load_session", { sessionId, mcpServers: params.mcpServers.length })
438
+
439
+ const mode = await this.loadSessionMode({
440
+ cwd: directory,
441
+ mcpServers: params.mcpServers,
442
+ sessionId,
443
+ })
444
+
445
+ this.setupEventSubscriptions(state)
446
+
447
+ // Replay session history
448
+ const messages = await this.sdk.session
449
+ .messages(
450
+ {
451
+ sessionID: sessionId,
452
+ directory,
453
+ },
454
+ { throwOnError: true },
455
+ )
456
+ .then((x) => x.data)
457
+ .catch((err) => {
458
+ log.error("unexpected error when fetching message", { error: err })
459
+ return undefined
460
+ })
461
+
462
+ for (const msg of messages ?? []) {
463
+ log.debug("replay message", msg)
464
+ await this.processMessage(msg)
465
+ }
466
+
467
+ return mode
468
+ } catch (e) {
469
+ const error = MessageV2.fromError(e, {
470
+ providerID: this.config.defaultModel?.providerID ?? "unknown",
471
+ })
472
+ if (LoadAPIKeyError.isInstance(error)) {
473
+ throw RequestError.authRequired()
474
+ }
475
+ throw e
476
+ }
477
+ }
478
+
479
+ private async processMessage(message: SessionMessageResponse) {
480
+ log.debug("process message", message)
481
+ if (message.info.role !== "assistant" && message.info.role !== "user") return
482
+ const sessionId = message.info.sessionID
483
+
484
+ for (const part of message.parts) {
485
+ if (part.type === "tool") {
486
+ switch (part.state.status) {
487
+ case "pending":
488
+ await this.connection
489
+ .sessionUpdate({
490
+ sessionId,
491
+ update: {
492
+ sessionUpdate: "tool_call",
493
+ toolCallId: part.callID,
494
+ title: part.tool,
495
+ kind: toToolKind(part.tool),
496
+ status: "pending",
497
+ locations: [],
498
+ rawInput: {},
499
+ },
500
+ })
501
+ .catch((err) => {
502
+ log.error("failed to send tool pending to ACP", { error: err })
503
+ })
504
+ break
505
+ case "running":
506
+ await this.connection
507
+ .sessionUpdate({
508
+ sessionId,
509
+ update: {
510
+ sessionUpdate: "tool_call_update",
511
+ toolCallId: part.callID,
512
+ status: "in_progress",
513
+ locations: toLocations(part.tool, part.state.input),
514
+ rawInput: part.state.input,
515
+ },
516
+ })
517
+ .catch((err) => {
518
+ log.error("failed to send tool in_progress to ACP", { error: err })
519
+ })
520
+ break
521
+ case "completed":
522
+ const kind = toToolKind(part.tool)
523
+ const content: ToolCallContent[] = [
524
+ {
525
+ type: "content",
526
+ content: {
527
+ type: "text",
528
+ text: part.state.output,
529
+ },
530
+ },
531
+ ]
532
+
533
+ if (kind === "edit") {
534
+ const input = part.state.input
535
+ const filePath = typeof input["filePath"] === "string" ? input["filePath"] : ""
536
+ const oldText = typeof input["oldString"] === "string" ? input["oldString"] : ""
537
+ const newText =
538
+ typeof input["newString"] === "string"
539
+ ? input["newString"]
540
+ : typeof input["content"] === "string"
541
+ ? input["content"]
542
+ : ""
543
+ content.push({
544
+ type: "diff",
545
+ path: filePath,
546
+ oldText,
547
+ newText,
548
+ })
549
+ }
550
+
551
+ if (part.tool === "todowrite") {
552
+ const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output))
553
+ if (parsedTodos.success) {
554
+ await this.connection
555
+ .sessionUpdate({
556
+ sessionId,
557
+ update: {
558
+ sessionUpdate: "plan",
559
+ entries: parsedTodos.data.map((todo) => {
560
+ const status: PlanEntry["status"] =
561
+ todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
562
+ return {
563
+ priority: "medium",
564
+ status,
565
+ content: todo.content,
566
+ }
567
+ }),
568
+ },
569
+ })
570
+ .catch((err) => {
571
+ log.error("failed to send session update for todo", { error: err })
572
+ })
573
+ } else {
574
+ log.error("failed to parse todo output", { error: parsedTodos.error })
575
+ }
576
+ }
577
+
578
+ await this.connection
579
+ .sessionUpdate({
580
+ sessionId,
581
+ update: {
582
+ sessionUpdate: "tool_call_update",
583
+ toolCallId: part.callID,
584
+ status: "completed",
585
+ kind,
586
+ content,
587
+ title: part.state.title,
588
+ rawOutput: {
589
+ output: part.state.output,
590
+ metadata: part.state.metadata,
591
+ },
592
+ },
593
+ })
594
+ .catch((err) => {
595
+ log.error("failed to send tool completed to ACP", { error: err })
596
+ })
597
+ break
598
+ case "error":
599
+ await this.connection
600
+ .sessionUpdate({
601
+ sessionId,
602
+ update: {
603
+ sessionUpdate: "tool_call_update",
604
+ toolCallId: part.callID,
605
+ status: "failed",
606
+ content: [
607
+ {
608
+ type: "content",
609
+ content: {
610
+ type: "text",
611
+ text: part.state.error,
612
+ },
613
+ },
614
+ ],
615
+ rawOutput: {
616
+ error: part.state.error,
617
+ },
618
+ },
619
+ })
620
+ .catch((err) => {
621
+ log.error("failed to send tool error to ACP", { error: err })
622
+ })
623
+ break
624
+ }
625
+ } else if (part.type === "text") {
626
+ if (part.text) {
627
+ await this.connection
628
+ .sessionUpdate({
629
+ sessionId,
630
+ update: {
631
+ sessionUpdate: message.info.role === "user" ? "user_message_chunk" : "agent_message_chunk",
632
+ content: {
633
+ type: "text",
634
+ text: part.text,
635
+ },
636
+ },
637
+ })
638
+ .catch((err) => {
639
+ log.error("failed to send text to ACP", { error: err })
640
+ })
641
+ }
642
+ } else if (part.type === "reasoning") {
643
+ if (part.text) {
644
+ await this.connection
645
+ .sessionUpdate({
646
+ sessionId,
647
+ update: {
648
+ sessionUpdate: "agent_thought_chunk",
649
+ content: {
650
+ type: "text",
651
+ text: part.text,
652
+ },
653
+ },
654
+ })
655
+ .catch((err) => {
656
+ log.error("failed to send reasoning to ACP", { error: err })
657
+ })
658
+ }
659
+ }
660
+ }
661
+ }
662
+
663
+ private async loadSessionMode(params: LoadSessionRequest) {
664
+ const directory = params.cwd
665
+ const model = await defaultModel(this.config, directory)
666
+ const sessionId = params.sessionId
667
+
668
+ const providers = await this.sdk.config.providers({ directory }).then((x) => x.data!.providers)
669
+ const entries = providers.sort((a, b) => {
670
+ const nameA = a.name.toLowerCase()
671
+ const nameB = b.name.toLowerCase()
672
+ if (nameA < nameB) return -1
673
+ if (nameA > nameB) return 1
674
+ return 0
675
+ })
676
+ const availableModels = entries.flatMap((provider) => {
677
+ const models = Provider.sort(Object.values(provider.models))
678
+ return models.map((model) => ({
679
+ modelId: `${provider.id}/${model.id}`,
680
+ name: `${provider.name}/${getRirdModelName(model.id)}`,
681
+ }))
682
+ })
683
+
684
+ const agents = await this.config.sdk.app
685
+ .agents(
686
+ {
687
+ directory,
688
+ },
689
+ { throwOnError: true },
690
+ )
691
+ .then((resp) => resp.data!)
692
+
693
+ const commands = await this.config.sdk.command
694
+ .list(
695
+ {
696
+ directory,
697
+ },
698
+ { throwOnError: true },
699
+ )
700
+ .then((resp) => resp.data!)
701
+
702
+ const availableCommands = commands.map((command) => ({
703
+ name: command.name,
704
+ description: command.description ?? "",
705
+ }))
706
+ const names = new Set(availableCommands.map((c) => c.name))
707
+ if (!names.has("compact"))
708
+ availableCommands.push({
709
+ name: "compact",
710
+ description: "compact the session",
711
+ })
712
+
713
+ const availableModes = agents
714
+ .filter((agent) => agent.mode !== "subagent" && !agent.hidden)
715
+ .map((agent) => ({
716
+ id: agent.name,
717
+ name: agent.name,
718
+ description: agent.description,
719
+ }))
720
+
721
+ const defaultAgentName = await AgentModule.defaultAgent()
722
+ const currentModeId = availableModes.find((m) => m.name === defaultAgentName)?.id ?? availableModes[0].id
723
+
724
+ const mcpServers: Record<string, Config.Mcp> = {}
725
+ for (const server of params.mcpServers) {
726
+ if ("type" in server) {
727
+ mcpServers[server.name] = {
728
+ url: server.url,
729
+ headers: server.headers.reduce<Record<string, string>>((acc, { name, value }) => {
730
+ acc[name] = value
731
+ return acc
732
+ }, {}),
733
+ type: "remote",
734
+ }
735
+ } else {
736
+ mcpServers[server.name] = {
737
+ type: "local",
738
+ command: [server.command, ...server.args],
739
+ environment: server.env.reduce<Record<string, string>>((acc, { name, value }) => {
740
+ acc[name] = value
741
+ return acc
742
+ }, {}),
743
+ }
744
+ }
745
+ }
746
+
747
+ await Promise.all(
748
+ Object.entries(mcpServers).map(async ([key, mcp]) => {
749
+ await this.sdk.mcp
750
+ .add(
751
+ {
752
+ directory,
753
+ name: key,
754
+ config: mcp,
755
+ },
756
+ { throwOnError: true },
757
+ )
758
+ .catch((error) => {
759
+ log.error("failed to add mcp server", { name: key, error })
760
+ })
761
+ }),
762
+ )
763
+
764
+ setTimeout(() => {
765
+ this.connection.sessionUpdate({
766
+ sessionId,
767
+ update: {
768
+ sessionUpdate: "available_commands_update",
769
+ availableCommands,
770
+ },
771
+ })
772
+ }, 0)
773
+
774
+ return {
775
+ sessionId,
776
+ models: {
777
+ currentModelId: `${model.providerID}/${model.modelID}`,
778
+ availableModels,
779
+ },
780
+ modes: {
781
+ availableModes,
782
+ currentModeId,
783
+ },
784
+ _meta: {},
785
+ }
786
+ }
787
+
788
+ async setSessionModel(params: SetSessionModelRequest) {
789
+ const session = this.sessionManager.get(params.sessionId)
790
+
791
+ const model = Provider.parseModel(params.modelId)
792
+
793
+ this.sessionManager.setModel(session.id, {
794
+ providerID: model.providerID,
795
+ modelID: model.modelID,
796
+ })
797
+
798
+ return {
799
+ _meta: {},
800
+ }
801
+ }
802
+
803
+ async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse | void> {
804
+ this.sessionManager.get(params.sessionId)
805
+ await this.config.sdk.app
806
+ .agents({}, { throwOnError: true })
807
+ .then((x) => x.data)
808
+ .then((agent) => {
809
+ if (!agent) throw new Error(`Agent not found: ${params.modeId}`)
810
+ })
811
+ this.sessionManager.setMode(params.sessionId, params.modeId)
812
+ }
813
+
814
+ async prompt(params: PromptRequest) {
815
+ const sessionID = params.sessionId
816
+ const session = this.sessionManager.get(sessionID)
817
+ const directory = session.cwd
818
+
819
+ const current = session.model
820
+ const model = current ?? (await defaultModel(this.config, directory))
821
+ if (!current) {
822
+ this.sessionManager.setModel(session.id, model)
823
+ }
824
+ const agent = session.modeId ?? (await AgentModule.defaultAgent())
825
+
826
+ const parts: Array<
827
+ { type: "text"; text: string } | { type: "file"; url: string; filename: string; mime: string }
828
+ > = []
829
+ for (const part of params.prompt) {
830
+ switch (part.type) {
831
+ case "text":
832
+ parts.push({
833
+ type: "text" as const,
834
+ text: part.text,
835
+ })
836
+ break
837
+ case "image":
838
+ if (part.data) {
839
+ parts.push({
840
+ type: "file",
841
+ url: `data:${part.mimeType};base64,${part.data}`,
842
+ filename: "image",
843
+ mime: part.mimeType,
844
+ })
845
+ } else if (part.uri && part.uri.startsWith("http:")) {
846
+ parts.push({
847
+ type: "file",
848
+ url: part.uri,
849
+ filename: "image",
850
+ mime: part.mimeType,
851
+ })
852
+ }
853
+ break
854
+
855
+ case "resource_link":
856
+ const parsed = parseUri(part.uri)
857
+ parts.push(parsed)
858
+
859
+ break
860
+
861
+ case "resource":
862
+ const resource = part.resource
863
+ if ("text" in resource) {
864
+ parts.push({
865
+ type: "text",
866
+ text: resource.text,
867
+ })
868
+ }
869
+ break
870
+
871
+ default:
872
+ break
873
+ }
874
+ }
875
+
876
+ log.info("parts", { parts })
877
+
878
+ const cmd = (() => {
879
+ const text = parts
880
+ .filter((p): p is { type: "text"; text: string } => p.type === "text")
881
+ .map((p) => p.text)
882
+ .join("")
883
+ .trim()
884
+
885
+ if (!text.startsWith("/")) return
886
+
887
+ const [name, ...rest] = text.slice(1).split(/\s+/)
888
+ return { name, args: rest.join(" ").trim() }
889
+ })()
890
+
891
+ const done = {
892
+ stopReason: "end_turn" as const,
893
+ _meta: {},
894
+ }
895
+
896
+ if (!cmd) {
897
+ await this.sdk.session.prompt({
898
+ sessionID,
899
+ model: {
900
+ providerID: model.providerID,
901
+ modelID: model.modelID,
902
+ },
903
+ parts,
904
+ agent,
905
+ directory,
906
+ })
907
+ return done
908
+ }
909
+
910
+ const command = await this.config.sdk.command
911
+ .list({ directory }, { throwOnError: true })
912
+ .then((x) => x.data!.find((c) => c.name === cmd.name))
913
+ if (command) {
914
+ await this.sdk.session.command({
915
+ sessionID,
916
+ command: command.name,
917
+ arguments: cmd.args,
918
+ model: model.providerID + "/" + model.modelID,
919
+ agent,
920
+ directory,
921
+ })
922
+ return done
923
+ }
924
+
925
+ switch (cmd.name) {
926
+ case "compact":
927
+ await this.config.sdk.session.summarize(
928
+ {
929
+ sessionID,
930
+ directory,
931
+ providerID: model.providerID,
932
+ modelID: model.modelID,
933
+ },
934
+ { throwOnError: true },
935
+ )
936
+ break
937
+ }
938
+
939
+ return done
940
+ }
941
+
942
+ async cancel(params: CancelNotification) {
943
+ const session = this.sessionManager.get(params.sessionId)
944
+ await this.config.sdk.session.abort(
945
+ {
946
+ sessionID: params.sessionId,
947
+ directory: session.cwd,
948
+ },
949
+ { throwOnError: true },
950
+ )
951
+ }
952
+ }
953
+
954
+ function toToolKind(toolName: string): ToolKind {
955
+ const tool = toolName.toLocaleLowerCase()
956
+ switch (tool) {
957
+ case "bash":
958
+ return "execute"
959
+ case "webfetch":
960
+ return "fetch"
961
+
962
+ case "edit":
963
+ case "patch":
964
+ case "write":
965
+ return "edit"
966
+
967
+ case "grep":
968
+ case "glob":
969
+ case "context7_resolve_library_id":
970
+ case "context7_get_library_docs":
971
+ return "search"
972
+
973
+ case "list":
974
+ case "read":
975
+ return "read"
976
+
977
+ default:
978
+ return "other"
979
+ }
980
+ }
981
+
982
+ function toLocations(toolName: string, input: Record<string, any>): { path: string }[] {
983
+ const tool = toolName.toLocaleLowerCase()
984
+ switch (tool) {
985
+ case "read":
986
+ case "edit":
987
+ case "write":
988
+ return input["filePath"] ? [{ path: input["filePath"] }] : []
989
+ case "glob":
990
+ case "grep":
991
+ return input["path"] ? [{ path: input["path"] }] : []
992
+ case "bash":
993
+ return []
994
+ case "list":
995
+ return input["path"] ? [{ path: input["path"] }] : []
996
+ default:
997
+ return []
998
+ }
999
+ }
1000
+
1001
+ async function defaultModel(config: ACPConfig, cwd?: string) {
1002
+ const sdk = config.sdk
1003
+ const configured = config.defaultModel
1004
+ if (configured) return configured
1005
+
1006
+ const model = await sdk.config
1007
+ .get({ directory: cwd }, { throwOnError: true })
1008
+ .then((resp) => {
1009
+ const cfg = resp.data
1010
+ if (!cfg || !cfg.model) return undefined
1011
+ const parsed = Provider.parseModel(cfg.model)
1012
+ return {
1013
+ providerID: parsed.providerID,
1014
+ modelID: parsed.modelID,
1015
+ }
1016
+ })
1017
+ .catch((error) => {
1018
+ log.error("failed to load user config for default model", { error })
1019
+ return undefined
1020
+ })
1021
+
1022
+ return model ?? { providerID: "rird", modelID: "rird-brain" }
1023
+ }
1024
+
1025
+ function parseUri(
1026
+ uri: string,
1027
+ ): { type: "file"; url: string; filename: string; mime: string } | { type: "text"; text: string } {
1028
+ try {
1029
+ if (uri.startsWith("file://")) {
1030
+ const path = uri.slice(7)
1031
+ const name = path.split("/").pop() || path
1032
+ return {
1033
+ type: "file",
1034
+ url: uri,
1035
+ filename: name,
1036
+ mime: "text/plain",
1037
+ }
1038
+ }
1039
+ if (uri.startsWith("zed://")) {
1040
+ const url = new URL(uri)
1041
+ const path = url.searchParams.get("path")
1042
+ if (path) {
1043
+ const name = path.split("/").pop() || path
1044
+ return {
1045
+ type: "file",
1046
+ url: `file://${path}`,
1047
+ filename: name,
1048
+ mime: "text/plain",
1049
+ }
1050
+ }
1051
+ }
1052
+ return {
1053
+ type: "text",
1054
+ text: uri,
1055
+ }
1056
+ } catch {
1057
+ return {
1058
+ type: "text",
1059
+ text: uri,
1060
+ }
1061
+ }
1062
+ }
1063
+ }