AgentSession:编码 Agent 的编排层
AgentSession 是 pi-coding-agent 里最重的一层——它不直接跑 LLM,也不直接渲染 UI,而是夹在两者中间:对上,接收用户输入和 UI 事件;对下,驱动 pi-agent-core 的 Agent 跑循环。模型注册、工具定义、系统提示构建、上下文压缩、失败重试、消息排队,都归它管。可以把它理解成「把通用 Agent 包装成一个有状态、可恢复、带技能和工具的编码助手」的装配车间。
职责
AgentSession 做四件事:
- 装配:
AgentSessionConfig里塞进Agent实例、模型注册表、工具定义、扩展运行时、系统提示构造器,构造时挂上beforeToolCall/afterToolCall钩子和事件订阅。见packages/coding-agent/src/core/agent-session.ts:313-330。 - 接收输入:
prompt(text)是主入口,处理/斜杠命令、展开文件型提示模板、把文本转成AgentMessage[],再交给底层Agent。见packages/coding-agent/src/core/agent-session.ts:967-1000。 - 事件转发:订阅
Agent的事件流,重新包装成AgentSessionEvent发给上层 UI。见packages/coding-agent/src/core/agent-session.ts:330-335。 - 排队与重试:流式输出期间用户继续打字时,按
streamingBehavior决定走steer(插话)还是followUp(排队),而不是简单丢弃或阻塞。见packages/coding-agent/src/core/agent-session.ts:1181-1296。
设计动机
为什么不直接让 UI 调 Agent.prompt?因为编码助手需要一堆「通用 Agent 不管」的横切逻辑:斜杠命令(/model、/settings)、提示模板展开、模型/API key 校验、上下文超长时的自动压缩、工具调用前后的鉴权与遥测。把这些塞进 Agent 会让通用循环变重;放到 UI 层又会在 print 模式、rpc 模式、扩展里重复实现。AgentSession 抽出来当唯一入口,三种运行模式共用同一套编排逻辑,UI 只管渲染事件。
关键文件
packages/coding-agent/src/core/agent-session.ts:121-149—AgentSessionEvent与AgentSessionConfig类型,定义事件形状和装配接口。packages/coding-agent/src/core/agent-session.ts:244-253—class AgentSession声明与内部状态字段。packages/coding-agent/src/core/agent-session.ts:313-335— 构造函数:挂beforeToolCall/afterToolCall钩子,订阅Agent事件。packages/coding-agent/src/core/agent-session.ts:379-435— 工具调用前后的钩子实现,做鉴权和事件重发。packages/coding-agent/src/core/agent-session.ts:967-1109—prompt主流程:命令分发、模板展开、this.agent.prompt(messages)。packages/coding-agent/src/core/agent-session.ts:1181-1296—steer/followUp排队语义。packages/coding-agent/src/core/agent-session.ts:1317-1389—sendUserMessage(程序化入口)与abort。packages/coding-agent/src/core/agent-session.ts:713-743—subscribe,UI 通过它拿事件。
构造函数里挂钩子和订阅,是整个编排的起点:
// packages/coding-agent/src/core/agent-session.ts:313-335
constructor(config: AgentSessionConfig) {
// ... 存 config,初始化状态 ...
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
// ... 略 ...
}prompt 把用户文本变成 AgentMessage[] 后交给底层 Agent:
// packages/coding-agent/src/core/agent-session.ts:1107-1112
try {
await this.agent.prompt(messages);
} catch (error) {
// ... 失败处理 ...
}数据流
用户在 TUI 编辑器按回车 → InteractiveMode.defaultEditor.onSubmit → session.prompt(text):
流式期间用户继续打字时,prompt 不会硬塞,而是分流:
边界与失败
- 斜杠命令冲突:扩展注册的命令在流式中也能即时执行(自己管 LLM 交互),不走排队。若命令无法排队会直接抛错,见
packages/coding-agent/src/core/agent-session.ts:1255-1259。 - 模型/key 缺失:非流式路径会校验模型和 API key,缺失时抛错而非发空请求。
- 压缩中止:
abort会同时取消主循环、压缩控制器、分支摘要控制器,见packages/coding-agent/src/core/agent-session.ts:1744-1752。 - 重试:
Agent.emit()同步调_handleAgentEvent,而prompt()会waitForRetry(),失败后由重试逻辑决定是否重新发轮。
小结
AgentSession 是编码助手的「装配车间 + 事件总线」:装配 Agent、转发事件、排队插话、管工具钩子。往上看是 InteractiveMode/runPrintMode/runRpcMode 三种 UI 模式,往下看是 pi-agent-core 的通用循环。装配细节看 createAgentSession 装配,底层循环看 双层 while 主循环。