Site logo
Published on

Pi 源码解读(七):执行过程如何变成界面上的文字和状态?

Authors
  • avatar
    Name
    Stone
    Twitter
本篇目录

假设你不想在终端里使用 Pi,而是想做一个 Vue 聊天页面:能逐字显示回答,也能看到“正在读文件”“修改失败”和“处理完成”。

你需要重写 Agent Loop 吗?不需要先从这里开始。你首先需要读懂 Pi 发出的事件。

NOTE

运行时报告发生了什么,界面决定怎么显示。

但“收到请求”“一条消息结束”“这次运行彻底收尾”,是三个不同的状态。

源码基准:Pi v0.84.1。本文分析真实的事件与 RPC 实现;Vue 页面是教学接入方案,不是 Pi 已经内置的页面。

一、别把“接口成功”当成“任务完成”

你在网页里发送:

把 App.vue 的页面标题改成你好。

假如只做一个普通请求,收到成功响应就关掉加载动画,可能会过早显示“已完成”。

Pi 的 RPC 文档明确区分两件事:prompt 的成功响应表示输入已被接受、排队或立即处理;后续运行进度与失败,继续通过事件报告。这张“受理回执”,不是任务做完的证明。1

这里的 RPC,可以先理解成“其他程序用消息指挥 Pi”。本课版本通过标准输入接收 JSON 命令,通过标准输出发送 JSON 行;它不是一个默认就能被浏览器直连的 HTTP 服务。2

一种可行的接入设计是:Vue 页面 ↔ 你自己的后端 ↔ Pi 进程。 后端负责转发命令与事件,也负责鉴权和运行环境;不能把浏览器界面直接当成本地文件执行权限。

二、先认识几类事件,就能画出界面状态

继续沿用修改标题的任务。Pi 可能先输出一句说明,再读取文件、修改文件,最后回答用户。

事件含义界面可以怎么处理
message_update助手消息正在更新,内部可能是文字、思考或工具请求的增量。遇到文字增量,追加到对应消息。
message_end一条消息完成,不等于整个任务完成。用最终内容校准这条消息。
tool_execution_start / update / end一个工具请求进入处理、提供进度、产生最终处理结果。按调用编号更新工具卡片,检查结果是否出错。
agent_end一次底层 Agent 运行结束,应用层可能还要继续处理。不要仅凭它宣布整个会话任务完成。
agent_settled会话级运行已收尾,没有剩余的自动重试、压缩重试或排队续跑。结束这次忙碌状态,再根据结果显示成功、失败或取消。

前面的消息和工具事件来自通用 Agent;agent_settled 是 Coding Agent 会话层补充的事件。它们不是同一层抽象。34

还有一个源码细节:tool_execution_start 在执行前检查之前就会发出。因此,它不证明工具函数已经真正运行;请求可能随后被扩展拦截。界面可以显示“正在处理工具请求”,但不能据此记录“文件已经被修改”。5

三、Pi 真的是怎样把事件传出去的?

先看进程内部:Agent Loop 接收模型流式输出,把文字和工具请求的变化包装成 message_update,交给事件处理链路。完成一条响应时,再发送 message_end。6

再看 RPC 出口:runRpcMode 会订阅 Session,将事件交给 toJsonEvent,然后序列化成 JSON 行写到标准输出。不是去识别终端上打印的“正在读取……”文字,而是接收明确的数据事件。7

这里有一个很具体的 Pi 设计:RPC 的消息更新会去掉累计的消息快照,主要保留增量。 message_start 提供开始状态,中间的增量拼出内容,message_end 提供最终权威消息。这个转换就在 json-event.ts,不是所有接入方式拿到的字段都完全一样。8

例如界面先收到“我先”,再收到“读取文件”,就拼成“我先读取文件”。如果把每次增量都当成整条消息替换,就可能只剩最后几个字;反过来,把累计快照反复追加,又会造成重复。

先确认拿到的是增量还是快照,再决定“追加”还是“替换”。

四、为什么 agent_end 后还不能马上当成空闲?

因为底层运行结束后,会话层还可能需要处理可重试错误、上下文压缩,以及结束处理器新加入的消息。

源码中,AgentSession._runAgentPrompt 等待底层 Agent 后,会通过 _handlePostAgentRun 检查是否需要继续;全部处理完,才调用 _emitAgentSettled。因此,agent_end 更像一个阶段的结束,agent_settled 才是会话层本次运行的收尾通知。9

还有一层区别:底层 Agent.waitForIdle() 会等当前运行以及被等待的事件监听器完成,而不是一发出 agent_end 就立即认为收尾结束。这里说的是程序内部的等待关系,不是保证远端网页已经渲染完成。1011

下面是一个简化的网页处理思路,不是 Pi 的原始实现:

发送需求,等待受理结果
受理成功:显示已受理,继续监听事件
收到消息开始:建立消息
收到文字增量:追加到对应位置
收到消息结束:用最终消息校准内容
收到工具事件:按调用编号更新状态和错误
收到 agent_end:不要立即停止监听
收到 agent_settled:结束忙碌状态,再展示实际结果

五、界面上的“结束”,也需要保留证据边界

工具卡片结束,不一定是成功。 检查 isError 和返回内容,不能把所有 tool_execution_end 都画成绿色对勾。12

会话收尾,也不证明业务目标达成。 Pi 的 _runAgentPrompt 在收尾路径里发出 agent_settled;它表达的是生命周期,而不是“测试全部通过”这类业务结论。13

连接断开,更不应自动显示成功。 这是接入设计要处理的情况:如果只是后端连接或 Pi 进程意外中断,界面应保留“中断或状态未知”,而不是替运行时编造完成结果。

对照 Codex:相似的问题,不同的协议

Codex App Server 的官方协议也区分文本增量、工作项结束和轮次结束,例如 item/agentMessage/delta、item/completed 和 turn/completed;结束通知还要结合 turn.status 区分完成、中断和失败。14

可以对照它们如何支持“边做边显示”,但不要把 Codex 的事件名、Turn 含义直接套到 Pi 上。这里比较的是官方公开接入协议,不是在声称两者内部代码相同。对照资料查阅于 2026-09-25。

六、回到源码和面试

第一遍只跟这条线:模型增量 → Agent 事件 → Session 订阅 → JSON 转换 → 客户端显示。

需要定位时,再展开三个入口

agent-loop.ts 的 streamAssistantResponse:看模型输出怎样变成消息事件。

json-event.ts 的 toJsonEvent:看 RPC 怎样移除累计快照。

rpc-mode.ts 的 runRpcMode:看订阅与输出怎样连接。

需要验证收尾,再读 agent-session.ts 的 _runAgentPrompt。

面试时,可以这样讲

我跟过 Pi 从模型流式输出到 RPC 的事件链路。运行时产生消息和工具事件,RPC 订阅 Session,再把事件转换成 JSON 发出去,所以界面不需要自己实现 Agent Loop。我特别注意了两个细节:RPC 的文字更新使用增量,不能按完整快照处理;agent_end 后也可能继续重试,要区分底层结束和会话级的 agent_settled。

一个不用写代码的小练习

网页收到 prompt 的成功响应,随后收到 agent_end,但还没收到 agent_settled。此时能否显示“代码已全部修改并验证通过”?

想过之后再看答案

不能。成功响应只表示输入被受理;agent_end 也不代表会话层已经没有后续处理。即使之后收到 agent_settled,仍要根据真实工具结果和测试证据判断任务是否成功。


这一课记住:用事件驱动界面,但不要把消息完成、运行收尾和业务成功当成同一件事。

参考源码

Footnotes

  1. Pi v0.84.1 · packages/coding-agent/docs/rpc.md ↩

  2. Pi v0.84.1 · packages/coding-agent/src/modes/rpc/rpc-mode.ts,第 47–58 行 ↩

  3. Pi v0.84.1 · packages/agent/src/types.ts,第 391–413 行 ↩

  4. Pi v0.84.1 · packages/coding-agent/src/core/agent-session.ts,第 135–150 行 ↩

  5. Pi v0.84.1 · packages/agent/src/agent-loop.ts,第 405–450 行 ↩

  6. Pi v0.84.1 · packages/agent/src/agent-loop.ts,第 261–346 行 ↩

  7. Pi v0.84.1 · packages/coding-agent/src/modes/rpc/rpc-mode.ts,第 330–340 行 ↩

  8. Pi v0.84.1 · packages/coding-agent/src/modes/json-event.ts ↩

  9. Pi v0.84.1 · packages/coding-agent/src/core/agent-session.ts,第 995–1035 行 ↩

  10. Pi v0.84.1 · packages/agent/src/agent.ts,第 306–313 行 ↩

  11. Pi v0.84.1 · packages/agent/src/types.ts,第 391–396 行 ↩

  12. Pi v0.84.1 · packages/agent/src/types.ts,第 409–413 行 ↩

  13. Pi v0.84.1 · packages/coding-agent/src/core/agent-session.ts,第 995–1006 行 ↩

  14. Codex 官方文档 · App Server 接入协议 ↩