Skip to content

4.3 monorepo 与 package 地图 ​

本页分析版本earendil-works/pi@16787ad2026-09-21

本章解决什么问题:Pi 仓库里有哪些 package、各自负责什么、谁依赖谁——给你一张读源码前必备的地图。 前置知识:1.3 npm、package.json 与项目结构(monorepo 与 workspaces 的概念在那里首次讲解)。 学习目标:① 说出各个 package 的职责,分清主线包与实验性的包;② 画出它们的依赖方向;③ 知道每个 package 大致的体量与阅读优先级。

workspaces:一个仓库,多个包 ​

Pi 根目录的 package.json 用 npm workspaces 声明了这是一个 monorepo(源码事实):

package.json · workspaces
earendil-works/pi@16787ad第 5–13 行在 GitHub 查看 ↗
根 package.json 的 workspaces 字段:packages/* 加上 session-backends 子目录与若干示例扩展目录。

npm install 在根目录执行一次,就会为所有子包统一安装依赖;子包之间通过包名互相引用(例如 coding-agent 依赖 @earendil-works/pi-agent-core),npm 会把它们链接到本地目录而不是从 registry 下载。

package 一览 ​

锁定版本下共有 12 个 package:packages/ 下除 session-backends 以外的 11 个目录各是一个包,再加上嵌套一层的 packages/session-backends/sqlite-node(session-backends 目录本身只是分组,不是包)。以下体量为 src/ 目录去除测试后的实际统计(源码事实,用 wc -l 统计 .ts 文件)。按与本书主线的关系分成两组,先看主线包和它们直接依赖的底层包:

packagenpm 包名职责体量(行)本书章节
packages/ai@earendil-works/pi-ai统一多 Provider 的 LLM API:消息类型、流式事件、41 个内置 Provider 适配(按 builtinProviders() 的条目数)、OAuth 登录≈25,1006.1 / 6.2
packages/agent@earendil-works/pi-agent-coreAgent 运行时:Agent Loop、工具执行、持久化的 AgentHarness 与 Session 抽象、Compaction 通用实现≈33,5006.3–6.6
packages/coding-agent@earendil-works/pi-coding-agent产品层:pi 命令本体。CLI、TUI 交互模式、RPC/JSON 模式、SDK、扩展/技能/主题系统、内置工具≈74,800第五、六、七部分
packages/tui@earendil-works/pi-tui终端 UI 库:组件模型与差分渲染(differential rendering),另有全屏(alternate screen)渲染器≈18,3006.9
packages/telemetry@earendil-works/pi-telemetry与厂商无关的遥测(telemetry)接口约定:span、事件与带类型的 schema,本身不带任何上报后端≈900不深入

再看其余七个。它们大多服务于一套官方标为 experimental(实验性)的远程会话架构,本书只在必要处提到:

packagenpm 包名职责体量(行)本书章节
packages/chord@earendil-works/chord通用的应用组装运行时:插件、服务、可复制状态与远程服务边界。README 说明它不依赖任何 Pi 包,也不算 Pi 专用的包≈7,600不深入
packages/protocol@earendil-works/pi-protocol远程 pi 会话的传输中立协议:CBOR 编码与字节流分帧≈900不深入
packages/client@earendil-works/pi-client上述协议的客户端≈1,100不深入
packages/server@earendil-works/pi-server实验性本机服务端:把请求路由到持久化的 Session 与 AgentHarness(包描述写明 experimental)≈2,0006.10 附带
packages/durable@earendil-works/pi-durable持久化的对话、任务与文档运行时(README 称为 Pico runtime),目前只公开记录约定与内存存储实现≈800不深入
packages/session-backends/sqlite-node@earendil-works/pi-session-backend-sqlite-node基于 Node 内置 node:sqlite 的 Session 存储后端≈2,0006.5 附带
packages/evals@earendil-works/pi-evals评测脚本集(private,不发布到 npm)≈1,400不深入
🌱 初学者提示为什么要分包
分包的边界就是职责的边界:ai 不知道什么是工具执行,agent 不知道什么是终端渲染,tui 甚至完全不知道 LLM 的存在。这让每一层都能被单独理解、单独测试、单独替换——第三方 Web UI 能存在,正是因为「界面层」与「Harness 层」被协议隔开。这也是你读源码时的导航原则:想找什么概念,先想它属于哪一层。

依赖方向图 ​

依赖关系全部来自各包 package.json 的 dependencies 字段(源码事实):

图 4.3-1 package 依赖方向
箭头 A → B 表示「A 依赖 B」,箭头指向的一方更基础:pi-tui、pi-telemetry 与 chord 不依赖任何本仓库的包;pi-ai 只依赖 pi-telemetry;pi-coding-agent 是主线的汇聚点。虚线为 devDependencies(pi-coding-agent 还以 dev 依赖引用 pi-protocol,为免线条过密未画出)。

读图要点:

  1. 依赖只朝一个方向流动,没有环。ai 永远不会 import agent,agent 永远不会 import coding-agent。你在下层包里看到的任何符号,都可以放心地在不了解上层的情况下理解。
  2. tui 与 ai 互不相识——终端渲染和模型调用是两个完全独立的世界,在 coding-agent 里才被组装到一起。
  3. 主线只有一条:pi-ai → pi-agent-core → pi-coding-agent,加上旁边的 pi-tui。pi-telemetry 与 chord 是被主线包依赖的底层积木;protocol、client、server、durable 属于实验性的远程会话架构。从源码结构看,coding-agent 里用到 chord、protocol、client、server 的代码都集中在 src/experimental/ 与 src/cli/experimental/ 下,pi-durable 目前没有被其他包引用。
  4. bin 字段揭示了命令入口(源码事实):pi 命令来自 coding-agent 的 dist/bundle/cli.js。
earendil-works/pi@16787ad第 9–11 行在 GitHub 查看 ↗
pi 命令的 bin 声明:全局安装后,敲 pi 实际执行的是这个包的 dist/bundle/cli.js——构建时打包出来的启动器。

每个包里有什么 ​

按第五部分将要走的请求路径,预览各包内部的关键目录:

text
packages/
├── ai/src/
│   ├── types.ts            ← 统一消息与事件类型(全书引用最多的文件)
│   ├── models.ts           ← Models 集合:stream/complete 等对外 API
│   ├── api/                ← 各协议实现(anthropic-messages、openai-*、…)
│   ├── providers/          ← 40 余个内置 Provider 的注册与模型清单
│   ├── auth/               ← API Key / OAuth 凭据
│   └── utils/event-stream.ts ← 流式事件的缓冲与分发
├── agent/src/
│   ├── agent-loop.ts       ← Agent Loop 本体(6.3 的主角)
│   ├── agent.ts            ← 有状态的 Agent 类
│   └── harness/            ← AgentHarness、session/、tools/、compaction/、runtime/
├── coding-agent/src/
│   ├── cli.ts → main.ts    ← 启动入口(5.1 的主角)
│   ├── core/               ← AgentSession、工具、扩展、技能、设置、compaction
│   ├── modes/interactive/  ← TUI 交互模式
│   ├── modes/rpc/          ← RPC/JSON 模式
│   └── extensions/         ← 内置扩展
├── tui/src/                ← Component 模型、差分渲染、编辑器组件、全屏渲染器
├── telemetry/src/          ← 遥测接口约定与内存参考实现
├── chord/ protocol/ client/ server/ durable/  ← 实验性远程会话架构
├── session-backends/sqlite-node/ ← Session 存储的 SQLite 实现
└── evals/                  ← 评测

不要现在就试图记住所有文件——这张目录树是后续章节的路标,每次深入一个模块时回来对一下位置即可。

哪些先读、哪些跳过 ​

按学习价值排序(分析解释,依据是依赖层级与第五部分的主链路):

  1. 必读主线:ai/src/types.ts → agent/src/agent-loop.ts → coding-agent/src/main.ts + core/agent-session.ts。第五部分沿这条线走。
  2. 按需深入:ai/src/api/anthropic-messages.ts(看一个 Provider 如何实现协议)、coding-agent/src/core/tools/、core/extensions/。
  3. 暂时不读:ai/src/providers/ 下几十个 Provider 文件(结构高度重复,看懂一个即可)、*.generated.ts(脚本生成的模型数据,不是手写代码)、evals/、tui/ 的细节(除非你对终端渲染感兴趣),以及 chord、protocol、client、server、durable 这组实验性的包。

实践任务 ​

🛠 实践任务亲手验证依赖图

目标:不相信书,自己从 package.json 里把依赖图挖出来。

步骤:在 Pi 仓库根目录运行:

for p in ai agent coding-agent tui server; do
  echo "== $p =="
  node -e "const d=require('./packages/$p/package.json'); console.log(Object.keys(d.dependencies||{}).filter(x=>x.startsWith('@earendil')))"
done

预期现象:输出与图 4.3-1 的实线箭头一一对应(tui 为空数组;ai 只依赖 pi-telemetry;agent 依赖 chord、pi-ai、pi-telemetry;coding-agent 依赖 chord、pi-agent-core、pi-ai、pi-tui 四个;server 依赖 chord、pi-agent-core、pi-protocol)。

如何判断成功:你能解释为什么 SQLite 会话后端依赖 agent 而不是反过来。

常见错误:想顺手查 SQLite 后端时忘了它的路径多一层(packages/session-backends/sqlite-node)。

本章小结 ​

  • Pi 是 npm workspaces monorepo:12 个 package,依赖单向流动、无环;其中一组(chord、protocol、client、server、durable)服务于实验性的远程会话架构。
  • 三个核心层:pi-ai(模型接口)→ pi-agent-core(Agent 运行时)→ pi-coding-agent(产品);pi-tui 独立负责渲染。
  • coding-agent 体量最大(≈7.5 万行),但主链路入口清晰:cli.ts → main.ts。
  • Provider 文件与 generated 文件可以跳过不读。
  • 关键术语:workspaces、依赖方向、bin 入口。
  • 自测问题:① pi-tui 为什么不依赖 pi-ai?② pi 命令对应哪个文件?③ 想看 Agent Loop 应该去哪个包?
  • 下一章:4.4 从哪里开始读源码。
✅ 自测问题参考答案先自己回答,再点开对照
  1. 因为 pi-tui 是一个纯终端 UI 库——组件模型加差分渲染,它的工作只是把内容画到终端上,完全不需要知道 LLM 的存在。终端渲染和模型调用是两个互不相识的世界,它们只在 pi-coding-agent 这一层才被组装到一起。这也保证了依赖单向流动、无环:越基础的包知道的越少(pi-tui 不依赖本仓库的任何包,pi-ai 也只依赖同样处在底层的 pi-telemetry)。
  2. 对应 packages/coding-agent——它的 package.json 里 bin 字段把命令名 pi 映射到 dist/bundle/cli.js,源码入口是 packages/coding-agent/src/cli.ts(它再转到 main.ts,那是 5.1 的主角)。注意 bin 指向的是 dist 而不是 src:用户运行的是编译、再打包后的 JavaScript。
  3. 去 packages/agent(包名 @earendil-works/pi-agent-core),本体是 packages/agent/src/agent-loop.ts,6.3 会精读它。判断方法就是那条导航原则——先想这个概念属于哪一层:Agent Loop 属于 Agent 运行时,既不属于模型接口层(ai),也不属于产品层(coding-agent)。

本书分析的 Pi 版本:earendil-works/pi@16787ad(2026-09-21)