opencode-server-adaptor 是一个兼容 OpenCode Server API 的 Agent 适配服务。它实现了 OpenCode Desktop
所需的 CLI、HTTP REST API 和 SSE 事件流,并将 OpenCode 的会话、消息、工具调用和子智能体操作转换为
后端 Agent 的运行协议。
目前项目接入了 Pi coding agent 和 DeepSeek Harness(DSH)两个后端,两者可以同时安装并共存;核心实现支持继续扩展
其他 Agent 后端。DSH 后端通过官方 @deepseek-ai/dsh-host-apiproxy 包连接一个已经运行的 dsh web 实例(默认
http://127.0.0.1:3080),复用其 typed API、事件协议和 Typert Remote 通道。DSH 连接按需延迟到首次创建或恢复 DSH
会话;未安装或未启动 DSH 不影响服务启动、健康检查以及 Pi 会话。架构设计、接口分层、生命周期、子智能体
方案、重启恢复约定、开发调优参数和测试规范见 AGENTS.md。接口分层细节见
INTERFACE.md。
服务按“协议映射 → 应用服务 → 后端接口 → 后端实现”分层。HTTP 路由只负责 OpenCode 字段、状态码和错误 envelope;
SessionService 统一会话用例,
AgentService 负责编排 Runtime、事件投影和子任务,
AgentAdapter / AgentRuntime 是后端扩展边界。SQLite 访问集中在 session、message 和
durable event 仓库中,路由与 Agent 编排不直接执行 SQL。Pi 的进程、RPC、事件转换和模型同步全部位于
src/agents/pi,DSH 的远程客户端与事件映射位于 src/agents/dsh;新增后端
不应修改 OpenCode 会话主流程。更完整的依赖方向和生命周期约定见
INTERFACE.md。
当前兼容目标为 OpenCode 1.18.7。服务默认启用 v2 协议(接口位于 /api/*),可通过 --api-version=v1
切换为精简的 v1 配置与检查层。两种模式默认都挂载一个兼容层,提供当前OpenCode即使检测到v2 server却仍在使用的v1接口。
v1/v2 的完整接口清单和设计说明见 AGENTS.md。
限制
项目优先兼容 v2 API,因为这是 OpenCode 面向后续长期维护的协议方向。但 OpenCode 的 v2 协议和 Desktop 实现仍在 迁移中,部分功能缺少 v2 支持,另一些功能即使在 v2 模式下仍依赖旧版协议。当前做了以下取舍和兼容:
- v2 模式下仍会提供一组 legacy 兼容功能,以保证 Desktop 的配置读取、会话删除和文件树可以正常工作。可以通过
--disable-v1-compatible关闭这些功能,但关闭后 Desktop 的文件树和部分操作可能不可用。 - legacy 文件功能只覆盖 Desktop 当前实际使用的文件树、文件读取和文件名查找。文件状态、全文搜索和符号搜索不可用; 大文件不能直接读取,文件名查找也限制搜索深度和结果数量,并会忽略常见的隐藏目录、依赖目录和符号链接。
- 部分 Desktop 版本在 v2 模式下无法完成终端连接认证,表现为终端创建后无法连接。可以使用
--disable-pty-token-check兼容这些版本;该参数会削弱终端连接保护,只应在可信的本机环境中使用。 - Desktop 一旦把服务识别为 v2,就不会开放仅支持 v1 的自定义 provider/model 结构编辑。可以直接修改
providers.yaml,或者用--api-version=v1启动服务完成配置;配置完成后必须以 v2 模式重启服务,并重启 OpenCode 客户端,使其重新探测协议。v1 模式只是精简的配置与检查模式,不能用于正常对话和任务执行。v2 模式仍可查看 provider/model,并可为已经存在的 provider 更新 API key。 - provider 认证目前只可靠支持 API key,不支持完整的第三方 OAuth 登录、token 交换和刷新流程。
- Skill 会从 Pi 原生的全局目录
~/.pi/agent/skills和当前工程的.pi/skills加载,也兼容 OpenCode、Agents 和 Claude 的常用 Skill 目录。Skill 会出现在 Desktop slash 菜单中,并交给 Pi 做原生自动发现;适配器自己的隔离 Pi 配置目录不会用于存放或扫描 Skill。 - plugin 和 reference 当前不会显示可用内容;交互式 question 尚未接入 Pi;MCP 配置不会启动真实 MCP server,也不会提供 MCP 资源或模板。
- “始终允许”的权限不会被保存,普通 Pi 工具也不会触发 OpenCode 的权限确认。具体安全边界见下方“工具权限与隔离”。
- 会话内的 Agent 只能在同一后端分组内切换:用 Pi 创建的会话可以切到 Pi-Plan,但不能切到 DeepSeek-Harness;用
DeepSeek-Harness 创建的会话同样不能切回 Pi/Pi-Plan。切换被拒绝时会返回
invalid_request错误并保持原 Agent。 - 不支持远程工作区;所有会话和文件操作都针对服务进程可直接访问的本地或 WSL 文件系统。
- 会话回退只影响模型对话上下文,不恢复工作区文件。已经执行的写文件、编辑和 shell 副作用需要用户自行 通过版本控制或其他方式恢复。
- v2 API 在客户端侧可能按同一 message 内重建后的 part ID 排序,导致重新加载历史消息后,推理、工具调用和文本的
显示顺序与实时过程不同。服务默认优先按连续相同类型拆成多条 assistant response message;如果调用方要求一次请求
只对应一条响应 message,可以使用
--msg-part-encap将本轮多个 part 包裹在同一 message 内,但上述客户端可能出现 part 顺序错乱。
综合上述兼容情况,在日常连接 OpenCode Desktop、且服务仅运行于可信本机环境时,推荐使用:
opencode-server-adaptor serve --disable-pty-token-check这个参数用于保证终端连接兼容;如果服务会暴露给其他主机或不可信用户,不应使用
--disable-pty-token-check。
当前版本不会在 Pi 读取或修改文件、执行命令等操作前请求用户授权。
Pi 及其工具继承启动本服务的操作系统用户权限,本项目也不提供沙箱隔离。plan Agent 的只读工具限制仅用于工作流
约束,不应视为安全隔离。请仅在可信工作区中运行;处理不可信代码、提示或自动化任务时,应使用低权限账号,并在容器、
虚拟机或其他操作系统级沙箱中运行本服务,同时只暴露任务所需的文件和凭据。
- Linux、WSL 或 Windows 10/11 x64。构建脚本会按当前宿主生成 Linux 可执行文件或 Windows
.exe。 - Bun 1.3.0 或更高版本。
- Node.js(Windows 使用 Pi 后端时需要;适配器通过 Node 子进程桥接 Pi 的持久 RPC)。
- Pi coding agent CLI,默认应能通过
~/.bun/bin/pi、~/.bun/bin/pi.exe或PATH找到。只用 DSH 时可不安装。 bash和curl只用于 Linux/WSL 的安装脚本。- OpenCode Desktop,仅在需要从桌面端连接本服务时需要。
在本项目使用的 WSL 环境中,先加载 shell 配置,使 bun 和 pi 可用:
source ~/.bashrc
bun --version
bun "$(readlink -f "$(command -v pi)")" --version这里显式使用 Bun 执行 Pi 的 JavaScript 入口,以避免 WSL 中系统 node 不可用或版本不兼容。适配器自动检测
~/.bun/bin/bun 和 ~/.bun/bin/pi 时也采用相同方式启动 Pi。
如果尚未安装 Pi,可按 Pi 项目的说明安装。使用当前 Pi 包时可以执行:
bun add --global @earendil-works/pi-coding-agent进入项目目录并安装锁定版本的依赖:
bun install --frozen-lockfile执行类型检查并构建当前平台的单文件可执行程序:
bun run typecheck
bun run buildLinux/WSL 默认生成兼容性较好的 bun-linux-x64-baseline,Windows 原生生成 bun-windows-x64-baseline:
dist/opencode-server-adaptor
dist/opencode-server-adaptor.exe
如需同时生成 Linux x64 baseline、x64 modern、arm64 和 Windows x64 产物:
BUILD_ALL_TARGETS=1 bun run build安装已经构建的程序:
./scripts/install.sh默认安装到 ~/.local/bin/opencode-server-adaptor。可以使用 --prefix PATH 指定其他安装目录。
Windows 原生无需运行 install.sh,可直接启动:
.\dist\opencode-server-adaptor.exe serve --hostname 127.0.0.1 --port 4096Windows 使用 Pi 时,先安装 CLI;如果之前只在 WSL 配置过模型,可复制配置后验证目录:
bun add --global @earendil-works/pi-coding-agent
New-Item -ItemType Directory -Force "$HOME\.pi\agent" | Out-Null
$destination = (wsl wslpath -a -u "$HOME\.pi\agent\models.json").Trim()
wsl sh -lc "cp ~/.pi/agent/models.json '$destination'"
pi --list-models复制的是包含 API key 引用的配置文件;引用的环境变量仍需在 Windows 进程环境中设置。不要把密钥提交到仓库。
如果 OpenCode Desktop 需要从 ~/.opencode/bin/opencode 启动兼容服务,可以额外创建兼容链接:
./scripts/install.sh --link-opencode如果该路径已经存在真正的 OpenCode CLI,脚本会拒绝覆盖。只有确认需要替换时才使用
--link-opencode --force;原文件会先被备份。
通用形式:
opencode-server-adaptor [全局选项] <命令> [命令选项]常用命令包括 serve(启动 HTTP 服务)、version / adaptor-version(查看版本)、
compatibility get|set(管理 OpenCode 兼容版本)和 help。运行 opencode-server-adaptor --help
可查看完整列表。下面列出最常用的选项;--help 的输出与下表一致。
放在子命令之前,对所有命令生效。
| 选项 | 说明 |
|---|---|
--verbose |
输出 HTTP 请求和调试日志到 stderr。开启后会同时启用结构化日志输出并把最低日志级别设为 DEBUG(见下方日志选项说明)。也可作为 serve 子命令选项放在 serve 之后。 |
--print-logs |
将结构化日志输出到 stderr,但不改变日志级别。 |
--log-level <LEVEL> |
最低日志级别,取值 DEBUG、INFO、WARN、ERROR。显式指定时会覆盖 --verbose 带来的 DEBUG 默认值;未指定时默认 INFO,开启 --verbose 时默认 DEBUG。 |
--version / -v |
打印 OpenCode 兼容版本并退出。 |
--help / -h |
显示帮助信息。 |
三个日志相关选项的关系:--verbose 是最完整的一档,等价于同时启用 --print-logs、把级别降到 DEBUG、并额外打印 HTTP 请求;如果只需要结构化日志而不想降到 DEBUG,单独使用 --print-logs;--log-level 可在任何情况下显式指定级别。
| 选项 | 说明 |
|---|---|
--hostname <HOST> |
监听地址,默认 127.0.0.1。 |
--port <PORT> |
监听端口,默认 4096,取值 0–65535。 |
--cors <ORIGIN> |
允许的 CORS 来源,可重复指定。 |
--verbose |
同全局 --verbose,可放在 serve 之前或之后。 |
--disable-pty-token-check |
跳过 PTY WebSocket 的 connect-ticket 校验,允许客户端不带 ticket 直接升级连接。仅用于兼容部分 OpenCode Desktop 版本的 PTY 连接问题(详见“启动服务”一节);不要在对公网暴露的服务上使用。 |
--api-version <VERSION> |
选择暴露的 API 协议版本,取值 v1 或 v2,默认 v2。详见上方“OpenCode 协议兼容”一节。 |
--disable-v1-compatible |
不挂载兼容层路由(GET /config、DELETE /session/:id、GET /file、GET /file/content、GET /find/file)。关闭后 Desktop 文件树和相关兼容功能可能不可用。 |
--msg-part-encap |
将一次请求产生的所有 assistant part 包裹在同一条响应 message 内,实现一次请求对应一次响应;默认关闭。默认模式按连续相同类型拆成多条 assistant message,以避免部分 v2 客户端在同 message 内重排 part。 |
使用安装后的程序启动:
opencode-server-adaptor serve --hostname 127.0.0.1 --port 4096服务默认启用 v2 协议。/api/health 返回当前进程的数值型 pid,/global/health 不注册并返回
404,以符合 OpenCode 客户端的协议探测规则。如需改用 v1 协议接口,加上 --api-version=v1:
opencode-server-adaptor serve --api-version=v1 --hostname 127.0.0.1 --port 4096v1 模式下挂载 v1 路由和兼容层,v2 的 /api/* 接口不可用;v2 模式下挂载 v2 路由和兼容层,v1 专属接口不可用。
默认模式优先将连续同类型的 assistant part 分组为多条响应 message,以规避部分 v2 客户端在同一 message 内按 part ID 重排而导致的顺序错乱。如果调用方要求一次请求只对应一次响应,可以启用:
opencode-server-adaptor serve --msg-part-encap --hostname 127.0.0.1 --port 4096该模式下,同一用户轮次只对应一条 assistant message,其中可以包含 reasoning、tool、text 等多个 part;该 message
保存整轮的 finish、usage 和错误。服务仍只在后端运行真正 settled 后将 session 切换为 idle。这一模式会减少消息
数量和历史分页开销,但在上述 v2 客户端中可能出现 part 顺序错乱,因此只建议在需要请求/响应一一对应时使用。
也可以直接从源码启动:
bun run src/cli.ts serve --hostname 127.0.0.1 --port 4096默认监听 127.0.0.1:4096。需要排查问题时可启用详细日志(选项说明见“命令行选项”一节):
opencode-server-adaptor --verbose serve --hostname 127.0.0.1 --port 4096某些 OpenCode Desktop 版本的 v2 PTY connectToken 调用尚未接好,连接终端时拿不到 ticket 会被服务端
403 拒绝。此时可以加上 --disable-pty-token-check 放宽校验(选项说明见“命令行选项”一节):
opencode-server-adaptor serve --disable-pty-token-check --hostname 127.0.0.1 --port 4096该选项仅放宽 PTY 终端连接的 ticket 校验,不影响其他接口;PTY 仍要求会话存在且属于当前 directory、 进程处于 running 状态。不要在对公网暴露的服务上使用。
PTY 终端在没有客户端消费(WebSocket 断开且无订阅者)超过 15 分钟后会被服务端自动关闭并回收, 避免客户端崩溃或断网后留下孤儿 shell 进程。客户端在线时(WebSocket 保持连接)不会触发回收。 PTY 进程退出时服务端会立即移除对应会话并通知客户端,随后重连该 PTY 会得到 404。
常用运行配置示例:
export OPENCODE_SERVER_USERNAME="opencode"
export OPENCODE_SERVER_PASSWORD="change-me"
#export PI_PROVIDER="my-provider"
#export PI_MODEL="my-model-id"
opencode-server-adaptor serve --hostname 127.0.0.1 --port 4096PI_PROVIDER 是 models.json 中 providers 对象的键,PI_MODEL 是该 provider 的 models[].id,两者不是
展示名称。未设置时,Pi 使用自己的默认 provider/model 选择。
所有配置均可通过环境变量覆盖,环境变量优先级高于配置文件(~/.config/opencode-server-adaptor/config.json)。
除下表列出者外,--help 的 ENVIRONMENT 区块也会列出认证相关变量。未显式说明的数值型变量解析失败时回退到默认值。
开发调优相关的 Agent 进程与子任务参数见 AGENTS.md。
一份配置文件,通用字段位于顶层,后端专属配置放在各自的 pi / dsh 分区:
{
"defaultAgent": "pi",
"host": "127.0.0.1",
"port": 4096,
"logLevel": "INFO",
"defaultWorkspace": "/home/user/workspace",
"pi": {
"cliPath": "/home/user/.bun/bin/pi",
"sessionDir": "/home/user/.local/state/opencode-server-adaptor/pi-sessions",
"provider": "my-provider",
"model": "my-model-id",
"maxActiveProcesses": 3,
"rpcTimeoutMs": 120000
},
"dsh": {
"baseUrl": "http://127.0.0.1:3080",
"allowRemote": false,
"requestTimeoutMs": 30000,
"approvalPolicy": "allow-once",
"provider": "",
"model": "",
"reasoningEffort": ""
}
}通用顶层字段:compatibilityVersion、defaultAgent、host、port、logLevel、defaultWorkspace、
agentIdleTimeoutMs、agentStartTimeoutMs,以及全部 *Subtask* 子任务调度参数(见下方 Pi 后端与
AGENTS.md 的调优表)。旧版本平铺的 pi*/dsh* 键(如 piProvider、dshBaseUrl)仍可读取以便
平滑迁移,但新配置应使用分区结构(分区优先级更高)。环境变量始终优先于文件中的任意键。
| 变量 | 说明 |
|---|---|
OPENCODE_SERVER_PASSWORD |
启用 HTTP Basic Auth 的关键变量。设置后服务开启认证;未设置或空字符串时不启用。注意只判断该变量,不判断 OPENCODE_SERVER_USERNAME。 |
OPENCODE_SERVER_USERNAME |
Basic Auth 用户名。未设置但已设密码时默认为 opencode;未设密码时为 null(不启用认证)。 |
认证为明文 HTTP Basic Auth,仅适用于 127.0.0.1 本地或受信网络。对公网暴露务必加 TLS 反代。
| 变量 | 说明 |
|---|---|
HOST |
监听地址,默认 127.0.0.1。 |
PORT |
监听端口,默认 4096。 |
DATABASE_PATH |
SQLite 数据库路径,默认 ~/.local/state/opencode-server-adaptor/adaptor.db。设为 :memory: 使用内存库(主要用于测试)。 |
PROVIDER_CONFIG_PATH |
providers.yaml 路径。未设置时与配置目录同目录;当 DATABASE_PATH=:memory: 时为空字符串。 |
XDG_STATE_HOME |
状态目录前缀,覆盖默认的 ~/.local/state。 |
XDG_CONFIG_HOME |
配置目录前缀,覆盖默认的 ~/.config。 |
OPENCODE_ADAPTOR_COMPAT_VERSION |
覆盖 OpenCode 兼容版本(如 1.18.7),优先级高于配置文件和内置默认值。 |
OPENCODE_CLIENT |
标记当前 OpenCode 客户端类型,主要供内部识别。 |
LOG_LEVEL |
默认日志级别,取值 DEBUG、INFO、WARN、ERROR,默认 INFO。命令行 --log-level 和 --verbose 优先于此变量。 |
| 变量 | 说明 |
|---|---|
PI_CLI_PATH |
Pi CLI 启动命令。Linux/WSL 默认通过 ~/.bun/bin/bun 执行 ~/.bun/bin/pi;Windows 优先使用 ~/.bun/bin/pi.exe。 |
PI_PROVIDER |
默认 Pi provider 键(models.json 中 providers 对象的键,非展示名称)。 |
PI_MODEL |
默认 Pi model ID(models[].id,非展示名称)。 |
PI_SESSION_DIR |
Pi 会话数据目录,默认 ~/.local/state/opencode-server-adaptor/pi-sessions。 |
DEFAULT_AGENT |
默认 Agent 后端,默认 pi;设为 deepSeek-Harness 时默认使用 DeepSeek Harness(旧值 dsh/DeepSeekHarness/DSHarness 仍可用)。 |
DEFAULT_WORKSPACE |
默认工作目录,默认当前目录。 |
DSH 后端连接一个已经启动的 dsh web 实例。创建 OpenCode 会话时选择 Agent deepSeek-Harness(或把
DEFAULT_AGENT=deepSeek-Harness 设为默认;旧标识 dsh、DeepSeekHarness、DSHarness 作为别名仍可解析)。
OpenCode Desktop 的 Agent 列表按 id 展示(首字母大写),因此适配器的 id 采用小写开头、后续大写的形式,
Desktop 会显示为 DeepSeek-Harness。适配器同时提供内置 provider dsh(名称 "DeepSeek Harness")及其默认模型
"DeepSeek Harness default model";未显式指定模型时,DSH 会话的默认模型会按后端归属解析为该条目,而不会落到
Pi 的默认模型。此外,适配器会通过 DSH 的 llm.models 把 DSH 实例已配置的 provider/模型目录实时镜像到 OpenCode
的模型列表,连接就绪后自动刷新;用户可以直接在模型选择器中挑选这些模型。镜像规则:provider id 统一加
dsh- 前缀(如 dsh-deepseek-official),显示名统一加 DSH: 前缀(如 DSH: DeepSeek);选择后适配器会去掉
dsh- 前缀再经 session.selectModel 转发给 DSH。DSH 会话若收到一个非 dsh- 开头的 provider(既不是镜像路由
也不是占位 dsh),适配器直接返回 invalid_request 错误。每个 OpenCode 会话对应一个 DSH 会话(cwd 为会话目录);
DSH 侧的 provider/model、工具、preset 和权限策略由 DSH 实例自身配置,适配器只在配置了 DSH_PROVIDER/DSH_MODEL
(或请求携带 provider/model)时调用 session.selectModel。DSH 官方模型目录中随模型公布的
reasoning.efforts(思考强度)会以 OpenCode model variants 的形式暴露:每个 DSH effort 对应一个 variant,
其 id 就是 DSH effort id;用户选择 variant 后,适配器把该 id 作为 reasoningEffort 与 provider/model 一起
传给 session.selectModel。未选择 variant 时,镜像的 dsh-* 模型沿用 DSH 目录自带的
defaultEffort;DSH_REASONING_EFFORT / dsh.reasoningEffort 只作为占位默认路由
(dsh / deepSeek-Harness)的默认思考强度。模型目录还会响应 DSH 转发的
llm/adapters-updated 与 settings/document-updated 事件并自动刷新。
服务启动时不会连接 DSH,也不会为普通的 health/provider/model discovery 请求启动连接。连接、握手和目录刷新延迟到 首次创建或恢复 DSH runtime;因此没有安装 DSH、3080 端口未监听或 DSH 暂时不可用时,Pi 后端和其他 v2 接口仍可正常使用。
| 变量 | 说明 |
|---|---|
DSH_BASE_URL |
dsh web 的 HTTP 地址,默认 http://127.0.0.1:3080。默认只允许 loopback 主机。 |
DSH_ALLOW_REMOTE |
设为 1 后允许连接非 loopback 的 DSH 地址(远程访问需自行保证 TLS 与认证)。 |
DSH_REQUEST_TIMEOUT_MS |
普通 unary RPC 超时(毫秒),默认 30000。长轮询和 prompt 等用户节奏调用不受该超时限制。 |
DSH_APPROVAL_POLICY |
DSH 工具审批请求的处理策略:allow-once(默认)或 reject。适配器是 headless 服务,不会把审批转发给 OpenCode UI。 |
DSH_PROVIDER |
会话默认选择的 DSH provider 键;为空时沿用 DSH 实例默认。 |
DSH_MODEL |
会话默认选择的 DSH model ID;为空时沿用 DSH 实例默认。 |
DSH_REASONING_EFFORT |
占位默认模型路由(dsh / deepSeek-Harness)的默认思考强度;dsh-* 镜像模型不选 variant 时沿用 DSH 模型目录默认,选中 variant 时 variant 优先。 |
事件下行使用 /api/events.mux 与 /api/events.host 双 WebSocket,断线后按 generation 重建并重新握手
host.describe;prompt 结算以 DSH 的 turn/end 为准,错过的事件通过 session.history 追补。手动压缩通过 DSH 的
/compact host 命令完成(commands/execute,阻塞到压缩结束;不支持自定义压缩指令),
会话回退使用 DSH 的 session.fork。deepSeek-Harness-Plan 变体把会话的 plan 状态与变体对齐(读 host 的 plan
投影,用 /plan / /plan off 定向设置;与 deepSeek-Harness 互切即可在 plan 模式与非 plan 模式间切换),plan review
按 DSH_APPROVAL_POLICY 自动应答(allow-once 自动批准)。DSH 上游没有 session.delete,
删除 OpenCode 会话时会把对应的 DSH 会话归档(workspace.archiveSession,尽力而为,DSH 不可达不阻塞删除)。
trustedHosts 不被当作认证
机制,适配器也不实现 DSH 不存在的认证协议。
providers.yaml 的完整字段说明和示例见 AGENTS.md。