【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除
标题
199、【Agent】【OpenCode】TuiThreadCommand handler:worker 到底是什么
背景
上篇 blog
【Agent】【OpenCode】TuiThreadCommand handler:transport 双形态的触发与误区
拆了 transport 双形态:外部模式由--port/--hostname/--mdns或 config 的server非默认触发(worker 真起 HTTP server、fetch/events为undefined),内部模式全默认走进程内 RPC(createWorkerFetch把请求序列化转发、createEventSource订阅 RPC 事件流、伪地址http://opencode.internal);并澄清internal/external 不等于"内部/外部大模型",模型由 worker 按配置调用、与传输通道正交。198 里"RPC 转发给 worker"反复出现,但worker 到底是个什么东西一直没讲透——本篇专门拆worker.ts:它的 RPC 方法表、它握着的 Server 核心、它的事件中转
OpenCode
192 篇只讲了"怎么把 worker 拉起来"(target()三级回退 +new Worker),本篇回答"worker 是什么"。一句话先给结论:worker 就是 opencode 的后端服务——会话、模型调用、权限、文件、事件全在它里面,TUI 只是前端界面壳,两者同进程分线程、靠 RPC 通信。
🧩架构总览:TUI 是壳,worker 是核
| 角色 | 所在线程 | 是什么 | 持有什么 |
|---|---|---|---|
| TUI | 主线程 | 前端界面 | 渲染、输入、快捷键,无核心状态 |
| Worker | 独立线程 | opencode 后端服务 | Server(session/provider/权限/MCP)、Instance(按目录实例)、事件总线 |
主线程通过Rpc.client<typeof rpc>(worker)(thread.ts:140)连上 worker,typeof rpc让 TUI 能类型安全地调用 worker 暴露的每一个方法。下面用 worker.ts 源码逐层佐证。
📋佐证 1:RPC 方法表——worker 能提供什么服务一目了然
worker.ts:101-148 的export const rpc就是 worker 的全部服务清单:
exportconstrpc={asyncfetch(input){...},// 内部模式:执行 TUI 转来的 HTTP 请求asyncserver(input){...},// 外部模式:真起 HTTP serverasynccheckUpgrade(input){...},// 版本升级检查asyncreload(){...},// 重置配置 + 重建实例(SIGUSR2 热重载)asyncsetWorkspace(input){...},// 切换 workspace 事件流asyncshutdown(){...},// 停事件流 + 释放实例 + 停 server}六个方法恰好对应前面文章里所有 RPC 调用的落点:
| RPC 方法 | 主线程调用处 | 用途 |
|---|---|---|
fetch | thread.ts:24-40createWorkerFetch | 内部模式请求代理 |
server | thread.ts:187 external 分支 | 外部模式起真 server |
shutdown | thread.ts:162stop | 优雅关闭(5s 超时) |
reload | thread.ts:145SIGUSR2 | 热重载 |
checkUpgrade | thread.ts:198 | 延迟升级检测 |
setWorkspace | thread.ts:46createEventSource | 切换工作区事件流 |
🏛️佐证 2:worker 握着 opencode 的核心服务 Server
worker.ts:102-120 的fetch里,关键一行是:
asyncfetch(input){...constresponse=awaitServer.Default().fetch(request)// 进程内交给核心 HTTP 服务return{status,headers,body}}Server(src/server/server.ts)是 opencode 的核心 HTTP 服务,挂满了业务路由(server.ts:244-254):
/session(会话) /provider(模型提供方) /question(提问) /permission(权限) /mcp /config /pty /experimental ...上表是 Server 提供的能力清单,而 worker 只是把 TUI 转来的请求"转交"进这套路由:
TUI 内部模式的每个请求,最终都落到这套路由上——会话、模型调用、权限判断都在这,由 worker 持有并执行。所以"TUI 用 fetch 请求后端"其实是"TUI 把请求 RPC 给 worker,worker 在进程内喂给同一个核心服务"。
📡佐证 3:worker 是事件流的中转站
worker.ts:37-39 把全局事件转发到 RPC:
GlobalBus.on("event",(event)=>{Rpc.emit("global.event",event)})worker.ts:47-97 的startEventStream还用 SDK 订阅 opencode 服务的事件流,逐条Rpc.emit("event", ...)(worker.ts:84-86)推给 TUI。这正是内部模式createEventSource的on(handler)收到的数据来源(thread.ts:42-49)。
🔌佐证 4:RPC 机制本身——JSON + postMessage
rpc.ts 定义了整套通信协议:
// worker 侧(rpc.ts:6-14)exportfunctionlisten(rpc){onmessage=async(evt)=>{constparsed=JSON.parse(evt.data)if(parsed.type==="rpc.request"){constresult=awaitrpc[parsed.method](parsed.input)postMessage(JSON.stringify({type:"rpc.result",result,id:parsed.id}))}}}worker 侧listen接收请求并回结果,emit主动推事件(rpc.ts:16-18);主线程侧client用call发请求挂 Promise、用on订阅事件频道(rpc.ts:20-65)。本质就是JSON 序列化走postMessage/onmessage的进程内消息通道。
🧩与前面所有文章串起来
worker 不是新概念——系列文章里每处"RPC 调用"都在它身上:
| 前面文章的点 | 落到 worker 的什么 |
|---|---|
内部模式createWorkerFetch | rpc.fetch→Server.Default().fetch |
内部模式createEventSource | worker 的Rpc.emit("event") |
外部模式client.call("server") | rpc.server→Server.listen |
幂等stop的 shutdown RPC | rpc.shutdown→ 释放实例 + 停 server |
SIGUSR2热重载 | rpc.reload→ 重置 config + disposeAll |
| 延迟升级检测 | rpc.checkUpgrade |
📊总结对比
| 维度 | TUI(主线程) | Worker(独立线程) |
|---|---|---|
| 职责 | 界面渲染与输入 | 后端服务(Server/Instance/事件) |
| 核心状态 | 无 | session / provider / 权限 / MCP |
| 对外通道 | 不直接暴露 | 经 RPC 或真 HTTP server |
| 生命周期 | tui()主循环 | 由stop的 shutdown RPC 收尾 |
📌一句话记忆
worker 就是 opencode 的后端服务:
rpc方法表(fetch/server/shutdown/reload…)是它的服务清单,Server是它的业务核心(会话/模型/权限),Rpc.emit是它的事件出口。TUI 只是壳,一切核心能力都在 worker 里,前后端靠 JSON+postMessage 的进程内 RPC 解耦。
OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog
【Agent】【OpenCode】TuiThreadCommand handler:checkUpgrade 的延迟与 unref