ACP、GitHub 与其他入口
opencode 作为 ACP agent 被编辑器驱动、在 GitHub Actions 里响应评论、用 run 命令脚本化,以及会话分享、Web、桌面端与 VS Code 扩展的接入方式。
TUI 之外,OpenCode 还有一串入口,它们与内核打交道的方式各不相同:
| 入口 | 命令 | 与内核的连接方式 |
|---|---|---|
| ACP | opencode acp | 起真实 HTTP 服务端,内部用 SDK 访问 |
| GitHub agent | opencode github run | 进程内调用 Effect 服务,不走 HTTP |
| 脚本 | opencode run | 本地走进程内 fetch,--attach 时连远端 |
| Web | opencode web | 起服务端并打开浏览器 |
| 桌面端 | Electron 应用 | 主进程托管一个服务端 |
| VS Code | 扩展 | 终端里运行 opencode --port,经 /tui/* 遥控 |
ACP:让编辑器驱动 opencode
Agent Client Protocol 是编辑器与编码 agent 之间的开放协议:编辑器作为客户端,通过 stdio 与 agent 进程交换 JSON-RPC 消息。agent 实现一次,Zed 等支持 ACP 的编辑器都能直接用。opencode 的实现在 packages/opencode/src/acp/(12 个文件、3537 行)与 packages/opencode/src/cli/cmd/acp.ts。
opencode acp 的做法有点出人意料:先按普通网络参数起一个真正的 HTTP 服务端,再用 SDK 连回自己。
const server = yield* Effect.promise(() => ACPProfile.measure("cli.acp.server.listen", () => Server.listen(opts)))
const sdk = createOpencodeClient({
baseUrl: `http://${server.hostname}:${server.port}`,
headers: ServerAuth.headers(),
})(packages/opencode/src/cli/cmd/acp.ts:25)
随后把标准输入输出包成 ndJsonStream,交给 @agentclientprotocol/sdk 的 AgentSideConnection,由 ACP.init({ sdk }).create(conn) 生成 agent。于是整个 ACP 层只是 SDK 的一个客户端,与 TUI 地位相同。
service.ts(1105 行)实现协议方法。initialize 声明的能力包括 loadSession、HTTP 与 SSE 两种 MCP 传输、嵌入上下文与图片提示词,以及会话的 close、fork、list、resume;认证方式指向终端里的 opencode auth login。主要流程:
newSession:用sdk.session.create在编辑器给定的cwd下建会话,并注册编辑器传来的 MCP 服务器;prompt:普通消息走sdk.session.prompt,外面包一层runUntilIdle等会话空闲;event.ts:订阅sdk.global.event(),把 part 更新翻译成 ACP 的sessionUpdate通知,如agent_message_chunk、agent_thought_chunk、tool_call、tool_call_update;permission.ts:遇到权限询问时调用编辑器的requestPermission,再经sdk.permission.reply交回结果。
对照 Kimi Code 的做法(klient、SDK 与 ACP),那边的 acp-server 在进程内启动引擎、经 klient 内存传输调用,这边只走 HTTP 契约。
GitHub agent
opencode github install 在仓库里生成 .github/workflows/opencode.yml 并引导安装 GitHub App;工作流调用仓库自带的组合 Action(github/action.yml),它安装 opencode 后执行 opencode github run。Action 的输入以 model(必填)为主,另有 agent、share、mentions、use_github_token 等。
支持的事件分两类(packages/opencode/src/cli/cmd/github.handler.ts:150):用户事件 issue_comment、pull_request_review_comment、issues、pull_request,仓库事件 schedule、workflow_dispatch。评论类事件必须包含触发词,默认 /opencode 或 /oc,例如在 issue 下评论 /oc summarize。令牌默认走 OIDC:拿 Actions 的 OIDC 令牌到 api.opencode.ai 换 GitHub App 令牌;开启 use_github_token 则直接用 GITHUB_TOKEN。
github.handler.ts 有 1606 行,按事件分叉:PR 相关事件在 PR 分支上工作;issue 事件新建分支,必要时开 PR;定时与手动触发没有 issue 上下文,用 schedule、dispatch 前缀的分支,结果只写日志或 PR。提示词里拼进 issue 或 PR 的上下文,用 <issue_comments>、<pull_request_changed_files> 等标签分段;评论中 github.com/user-attachments 的图片会被下载,以 data URL 作为文件 part 发送。
与 ACP 不同,GitHub agent 不起服务端:githubRun 直接取出 Session.Service、SessionPrompt.Service 等 Effect 服务,在进程内调用 prompt.prompt(...)。CI 里只有一个一次性的调用方,没必要多一层 HTTP。公开仓库默认分享会话,并把分享链接写进回复评论。
会话分享
分享的开关在 SessionShare 服务里:
const create = Effect.fn("SessionShare.create")(function* (input?: Session.CreateInput) {
const result = yield* session.create(input)
if (result.parentID) return result
const conf = yield* cfg.get()
if (!(flags.autoShare || conf.share === "auto")) return result
yield* share(result.id).pipe(Effect.ignore, Effect.forkIn(scope))
return result
})(packages/opencode/src/share/session.ts:39)
设了环境变量 OPENCODE_AUTO_SHARE(即 autoShare 开关)或配置 share: "auto" 时,新建的顶层会话在后台自动分享,子会话不分享;配置为 share: "disabled" 时手动分享也会报错。
与分享服务通信的是同目录的 share-next.ts:未登录控制台组织账号时,向 https://opncd.ai(或配置里的 enterprise.url)的 /api/share 创建分享;登录了则带 Bearer 令牌走控制台的 /api/shares。拿到的 id、secret、url 存进数据库。之后它监听本目录的 session.updated、message.updated、message.part.updated、session.diff、session.deleted,攒进队列、延迟 1 秒批量推送到该分享的 sync 端点——分享页的实时更新,不过是事件系统的又一个订阅者。设 OPENCODE_DISABLE_SHARE=true 可整体关闭。
opencode run:脚本化入口
opencode run [message..] 发一条提示词,把事件流打印到标准输出,会话空闲即退出。常用参数:
| 参数 | 作用 |
|---|---|
--command | 执行斜杠命令,消息作为参数 |
-c、-s、--fork | 继续最近或指定会话,可先分叉 |
-m、--agent、--variant | 模型、agent 与推理强度 |
-f、--file | 附加文件 |
--format json | 以 JSON 行输出事件 |
--share、--title | 分享会话、设置标题 |
--attach、-p、-u、--dir | 连接远端服务端、认证与远端目录 |
--format json 时每个事件输出一行 JSON,带 type、timestamp、sessionID,类型有 tool_use、step_start、step_finish、text、reasoning、error。本地模式不开端口:SDK 的 baseUrl 是 http://opencode.internal,fetch 直接调用 Server.Default().app.fetch(见HTTP 服务端)。--attach 时换成指向远端的普通客户端,并检查附件:没有共享文件系统时拒绝附加本地目录,单个文件上限 10 MiB。
Web 与桌面端
opencode web 就是 Server.listen() 加打开浏览器;监听 0.0.0.0 时额外列出本机网卡地址,开了 mDNS 再显示 opencode.local 地址。页面由服务端的兜底路由提供:优先用构建时嵌入的静态文件,否则代理 https://app.opencode.ai。
packages/app 是共享的 SolidJS Web 界面(Vite 构建),packages/desktop 用 Electron 把它包成桌面应用。主进程要自己托管后端,按 SIDECAR_VERSION 分两条路:v1 用 Electron 的 utilityProcess 启动一个 sidecar,里面调用打包进来的 Server.listen(),以 Basic 认证保护,并把 oc://renderer 加进 CORS 白名单——服务端白名单里那个奇特的来源就出自这里;v2 则运行随应用分发的新 CLI,先用它的 service status 查找已在运行的后台实例(见新 CLI 与守护进程)。
IDE 扩展
仓库的 sdks/ 目录目前只有 VS Code 扩展(sdks/vscode),实现很轻:在分屏终端里执行 opencode --port <随机端口>,环境变量带上 _EXTENSION_OPENCODE_PORT 与 OPENCODE_CALLER=vscode;轮询 http://localhost:<端口>/app 直到连通,再 POST /tui/append-prompt,把当前文件引用(形如 @File#L37-42)塞进 TUI 的输入框。默认快捷键 Cmd+Esc(Windows 与 Linux 上为 Ctrl+Esc)打开或聚焦 opencode 终端。
扩展没有自己的界面,它借服务端的 /tui/* 端点遥控终端里的 TUI。服务端把界面动作也做成了事件,这种轻量集成才成为可能。
下一篇:Effect 运行时与 Location——第 5 部分到此结束,接下来进入 V2 架构,从 Effect 运行时与 Location 这个核心抽象讲起。