05 — 编码 Agent (pi-coding-agent)

问题

pi-agent-core 提供了 Agent 运行时,但它不知道什么是”文件”、什么是”bash 命令”、什么是”系统提示”。它只是一个通用的 agent loop。

pi-coding-agent 的任务是:在 agent-core 之上组装一个可用的编码助手产品——定义具体工具、构建系统提示、管理会话、支持扩展、提供终端 UI。

启动流程

packages/coding-agent/src/main.ts(~982 行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
用户执行 `pi "帮我读一下 README"`


cli.ts (7行)
└─ setupCli() → main(process.argv.slice(2))


main.ts
├─ parseArgs() // 解析 CLI 参数 (args.ts)
├─ 运行一次性迁移 // migrations.ts
│ (oauth.json → auth.json, commands/ → prompts/, etc.)
├─ resolveAppMode() // 确定: interactive | print | json | rpc
├─ createSessionManager() // 处理 --session/--resume/--continue/--fork
├─ createRuntime() 工厂 // 创建 cwd 绑定的服务
│ ├─ ModelRuntime // 模型管理
│ ├─ SettingsManager // 设置管理
│ ├─ ResourceLoader // 加载扩展/技能/提示/主题
│ └─ AgentSession // 封装 Agent
├─ AgentSessionRuntime // 管理会话生命周期
└─ 分发到运行模式
├─ InteractiveMode // TUI 交互模式
├─ runPrintMode // 单次输出模式 (pi -p)
└─ runRpcMode // RPC 嵌入模式

config.ts 定义路径:~/.pi/agent/ 是全局配置目录,包含 auth.jsonsettings.jsonsessions/

AgentSession — 产品层核心

packages/coding-agent/src/core/agent-session.ts(~120K,最大文件)

AgentSession 类(line 310)封装 Agent(来自 agent-core),添加:

能力 说明
会话持久化 事件订阅 + 自动保存到 session 文件
模型管理 运行时切换模型和 thinking level
Compaction 手动 + 自动压缩,含溢出恢复
Bash 执行 内联 bash 命令处理
会话分支 fork、导航会话树
扩展工具注册 _toolDefinitions+_toolRegistry
系统提示构建 每轮重建,纳入扩展 append

prompt() 方法

agent-session.ts:1159 — 处理用户输入的入口:

1
2
3
4
5
6
7
8
9
prompt(userInput)

├─ 1. 检查扩展命令("/" 前缀)
├─ 2. 处理 input 事件(扩展钩子)
├─ 3. 技能/模板展开
├─ 4. steer/followUp 队列管理(流式时排队,否则直接发送)
├─ 5. 认证验证
├─ 6. Compaction 检查
└─ 7. 发送到 Agent.prompt()

AgentSessionEvent

agent-session.ts:144 — 扩展 AgentEvent(agent-core 的事件),添加会话级事件:

事件 说明
compaction_start/end 上下文压缩开始/结束
thinking_level_changed thinking level 变更
auto_retry_start/end 自动重试
bash_execution_update bash 执行进度
session_info_changed 会话信息变更

SDK 工厂

packages/coding-agent/src/core/sdk.ts:173createAgentSession()

这是组装 Agent 的核心函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
createAgentSession(options)

├─ 创建 ModelRuntime // 模型目录、认证、provider 注册
├─ 创建 SettingsManager // 全局 + 项目设置
├─ 创建 SessionManager // JSONL 会话持久化
├─ 创建 ResourceLoader // 扩展/技能/提示/主题

├─ 解析模型 // 从会话历史 → 设置默认 → provider 默认
├─ 解析 thinking level // clamp 到模型能力
├─ 配置默认工具 // ["read", "bash", "edit", "write"]

├─ 创建 streamFn // 包装 ModelRuntime.streamSimple()
│ ├─ provider 重试设置
│ ├─ header 变换(扩展 before_provider_headers 钩子)
│ └─ context 变换(扩展 context 钩子)

├─ setDefaultStreamFn(streamSimple) // 注入 agent-core

└─ 创建 Agent(来自 agent-core)
└─ 包装为 AgentSession

关键设计setDefaultStreamFn()sdk.ts:37)将 pi-ai 的流式函数注入 agent-core。这样 agent-core 的 Agent 类在调用 LLM 时,实际上是通过 pi-aiModels.stream() 路由到具体 provider。

工具定义

packages/coding-agent/src/core/tools/

8 个内置工具

工具 文件 说明
read read.ts 读取文件(支持截断)
bash bash.ts 执行 shell 命令(spawn hooks)
edit edit.ts+edit-diff.ts 文件编辑 + diff 生成
write write.ts 写文件
grep grep.ts 内容搜索(使用rg
find find.ts 文件查找(使用fd
ls ls.ts 目录列出
powershell powershell.ts Windows PowerShell 执行

ToolDefinition → AgentTool 转换

每个工具有两层定义:

1
2
3
4
5
6
7
8
9
10
11
ToolDefinition(扩展 API 层)
├─ name, description
├─ inputSchema (TypeBox) // 参数 schema
├─ execute(args, context) // 执行函数
└─ render hooks // UI 渲染钩子

▼ wrapRegisteredTool()
AgentTool(agent-core 运行时层)
├─ name, description
├─ inputSchema (TSchema)
└─ execute(args, context)

工厂函数(tools/index.ts):

  • createCodingTools()[read, bash, edit, write](默认集)
  • createReadOnlyTools()[read, grep, find, ls]
  • createAllTools() → 全部 8 个

系统提示构建

packages/coding-agent/src/core/system-prompt.ts:28buildSystemPrompt()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
buildSystemPrompt()

├─ 默认提示 (line 127)
│ "You are an expert coding assistant operating inside pi,
│ a coding agent harness..."

├─ 列出可用工具(一行简介)

├─ 根据活跃工具生成指南
│ (e.g., 有 bash 但没 grep/find/ls → "Use bash for file operations")

├─ 项目上下文文件
│ AGENTS.md, CLAUDE.md → 包裹在 <project_context> 标签中

├─ 技能 (skills)
│ formatSkillsForPrompt()

├─ pi 文档路径
│ README, docs, examples

└─ 自定义覆盖
customPrompt / appendSystemPrompt

关键设计:系统提示每轮重建。因为扩展可以在运行时添加工具或 append 内容,静态缓存的系统提示会过期。每次 LLM 调用前都重新构建,确保提示反映当前状态。

扩展系统

packages/coding-agent/src/core/extensions/

扩展类型定义

extensions/types.ts(~64K,第二大文件):

1
2
3
4
5
6
7
8
9
10
11
12
13
interface ExtensionContext {
ui: ExtensionUIContext; // UI 原语: select, confirm, input, notify...
mode: ExtensionMode; // "tui" | "rpc" | "json" | "print"
cwd: string;
sessionManager: SessionManager;
modelRegistry: ModelRegistry;
model: Model<any>;
thinkingLevel: ThinkingLevel;
isIdle(): boolean;
abort(): void;
compact(): void;
getSystemPrompt(): string;
}

扩展可以注册:

注册项 方法 说明
工具 pi.registerTool() 添加自定义工具
命令 pi.registerCommand() 添加/ 命令
快捷键 pi.registerShortcut() 添加键盘快捷键
Provider pi.registerProvider() 添加自定义 LLM provider
标志 pi.registerFlag() 添加 CLI 标志

扩展加载

extensions/loader.ts(~26K):

使用 jiti(运行时 TypeScript 转译)加载 .ts/.js 扩展文件。

关键设计VIRTUAL_MODULES(line 50):将 pi 包名映射到打包实例,让扩展即使在编译后的 Bun 二进制中也能 import pi 包。

加载来源:

  • 文件扩展:从 extensions/ 目录发现
  • 内联扩展:InlineExtension + factory 函数
  • 包扩展:npm、git、本地路径安装的扩展包

扩展运行

extensions/runner.ts(~40K)— ExtensionRunner

1
2
3
4
生命周期事件分发:
before_agent_start → tool_call → message_update → context →
input → session_before_compact → before_provider_request →
before_provider_headers → ...

ExtensionRunner 在每个生命周期节点调用已注册的处理器。before_provider_headersbefore_provider_request 事件由 AgentstreamFn 触发——这是扩展修改 LLM 请求的入口。

内置扩展

extensions/index.ts:只有一个内置扩展——llama.cppextensions/llama/):

  • 注册 llama.cpp provider 和 /llama 命令
  • HTTP 客户端连接 llama.cpp server
  • HuggingFace 模型搜索/下载
  • TUI 模型管理界面

运行模式

packages/coding-agent/src/modes/

Interactive 模式

modes/interactive/interactive-mode.ts(~229K,仓库最大文件)

InteractiveMode 类管理完整的 TUI 体验:

  • 使用 pi-tui 库渲染(TuiMainScreenContainerMarkdownText
  • 管理聊天视口、编辑器、footer、模型选择器、会话选择器
  • 处理键盘输入、slash 命令、自动补全、剪贴板、图片粘贴
  • 订阅 AgentSession 事件,事件驱动渲染
  • 处理扩展 UI 上下文(对话框、组件、自定义编辑器、覆盖层)

components/(38 个文件):每个渲染需求一个组件——assistant-message.tsbash-execution.tsfooter.tsmodel-selector.tsdiff.tsmermaid.tslogin-dialog.ts 等。

modes/print-mode.ts:单次模式,用于 pi -p "prompt"。发送 prompt → 输出结果 → 退出。用于脚本化。

RPC 模式

modes/rpc/:无头 JSON-over-stdin/stdout 协议,用于嵌入。runRpcMode() 监听 JSON 命令,输出事件和响应。

JSON Event 模式

modes/json-event.tstoJsonEvent()AgentSessionEvent 转为 JSON 可序列化对象,用于 --mode json 输出。

资源加载

packages/coding-agent/src/core/resource-loader.ts(~40K)

DefaultResourceLoader 从全局(~/.pi/agent/)和项目(.pi/)位置加载:

资源 位置 说明
扩展 extensions/ .ts/.js 文件
技能 skills/ SKILL.md 文件夹或 .md 文件
提示模板 prompts/ .md 文件
主题 themes/ .json 文件
上下文文件 根目录 AGENTS.mdCLAUDE.md

reload() 可以在信任决策后重新加载需要信任的资源。

其他核心组件

组件 文件 说明
ModelRuntime model-runtime.ts 模型目录、认证、provider 注册、流式协调
ModelResolver model-resolver.ts 解析provider/model:thinking 模式为 Model 对象
SessionManager session-manager.ts JSONL 会话持久化、分支、fork、会话树导航
SettingsManager settings-manager.ts 全局~/.pi/agent/settings.json+ 项目 .pi/settings.json
Skills skills.ts 加载和格式化 skill 文件(markdown + frontmatter)
TrustManager trust-manager.ts 项目信任管理(不受信任项目的扩展需要审批)
Keybindings keybindings.ts KeybindingsManager+DEFAULT_EDITOR_KEYBINDINGS/DEFAULT_APP_KEYBINDINGS
PackageManager package-manager.ts 安装/管理 npm、git、本地路径的 pi 包

AgentSession 如何扩展 Agent 核心

这是理解整个项目的关键——AgentSession 在不修改 Agent 类的前提下扩展其行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Agent (agent-core)         AgentSession (coding-agent)
│ │
│ ← streamFn ──────────────┤ 包装 ModelRuntime.streamSimple()
│ │ + 扩展 header/context 变换
│ │
│ ← tools ─────────────────┤ 从 ToolDefinition 创建 AgentTool
│ │ + 扩展注册的工具
│ │
│ ← systemPrompt ──────────┤ buildSystemPrompt() 每轮重建
│ │ + 项目上下文 + 技能 + 扩展 append
│ │
│ ← convertToLlm ──────────┤ 过滤 UI-only 消息
│ │
│ ← transformContext ──────┤ 扩展 context 钩子
│ │
│ ← shouldStopAfterTurn ───┤ compaction 检查
│ │
│ ← getSteeringMessages ───┤ 用户运行时输入
│ getFollowUpMessages │
│ │
│ ← beforeToolCall ────────┤ 扩展 before_tool 钩子
│ afterToolCall │
│ │
├─── 事件 ─────────────────▶ 订阅 → 持久化 + UI 渲染
│ │
└─── agent_end ────────────▶ 保存会话 + 更新统计

关键设计:所有扩展点都通过 AgentLoopConfig 的回调函数注入。Agent 类不需要知道扩展系统的存在——它只看到一组回调函数。这实现了开闭原则:对扩展开放,对修改关闭。

学习要点

  • AgentSession 是组装层:它不实现 Agent 逻辑,而是通过回调函数将工具、系统提示、扩展、会话管理注入 agent-core
  • 系统提示每轮重建:因为扩展可以动态添加工具和内容,静态缓存会过期
  • ToolDefinition → AgentTool 的两层设计:ToolDefinition 面向扩展开发者(含 UI 渲染钩子),AgentTool 面向运行时(只含执行逻辑)
  • 扩展系统通过生命周期事件解耦:扩展在 before_agent_starttool_callbefore_provider_request 等节点被调用,不需要直接修改 Agent 代码
  • streamFn 是 LLM 调用的唯一入口:通过 setDefaultStreamFn() 注入,让 agent-core 保持 provider 无关
  • VIRTUAL_MODULES 解决打包扩展的 import 问题:编译后的 Bun 二进制中,扩展仍然可以 import pi 包

上一篇:04-会话持久化-pi-protocol 与 session-backends
下一篇:06-终端 UI-pi-tui