# ACP、GitHub 与其他入口

> opencode 作为 ACP agent 被编辑器驱动、在 GitHub Actions 里响应评论、用 run 命令脚本化，以及会话分享、Web、桌面端与 VS Code 扩展的接入方式。

- 作者：David（道雾轩）
- 专栏：OpenCode 源码解读（https://daiw.org/manual/opencode.md）
- 最后更新：2026-09-19
- 原文：https://daiw.org/manual/opencode/frontends
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

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](https://agentclientprotocol.com/) 是编辑器与编码 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 连回自己。

```ts
    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](https://daiw.org/manual/kimi-code/klient-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` 服务里：

```ts
    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` 端点——分享页的实时更新，不过是[事件系统](https://daiw.org/manual/opencode/events-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 服务端](https://daiw.org/manual/opencode/server)）。`--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 与守护进程](https://daiw.org/manual/opencode/cli-daemon)）。

## 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](https://daiw.org/manual/opencode/effect-runtime)——第 5 部分到此结束，接下来进入 V2 架构，从 Effect 运行时与 Location 这个核心抽象讲起。
