Pi Agent Harness 源码解析:TypeScript 实现的模块化 Coding Agent

Pi Agent Harness 源码解析:TypeScript 实现的模块化 Coding Agent

深入剖析 Earendil Works 开源的 Pi Agent Harness——一个基于 TypeScript Monorepo 构建的模块化交互式编程代理

项目简介

Pi Agent Harness 是由 Earendil Works 开发的开源 coding agent 项目。其核心定位是提供一个可扩展的交互式编程代理,能运行在终端中,通过多 LLM provider 驱动,完成代码编写、搜索、编辑等任务。

核心特点

  • 🔌 多 LLM Provider 支持:OpenAI、Anthropic、Google、GitHub Copilot、OpenRouter、xAI、Mistral 等 20+ 个 provider 的统一接口
  • 🧩 自扩展:coding agent 可以通过 skill、extension、prompt template 等方式进行扩展
  • 🖥️ 交互式终端 UI:支持差分渲染的 TUI、alt-screen 全屏模式、overlay、滚动视图等
  • 📦 会话持久化与分支:支持 JSONL 格式的会话存储、会话 fork、恢复、continue
  • 🔗 RPC 模式:支持通过 JSON 行进行程序化控制,可嵌入到其他应用中
  • 🔐 供应链安全:pin 依赖版本、npm shrinkwrap、install lock、lifecycle script 审计

发布包

npm 包名 描述
@earendil-works/pi-coding-agent 交互式 coding agent CLI
@earendil-works/pi-agent-core Agent 运行时(工具调用、状态管理)
@earendil-works/pi-ai 统一多 provider LLM API
@earendil-works/pi-tui 终端 UI 库(差分渲染)

整体架构

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
┌─────────────────────────────────────────────────────────────┐
│ 用户终端 │
└────────────────────────┬────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ packages/coding-agent (交互式 CLI 主程序) │
│ ┌───────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ main.ts │→ │ AgentSession │→ │ SessionManager │ │
│ │ cli.ts │ │ Runtime │ │ (会话持久化) │ │
│ └───────────┘ └──────┬───────┘ └──────────────────┘ │
│ │ │
│ ┌───────────────────┐ │ ┌────────────────────────┐ │
│ │ Extensions / Skills│←─┘→│ SettingsManager │ │
│ │ (扩展与技能) │ │ (配置管理) │ │
│ └───────────────────┘ └────────────────────────┘ │
└────────────────────────┬────────────────────────────────────┘

┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────────┐ ┌────────────┐ ┌──────────────┐
│ packages/ │ │ packages/ │ │ packages/
│ agent │ │ tui │ │ protocol │
│ (Agent 运行时)│ │ (终端 UI) │ │ (RPC 协议) │
└──────┬───────┘ └────────────┘ └──────────────┘


┌─────────────────────────────────────────────────────────────┐
│ packages/ai (统一 LLM API 层) │
│ ┌────────────┐ ┌──────────┐ ┌────────────────┐ │
│ │ stream() │→ │ Provider │→ │ HTTP Client │ │
│ │ (统一入口) │ │ Registry │ │ (fetch/SSE) │ │
│ └────────────┘ └──────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ OpenAI / Anthropic / Google / Copilot / OpenRouter / xAI │
│ Mistral / Ollama / Groq / Cerebras / DeepSeek / ... │
└─────────────────────────────────────────────────────────────┘

分层结构(自底向上):

  1. AI 层packages/ai):统一 LLM API,屏蔽不同 provider 的协议差异
  2. Agent 层packages/agent):Agent 运行时,管理状态、工具调用、消息队列
  3. TUI 层packages/tui):终端 UI 组件库,差分渲染、事件处理
  4. 协议层packages/protocol):RPC 协议的 schema 定义
  5. Client 层packages/client):RPC 客户端 SDK
  6. Server 层packages/server):RPC 服务端框架
  7. Coding Agent 层packages/coding-agent):组装所有模块的 CLI 主程序

Monorepo 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pi-monorepo/
├── packages/
│ ├── ai/ # 统一多 provider LLM API
│ ├── agent/ # Agent 运行时核心
│ ├── coding-agent/ # 交互式 coding agent CLI(主程序)
│ ├── tui/ # 终端 UI 库
│ ├── protocol/ # RPC 协议定义
│ ├── client/ # RPC 客户端 SDK
│ ├── server/ # RPC 服务端框架
│ ├── evals/ # 评估基准
│ └── storage/ # 存储后端(sqlite-node 等)
├── scripts/ # 构建、发布、检查脚本
├── .pi/ # Pi 自身配置
├── package.json # Monorepo 根配置
├── tsconfig.json # TypeScript 配置
├── biome.json # Biome linter/formatter 配置
└── vitest.base.ts # 测试配置

构建顺序npm run build 的依赖链):

1
tui → ai → agent → storage/sqlite-node → protocol → client → coding-agent → server

这种依赖链设计非常清晰——底层模块不依赖上层,每一层只依赖其下的层。


核心模块详解

1. coding-agent:交互式 CLI 主程序

这是整个项目的主程序包,将 agent 运行时、TUI、会话管理、配置管理、工具系统等组合为一个完整的交互式 coding agent。

入口文件

src/main.tsmain() 函数是整个 CLI 的入口点。

启动流程概览

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
27
28
29
30
31
32
33
34
35
36
37
main()
├── 1. 初始化
│ ├── resetTimings() // 重置性能计时
│ ├── 合并 builtInExtensions // 注册内置扩展
│ ├── 检查 offline 模式 // --offline 或 PI_OFFLINE
│ └── applyHttpProxySettings() // 应用 HTTP 代理配置

├── 2. 命令处理(短路)
│ ├── handlePackageCommand() // pi install/uninstall/update/...
│ ├── handleConfigCommand() // pi config ...
│ └── runCredentialPrintCommand() // pi credential ...

├── 3. 参数解析
│ ├── parseArgs() // CLI 参数解析
│ └── resolveAppMode() // 确定运行模式(interactive/rpc/print)

├── 4. 迁移与设置
│ ├── runMigrations() // 执行配置迁移
│ └── SettingsManager.create() // 创建设置管理器

├── 5. 会话管理
│ ├── createSessionManager() // 创建/恢复/继续会话
│ └── getMissingSessionCwdIssue() // 检查会话 CWD 一致性

├── 6. 运行时创建
│ ├── createRuntime() // 创建 AgentSessionRuntime
│ │ ├── ModelRuntime // 模型运行时
│ │ ├── ResourceLoader // 资源加载器
│ │ ├── AuthStorage // 认证存储
│ │ ├── ExtensionManager // 扩展管理器
│ │ └── SkillRegistry // 技能注册表
│ └── createAgentSession() // 创建 Agent 会话

└── 7. 运行模式分派
├── runInteractiveMode() // 交互式 TUI 模式
├── runRpcMode() // RPC(JSON-lines)模式
└── runPrintMode() // 单次打印模式

核心子模块

AgentSessionsrc/core/agent-session.ts

编码 agent 的会话对象,封装了:

  • 底层 Agent(来自 packages/agent)实例
  • 系统提示词管理(system prompt)
  • 工具注册(内置工具 + 扩展工具)
  • 模型切换(Ctrl+P 循环)
  • 自动压缩(auto-compaction,当 token 接近上限时自动摘要)
  • 会话事件分发

AgentSessionRuntimesrc/core/agent-session-runtime.ts

与特定 CWD 绑定的运行时环境,包含:

  • ModelRuntime:模型查询、切换、API 兼容性处理
  • SettingsManager:全局/项目/会话级设置
  • ResourceLoader:资源文件(CLAUDE.md 等)加载
  • AuthStorage:认证凭据存储
  • ExtensionManager:扩展加载与管理
  • SkillRegistry:技能(/command)注册

SessionManagersrc/core/session-manager.ts

会话的生命周期管理:

  • 创建新会话(create()
  • 打开已有会话(open()
  • 继续最近会话(continueRecent()
  • Fork 会话(forkSessionOrExit()
  • 列出所有会话(list()listAll()

会话文件存储在 ~/.pi/agent/sessions/<encoded-cwd>/ 下,格式为 JSONL。

运行模式

交互式模式src/modes/interactive/):

  • 使用 TuiMainScreen 进行全屏或滚动渲染
  • 支持键盘快捷键(Ctrl+C、Ctrl+P、Escape 等)
  • 输入编辑器、自动补全、overlay 弹窗
  • 流式输出渲染

RPC 模式src/modes/rpc/):

  • 通过 stdin/stdout 交换 JSON-lines 消息
  • 支持 get_statepromptsteerabortset_model 等命令
  • 可嵌入到 IDE、web 应用等外部宿主

Print 模式src/modes/print/):

  • 非交互式单次执行
  • 输出到 stdout,适合脚本和管道

2. agent:Agent 运行时

Agent 运行时是与 UI 无关的核心引擎,负责:

  • 管理 Agent 状态(消息、工具、模型)
  • 驱动 Agent Loop(LLM 调用 → 工具执行 → 继续)
  • 消息队列(steering queue、follow-up queue)
  • 事件分发
  • 会话持久化抽象

Agent 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Agent {
// 状态
state: AgentState; // 包含 systemPrompt, model, tools, messages, isStreaming 等

// 消息队列
steer(message: AgentMessage): void; // 在当前 turn 结束后注入消息
followUp(message: AgentMessage): void; // 在 agent 即将停止时注入消息

// 主入口
prompt(input: string | AgentMessage): Promise<void>; // 开始一个新 prompt
continue(): Promise<void>; // 继续运行(处理队列)
abort(): void; // 中止当前运行

// 事件
addListener(listener: (event: AgentEvent) => void): void;
// AgentEvent: assistant_start, text_delta, toolcall_start, tool_execution_end, agent_end, ...
}

状态模型(AgentState 接口):

1
2
3
4
5
6
7
8
9
10
11
interface AgentState {
systemPrompt: string; // 系统提示词
model: Model; // 当前模型
thinkingLevel: ThinkingLevel; // 推理等级(off/minimal/low/medium/high/xhigh/max)
tools: AgentTool[]; // 可用工具列表
messages: AgentMessage[]; // 对话历史
readonly isStreaming: boolean; // 是否正在流式输出
readonly streamingMessage?: AgentMessage; // 当前部分消息
readonly pendingToolCalls: ReadonlySet<string>; // 正在执行的工具调用 ID
readonly errorMessage?: string; // 最近的错误消息
}

Agent Loop

Agent Loop 是整个系统的核心循环,位于 src/agent-loop.ts

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
27
28
29
30
31
32
33
34
35
runAgentLoop(context, streamFn, config)

├── 1. 准备上下文
│ ├── transformContext() // 消息转换(过滤自定义消息)
│ ├── convertToLlm() // AgentMessage → LLM Message
│ └── 构建 streamFn 参数

├── 2. 调用 LLM
│ ├── streamFn(model, context) // 调用 LLM API,返回流
│ └── 处理流事件:
│ ├── text_delta // 文本增量
│ ├── thinking_delta // 思考增量
│ ├── toolcall_start/delta/end // 工具调用
│ └── done / error // 结束/错误

├── 3. 工具执行
│ ├── 识别工具调用(从 assistant message 中)
│ ├── prepareToolCall() // 参数准备和验证
│ ├── beforeToolCall() // 工具执行前钩子
│ ├── 并行/串行执行工具 // 根据 executionMode
│ │ ├── tool.execute() // 执行工具
│ │ └── 收集 AgentToolResult
│ ├── afterToolCall() // 工具执行后钩子
│ └── 将工具结果追加到 messages

├── 4. 检查终止条件
│ ├── stopReason === "stop" // LLM 决定停止
│ ├── terminate === true // 工具请求终止
│ ├── 队列消息处理 // drain steering/followUp queue
│ └── prepareNextTurn() // 下一轮准备钩子

└── 5. 继续或结束
├── 有工具结果 → 继续循环
├── 有队列消息 → 注入消息继续
└── 否则 → 结束循环

关键设计决策

  • 工具执行模式:支持 sequential(逐个执行)和 parallel(并行执行)两种模式,每个工具可通过 executionMode 属性覆盖默认行为
  • 消息队列steering 队列在每轮结束后注入,followUp 队列在 agent 即将停止时注入,支持不同的 QueueModeall / one-at-a-time
  • 流式处理:LLM 返回的流通过 AssistantMessageEventStream 处理,支持增量更新 UI

工具接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface AgentTool<TParameters, TDetails> {
name: string; // 工具名称
description: string; // 工具描述(给 LLM 看的)
parameters: TSchema; // JSON Schema 参数定义
label: string; // 人类可读标签(给 UI 看的)
executionMode?: ToolExecutionMode; // 执行模式

// 参数预处理(可选)
prepareArguments?: (args: unknown) => Static<TParameters>;

// 执行函数
execute: (
toolCallId: string,
params: Static<TParameters>,
signal?: AbortSignal,
onUpdate?: AgentToolUpdateCallback<TDetails>,
) => Promise<AgentToolResult<TDetails>>;
}

3. ai:统一多 Provider LLM 接口

AI 层是 LLM 调用的统一抽象层,将不同 provider 的协议差异屏蔽,为上层提供一致的 API。

核心 API

1
2
3
4
5
// 简化的流式调用
streamSimple(model, context, options): AssistantMessageEventStream

// 完整的流式调用
stream(model, context, options): AssistantMessageEventStream

API 实现

每种 API 协议有一个独立的实现文件:

文件 协议 使用的 Provider
src/api/openai-completions.ts OpenAI Chat Completions OpenAI、Azure、Mistral、Groq、Cerebras、DeepSeek、xAI 等
src/api/openai-responses.ts OpenAI Responses API OpenAI (GPT-5 等)
src/api/openai-codex-responses.ts OpenAI Codex Responses Codex
src/api/anthropic.ts Anthropic Messages Anthropic (Claude)
src/api/google.ts Google Gemini Google
src/api/bedrock.ts AWS Bedrock Amazon
src/api/ollama.ts Ollama Ollama (本地)
src/api/github-copilot.ts GitHub Copilot GitHub Copilot

流式处理流程(以 OpenAI Completions 为例)

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
27
28
29
30
31
32
33
stream(model, context, options)

├── 1. 初始化
│ ├── getClientApiKey() // 获取 API Key
│ ├── getCompat() // 获取 provider 兼容性配置
│ ├── createGrammarToolInputProperties() // 语法工具处理
│ ├── resolveCacheRetention() // 缓存保留策略
│ └── createClient() // 创建 OpenAI SDK 客户端

├── 2. 构建请求参数
│ └── buildParams()
│ ├── convertMessages() // 消息格式转换
│ ├── 设置 prompt_cache_key // 缓存键
│ ├── 设置 tools // 工具定义
│ └── 设置 reasoning_effort // 推理等级

├── 3. 发送请求
│ ├── retryProviderRequest() // 带重试的请求
│ └── client.chat.completions.create() // OpenAI SDK 调用

├── 4. 处理流式响应
│ └── for await (chunk of openaiStream)
│ ├── 解析 usage // token 用量
│ ├── 解析 finish_reason // 停止原因
│ ├── 解析 text delta // 文本增量
│ ├── 解析 reasoning delta // 推理增量(thinking)
│ ├── 解析 tool_calls delta // 工具调用增量
│ └── 解析 reasoning_details // 加密推理详情

└── 5. 完成/错误处理
├── finishBlock() // 完成每个内容块
├── stream.push(done) // 推送完成事件
└── stream.end() // 结束流

模型管理

1
2
3
4
5
6
7
interface Model<TApi> {
id: string; // 模型 ID
provider: string; // Provider ID
api: TApi; // API 类型
baseUrl?: string; // 自定义 base URL
headers?: Record<string, string>; // 自定义请求头
}

ModelsRuntime 负责:

  • 内置模型数据(编译时生成)
  • 远程 catalog 更新(运行时刷新)
  • 模型查询和匹配

4. tui:终端 UI 库

TUI 是一个独立的终端 UI 库,提供类似 React 的组件化开发模型,支持差分渲染。

核心接口

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
interface Component {
// 渲染组件为行数组
render(width: number): string[];

// 处理键盘输入(当组件有焦点时)
handleInput?(data: string): void;

// 是否接收按键释放事件(Kitty 协议)
wantsKeyRelease?: boolean;

// 使缓存失效(主题变化等)
invalidate(): void;
}

interface TUI extends Component {
children: Component[]; // 子组件
terminal: Terminal; // 终端抽象

// 组件管理
addChild(component: Component): void;
removeChild(component: Component): void;
clear(): void;

// 焦点管理
setFocus(component: Component | null): void;

// Overlay(弹窗/浮层)
showOverlay(component: Component, options?: OverlayOptions): OverlayHandle;
hideOverlay(): void;

// 生命周期
start(): void;
stop(): void;

// 渲染
requestRender(force?: boolean): void;

// 输入监听
addInputListener(listener: TuiInputListener): () => void;

// 终端颜色
queryTerminalBackgroundColor(options): Promise<RgbColor | undefined>;
queryTerminalColorScheme(options): Promise<TerminalColorScheme | undefined>;
}

TUI 实现层次

1
2
3
4
TUI (interface)
└── TuiBase (abstract class, extends Container)
├── TuiMainScreen // 主屏幕模式(终端滚动缓冲区)
└── TuiAltScreen // 替代屏幕模式(全屏,应用控制视口)

TuiMainScreen

  • 使用终端的主屏幕(normal screen buffer)
  • 输出追加到终端滚动缓冲区
  • 支持差分渲染:只重绘变化的行
  • 适用于常规 chat 模式

TuiAltScreen

  • 使用终端的替代屏幕(alternate screen buffer,ESC[?1049h
  • 应用完全控制视口内容
  • 支持滚动、鼠标事件、文本选择
  • 适用于全屏模式

差分渲染机制

TUI 的核心性能优化是差分渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
requestRender(force?)

├── 1. 收集所有子组件的 render() 输出
│ └── 合并为完整的文档行数组

├── 2. 与上一帧对比
│ ├── 找到变化的行范围
│ └── 处理 Kitty 图片(特殊协议)

├── 3. 生成终端指令
│ ├── 移动光标到变化区域
│ ├── 输出变化的行
│ └── 清理多余行

└── 4. 更新缓存状态
├── lastDocument = newDocument
└── fullRedraws++(如果是强制重绘)

Overlay 系统

Overlay 是 TUI 的浮层/弹窗机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface OverlayOptions {
width?: SizeValue; // 宽度(数字或百分比)
maxHeight?: SizeValue; // 最大高度
anchor?: OverlayAnchor; // 锚点(center/top-left/...)
offsetX?: number; // 水平偏移
offsetY?: number; // 垂直偏移
margin?: OverlayMargin | number; // 边距
visible?: (w, h) => boolean; // 可见性条件
nonCapturing?: boolean; // 是否不捕获焦点
}

interface OverlayHandle {
hide(): void; // 永久隐藏
setHidden(hidden: boolean): void; // 临时隐藏/显示
focus(): void; // 获取焦点
unfocus(options?): void; // 释放焦点
isFocused(): boolean; // 是否获得焦点
}

5. server / client / protocol

这三个包提供了 RPC(远程过程调用) 能力,允许外部程序通过 JSON-lines 协议控制 agent。

  • protocol:定义了 RPC 协议的消息格式(请求、响应、事件)
  • server:RPC 服务端框架(PiSessionBackendPiSessionRuntimeLiveSession
  • client:RPC 客户端 SDK(PiClientRemoteSession,支持自动重连、状态同步)

工具系统

内置工具

coding-agent 提供了一组内置工具(packages/agent/src/harness/tools/):

工具 文件 功能
Bash bash.ts 执行 shell 命令
Edit edit.ts 文本替换编辑
Read read.ts 读取文件内容
Write write.ts 写入文件
Find find.ts 文件搜索(按名称/glob)
Grep grep.ts 内容搜索(正则表达式)
Ls ls.ts 列出目录内容
NotebookEdit notebook-edit.ts Jupyter notebook 编辑
TodoRead todo-read.ts 读取待办事项
TodoWrite todo-write.ts 写入待办事项

Edit 工具的模糊匹配

Edit 工具(packages/agent/src/harness/tools/edit-diff.ts)实现了模糊匹配机制:

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

├── 1. 尝试精确匹配
│ └── content.indexOf(oldText)

├── 2. 如果失败,尝试模糊匹配
│ ├── normalizeForFuzzyMatch(content)
│ │ ├── 标准化 Unicode 引号
│ │ ├── 标准化 Unicode 破折号
│ │ ├── 标准化特殊空格
│ │ └── 去除行尾空白
│ └── fuzzyContent.indexOf(fuzzyOldText)

├── 3. 检查重复匹配
│ └── countOccurrences() > 1 → 报错

├── 4. 检查重叠
│ └── 排序后检查相邻 edit 是否重叠

└── 5. 应用替换
├── 精确匹配:直接替换
└── 模糊匹配:保留原始行的未变部分

这个设计很巧妙——AI 生成的代码经常会有 Unicode 引号(如 "")或行尾空白不一致的问题,模糊匹配能有效解决这些场景。


会话管理与持久化

会话文件格式

会话文件采用 JSONL 格式(每行一个 JSON 对象),存储在 ~/.pi/agent/sessions/<encoded-cwd>/ 下。

文件命名:<timestamp>_<session-id>.jsonl

1
2
3
4
5
{"type":"session","version":3,"id":"<uuid>","timestamp":"2026-08-02T...","cwd":"/path/to/project"}
{"type":"message","id":"msg_001","message":{"role":"user","content":[{"type":"text","text":"Hello"}]}}
{"type":"message","id":"msg_002","message":{"role":"assistant","content":[{"type":"text","text":"Hi!"}]}}
{"type":"label","id":"lbl_001","targetId":"msg_002","label":"Greeting"}
{"type":"compaction","id":"cmp_001","usage":{"input":1000,"output":200,...}}

条目类型

类型 说明
session 会话头(必须第一行)
message 对话消息
label 消息标签(可引用其他条目)
compaction 压缩摘要记录
branch_summary 分支摘要
session_info 会话元信息(名称等)

自动压缩

当 token 使用接近上下文窗口限制时,系统会自动触发压缩

1
2
3
4
5
6
7
8
9
10
11
12
检查 token 使用量

├── 如果超过阈值:
│ ├── 保留最近 N 条消息
│ ├── 将早期消息摘要为压缩文本
│ ├── 将摘要作为新的 system prompt 前缀
│ └── 记录 compaction 条目

└── 设置:
├── compaction.enabled(默认 true
├── compaction.reserveTokens(默认 16384
└── compaction.keepRecentTokens(默认 20000

配置系统

三级配置合并

1
2
3
4
5
全局设置 (~/.pi/settings.json)
↓ 合并
项目设置 (<project>/.pi/settings.json)
↓ 合并
运行时覆盖 (CLI 参数 / 会话内设置)

合并策略:深度合并(deepMergeSettings),嵌套对象递归合并,后者覆盖前者。

主要配置项

配置项 类型 默认值 说明
defaultProvider string - 默认 LLM provider
defaultModel string - 默认模型
defaultThinkingLevel ThinkingLevel “off” 默认推理等级
transport TransportSetting “auto” 传输协议
steeringMode “all” | “one-at-a-time” - 转向队列模式
followUpMode “all” | “one-at-a-time” - 后续队列模式
theme string - UI 主题
uiMode “regular” | “fullscreen” “regular” UI 模式
compaction.enabled boolean true 是否启用自动压缩
compaction.reserveTokens number 16384 预留 token 数
compaction.keepRecentTokens number 20000 保留最近消息的 token 数
terminal.showImages boolean true 是否显示图片
httpProxy string - HTTP 代理 URL
packages PackageSource[] - npm/git 包源
extensions string[] - 扩展文件路径
skills string[] - 技能文件路径
enabledModels string[] - 可循环的模型模式

扩展机制

Extensions(扩展)

扩展是 TypeScript 文件,可以:

  • 注册新工具
  • 添加 UI 组件(overlay)
  • 修改系统提示词
  • 注册新的 LLM provider
  • 添加自定义消息类型
1
2
3
4
5
6
7
interface Extension {
name: string;
tools?: AgentTool[];
uiComponents?: UIComponent[];
systemPrompt?: string;
providers?: Provider[];
}

扩展加载顺序

  1. 内置扩展(builtInExtensions
  2. Settings 中 extensions 配置的扩展
  3. CLI --extensions 参数指定的扩展

Skills(技能)

技能是可以通过 /skill:name 命令调用的功能模块:

  • 技能文件可以是 TypeScript 或 Markdown
  • 支持参数传递
  • settings.skills 中配置路径

Prompt Templates(提示词模板)

可复用的提示词模板:

  • 存储在 settings.prompts 指定的路径
  • 支持变量替换
  • 通过 /prompt 命令或 API 调用

Themes(主题)

UI 主题定制:

  • 定义颜色、样式
  • 存储在 settings.themes 指定的路径
  • 通过 settings.theme 激活

关键数据流

用户输入到 LLM 响应的完整流程

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
27
28
29
30
31
32
33
34
35
36
37
38
用户输入 "帮我写一个函数"

├── TUI 层
│ ├── 键盘事件 → InputHandler
│ ├── 输入编辑器收集文本
│ └── 按 Enter 提交

├── AgentSession
│ ├── 创建 UserMessage
│ ├── 追加到 state.messages
│ └── 调用 agent.prompt()

├── Agent
│ ├── 将消息放入队列
│ └── 启动 Agent Loop

├── Agent Loop
│ ├── convertToLlm(messages) // AgentMessage[] → Message[]
│ ├── transformContext() // 消息转换
│ └── streamFn(model, context) // 调用 LLM

├── AI 层
│ ├── 选择 API 实现(如 openai-completions)
│ ├── buildParams() // 构建请求参数
│ ├── client.chat.completions.create() // HTTP 请求
│ └── 返回 AssistantMessageEventStream

├── 流处理
│ ├── text_delta → AgentEvent → TUI 渲染
│ ├── toolcall_start → 工具准备
│ ├── tool_execution → 工具运行
│ ├── tool_result → 追加到 messages
│ └── done → 检查是否继续

└── 输出渲染
├── 文本增量 → 差分渲染到终端
├── 工具调用 → overlay/状态更新
└── 完成 → 恢复输入

关键文件索引

入口文件

文件 说明
packages/coding-agent/src/main.ts CLI 主入口(main() 函数)
packages/coding-agent/src/cli.ts CLI 分派入口
packages/coding-agent/src/rpc-entry.ts RPC 模式入口

核心模块

文件 说明
packages/agent/src/agent.ts Agent 核心类
packages/agent/src/agent-loop.ts Agent Loop 实现
packages/agent/src/types.ts Agent 类型定义
packages/coding-agent/src/core/agent-session.ts AgentSession 实现
packages/coding-agent/src/core/agent-session-runtime.ts AgentSessionRuntime
packages/coding-agent/src/core/session-manager.ts SessionManager
packages/coding-agent/src/core/settings-manager.ts SettingsManager
packages/coding-agent/src/core/model-runtime.ts ModelRuntime

AI 层

文件 说明
packages/ai/src/index.ts AI 包入口
packages/ai/src/api/openai-completions.ts OpenAI Completions API
packages/ai/src/api/anthropic.ts Anthropic Messages API
packages/ai/src/api/google.ts Google Gemini API
packages/ai/src/models.ts Model 类型和管理
packages/ai/src/models-runtime.ts ModelsRuntime

TUI 层

文件 说明
packages/tui/src/tui.ts TUI 接口和基础类
packages/tui/src/tui-main-screen.ts 主屏幕 TUI
packages/tui/src/tui-alt-screen.ts 替代屏幕 TUI
packages/tui/src/terminal.ts 终端抽象

工具

文件 说明
packages/agent/src/harness/tools/bash.ts Bash 工具
packages/agent/src/harness/tools/edit.ts Edit 工具
packages/agent/src/harness/tools/edit-diff.ts Edit 模糊匹配算法
packages/agent/src/harness/tools/read.ts Read 工具
packages/agent/src/harness/tools/find.ts Find 工具
packages/agent/src/harness/tools/grep.ts Grep 工具

总结

Pi Agent Harness 是一个工程质量极高的 TypeScript Monorepo 项目,几个亮点:

  1. 模块化极致:7 个独立 npm 包,每一层职责清晰
  2. 差分渲染 TUI:独立的终端 UI 库,性能优化到位
  3. 模糊匹配 Edit:解决 AI 生成代码的 Unicode 引号/空白问题
  4. RPC 模式:让 agent 可以被嵌入到 IDE、Web 应用等外部宿主
  5. 三级配置合并:全局 + 项目 + 运行时的灵活配置
  6. 会话 Fork 与分支:支持树形会话结构
  7. 供应链安全:pin 依赖、shrinkwrap、lifecycle script 审计

对于想要用 TypeScript 构建 AI Agent 工具的开发者来说,Pi 的架构设计(尤其是分层结构、Agent Loop、TUI 差分渲染、RPC 模式)非常值得学习。


项目地址https://github.com/earendil-works/pi-monorepo
许可证:开源


Pi Agent Harness 源码解析:TypeScript 实现的模块化 Coding Agent
https://tingfeng347.github.io/2026/08/02/Pi Agent Harness 源码解析:TypeScript 实现的模块化 Coding Agent/
作者
Tingfeng
发布于
2026年8月2日
许可协议