主要技术:Node.js、Pi、JSONL
项目目录
项目简介
Pi 是一套基于 TypeScript、运行于 Node.js 的可扩展编程智能体。它以读取、编辑文件和执行命令为基础,接收仓库中的开发任务,调用模型选择工具,再把执行结果交给模型继续处理。
开发者可以让它阅读项目、修改组件、根据编译错误调整代码,也可以返回旧会话节点尝试另一种方案。项目提供终端、脚本输出、RPC 和 SDK 入口;团队的工具、命令和请求前处理可通过 Extension 接入,核心没有预设完整的 Plan Mode 或子智能体工作流。
项目亮点
- 模型、循环与会话分层:
pi-ai适配不同模型的消息和流,pi-agent-core执行工具闭环,pi-coding-agent负责仓库上下文、会话与交互,宿主可以分别复用这些能力。 - 追加式会话树:
JSONL条目通过id、parentId记录分支,模型只读取当前路径;切换方案时可选择携带分支摘要,原讨论仍保留。会话分支不会回滚磁盘代码。 - 历史与模型输入分开: 压缩条目提供摘要及近期保留边界,
context_edit可替换或省略旧条目的模型输入贡献,两者都不改写原始历史。 - 运行时扩展与消息钩子:
Extension可注册工具、命令并处理调用前后事件。普通context与完整context_with_system分别处理对话和含系统消息的请求;活动工具集合只控制模型声明,不构成沙箱。
技术架构
任务执行: 编程会话加载项目资源和当前消息,Agent 循环经模型适配层发起请求,按工具调用执行文件或命令操作,将结果及事件返回会话与界面。
会话与定制: Session 管理器追加历史,并按当前分支、压缩和编辑记录投影上下文;扩展加载器接入工具、事件和界面定制。终端、RPC 与 SDK 共用这些运行能力。
| 代码位置 | 主要职责 |
|---|---|
packages/ai/ |
模型定义、供应商协议与流式消息 |
packages/agent/src/agent-loop.ts |
模型请求、工具执行与结果回填 |
packages/coding-agent/src/core/session-manager.ts |
会话树、上下文投影和追加记录 |
packages/coding-agent/src/core/compaction/、packages/coding-agent/src/core/extensions/ |
压缩及运行时扩展 |
packages/coding-agent/src/modes/、packages/tui/ |
交互、RPC 等运行入口与终端组件 |
如何写进简历
适配通 · 工业设备 SDK 升级助手 | 工业软件场景示例
项目描述:面向设备接入软件研发的 SDK 升级助手,结合版本说明、旧接口调用和编译日志推进适配改码,保留不同迁移方案的讨论与工具记录,交付补丁和检查结果。
技术栈:TypeScript、Node.js、Pi、JSONL
主要工作:
- SDK 嵌入与适配工具接入:通过
createAgentSession()指定接入代码目录,以Pi Extension接入 SDK 资料查询和项目编译入口;将版本差异、旧调用位置与诊断输出送入工具循环,衔接分析和适配改码。 - 迁移方案的分支会话:利用带
id/parentId的JSONL会话树记录保留兼容层、直接替换接口等不同尝试;按活动分支重建模型上下文,保留各方案的讨论、工具结果与分支摘要。 - 编译日志投影与原始历史保留:结合压缩摘要和近期消息构建迁移上下文,通过追加
context_edit省略或替换已处理的长编译输出;模型输入按当前分支投影,原始条目继续保存在JSONL中供回查。 - 检查阶段的工具暴露控制:编写检查阶段扩展,通过
setActiveTools()切换声明给模型的工具集合,在context钩子补充阶段要求;退出检查时恢复原集合,使接口阅读与实际改码按阶段推进。
前端搭档 · 在线课程界面维护助手 | 在线教育场景示例
项目描述:面向在线教育研发团队的课程界面维护助手,接收播放器和课程导航需求,调用代码工具与项目检查入口推进修改,保留界面方案和执行过程以便审查补丁。
技术栈:TypeScript、Node.js、Pi、JSONL
主要工作:
- SDK 接入与工具结果回填:将
Pi SDK嵌入课程维护入口,按工程目录接入代码工具和项目检查命令;以toolCallId关联启动、增量输出与终态事件,把参数校验或执行异常转为isError结果交给模型继续修正。 - 界面方案与分支上下文:以
JSONL会话树分别记录播放器交互和章节导航方案,选定分支后只沿活动路径组织模型输入;结合分支摘要承接必要背景,减少其他尝试对当前修改的干扰。 - 长任务压缩与输入投影:利用压缩摘要整理近期交互要求和待完成项,再以追加式
context_edit省略过期代码及检查输出;后续请求使用编辑后的投影,原始工具记录仍可回查。 - 运行中更正与后续任务调度:区分
steer和followUp输入,把当前修改的更正放到助手回合及工具完成后处理,将后续需求排到当前运行结束;协调播放器修改中的临时反馈与下一轮任务。
面试题与回答
3 题Pi Coding Agent 如何设计?
展开收起
核心回答
Pi Coding Agent是基于TypeScript、运行于Node.js的可组合编程助手。它把模型适配、Agent 运行循环和编程会话分开:pi-ai统一模型消息与流式协议,pi-agent-core组织模型请求、工具执行和结果回填,pi-coding-agent在上面提供仓库工具、会话持久化、分支、压缩及扩展能力。一次任务中,模型根据当前上下文提出工具调用,运行时执行工具并回填结果,再请求模型决定下一步。长任务的历史保存为会话树,模型只读取活动分支经过投影和压缩后的内容;团队需要的工具、提示词和运行规则可通过
Extension接入。这使核心循环可以复用,终端、RPC和SDK则承担不同的交互与集成方式。各层怎样分工?
- 模型适配层
pi-ai: 提供模型描述、结构化消息、工具声明、流式事件和最终响应,再由供应商适配器处理实际请求与响应协议。上层不用逐家解析流,但不同模型的能力、错误和调用质量仍可能不同。 - 运行循环
pi-agent-core: 管理当前消息、可用工具与运行状态;调用模型、执行工具、追加结果,并处理用户插入的新指令、后续消息和中断。它不等于会话文件管理器。 - 编程会话层
pi-coding-agent: 组合项目上下文、读写与命令工具、模型设置和资源加载,把消息写入 Session,提供树导航、压缩和 Extension。 - 交互层:
pi-tui提供终端组件,交互模式展示运行事件;RPC通过标准输入输出交换JSONL,SDK则让 Node.js 程序直接创建和控制会话。
模型接口、循环和会话可分别复用,产品入口也共用会话与运行机制。
一次请求怎样完成工具闭环?
以“读取 README 并概括项目”为例,第一次模型请求携带用户消息和
read工具定义。模型返回带调用 ID 的工具调用,循环按工具名找到实现、处理参数并执行,生成带相同 ID 的toolResult。下一次请求加入这个结果,模型才能根据真正读到的文件回答。这段闭环有两个关键边界:工具调用表示模型想做什么,工具结果才表示执行观察到了什么;模型停止生成也不等于开发任务已经验收。 文件修改是否正确、测试是否覆盖需求,仍要根据真实文件和命令结果判断。
运行循环把工具结果追加回上下文,再判断是否需要下一次请求。模型错误或中断会结束当前循环;输出长度达到限制时,可能不完整的工具调用参数会被转成失败结果,避免直接执行半截调用。
流式文本、工具开始和结束等事件用于驱动界面和宿主程序;Session 中的消息、调用和结果用于后续重建。不能只保存屏幕上的文本增量,却丢掉工具调用 ID、错误和完整结果。
为什么会话历史和模型输入要分开?
Pi 的持久 Session 是追加式
JSONL文件。消息、模型切换、摘要等条目都有id和parentId,形成会话树;当前leaf表示选中的位置。返回旧节点后继续追加,就能保留方案 A,再尝试方案 B,而不用覆盖原来的记录。模型输入从当前叶子沿父指针回到根,只采用这条活动路径。切换分支时,可以选择将离开路径到共同祖先之间的内容生成分支摘要,并挂到目标位置;没有选择摘要时,其他分支的发现不会自动全部带过来。会话导航改变的是对话路径,不会把工作目录中的文件一并回滚。
上下文投影进一步处理压缩和上下文编辑:原始记录保留,模型看到的是当前路径中实际有效的消息。
context_edit可以追加一条编辑记录,替换或省略旧消息对模型输入的贡献,而不改写原始消息。这便于保留历史依据,同时减少过期或无用内容对后续判断的影响。长任务怎样压缩?
Pi 结合最近一次模型用量和后续新增消息估算上下文,当接近模型窗口预留边界时触发压缩,也支持手动压缩。它将摘要及保留起点写成新的
compaction条目,后续请求采用摘要、近期保留消息和新增消息,旧记录仍留在会话文件中。切分时要维护工具调用与结果的配对关系,不能为了达到一个 Token 数就从工具结果中间截断。较大的交互可能需要单独总结前半段,同时保留后半段继续执行。压缩准备基于模型可见的投影选择这些范围;上下文编辑或压缩使旧用量失效后,也需要重新估算。
压缩摘要和分支摘要解决的问题不同:前者减少当前路径的输入,后者把离开分支的有用发现带到新位置。两者都有信息损失风险;精确错误、关键约束和文件状态应能回到原记录或实际环境核对。
Extension 能怎样接入运行过程?
Extension是运行在 Pi 进程内的 TypeScript 模块,可以注册工具、命令及事件处理器。例如,在运行开始前调整提示词,在工具调用前返回阻止结果,在工具返回后整理内容,或定制分支摘要和压缩行为。请求前的上下文钩子有两层:普通
context处理对话消息,Pi 会恢复系统提示与工具声明;context_with_system处理包含系统消息的完整输入,扩展要自己维护提示词和工具声明。这些请求时的变换不会自动改写持久历史。扩展状态同样要区分用途:
custom条目用于保存程序状态,默认不进入模型上下文;custom_message才用于注入模型需要知道的内容。只需要提供操作知识时可以使用Skill,不必写成扩展代码;需要运行时执行和拦截时再使用 Extension。这种设计把工作流定制留给扩展,但扩展与工具仍受所在进程的权限影响。钩子拦截、项目信任和操作系统隔离各有职责,不能把“支持扩展”当成默认沙箱或完整权限保证。
- 模型适配层
压缩多轮对话后,如何拼接用户消息、模型消息与摘要?
展开收起
Pi 1.4.0将会话保存为带id、parentId的条目树。模型输入由当前分支投影生成:当前路径上的最新压缩摘要+保留的近期条目+压缩后的新增条目,并应用该路径上的上下文编辑。先选路径,再应用压缩边界
buildSessionPath()从当前叶子沿parentId回到根,再反转为执行顺序;它不会遍历所有兄弟分支。buildContextEntries()找到这条路径上最新的compaction,把该条目放在最前,然后接上:- 从
firstKeptEntryId开始、位于压缩条目之前的保留内容;这段中的旧system消息会被排除。 - 压缩条目之后、本分支新增的内容。
压缩条目保存了
systemMessage时,投影先恢复这份提示与工具状态快照,再放摘要。即使较旧的压缩条目仍落在保留范围里,也不会再次注入旧摘要,因此不会出现多份历史摘要层层堆叠。保留原角色,摘要明确标记为历史
普通消息保留
user、assistant、toolResult等角色。内部的compactionSummary经过convertToLlm()转成user角色的文本消息,并带有明确的“此前历史已压缩为以下摘要”前缀与摘要标签。这里使用user作为兼容接口的载体,不表示摘要是用户刚发出的新要求。默认压缩准备会避开工具结果作为切分起点,保留它前面的模型工具调用;若一个用户请求本身过长而需要从中间切开,则另做该请求前半段的摘要,接上后半段消息,而不是硬保留整个超长请求。
上下文编辑不改写原始会话树
context_edit通过目标条目 ID 替换其上下文content,或用replacement: null使它不进入模型输入;同一目标采用当前有效范围中最后一次编辑。原始条目、角色与其他元数据继续留在记录里,编辑只影响投影。因此,界面和历史工具能看到的完整过程,不等于模型这一轮收到的内容。扩展省略工具调用或结果时仍需维护对应关系;投影机制本身不意味着任何任意删改都符合提供商消息要求。切换会话分支也只改变对话路径,磁盘文件或外部动作需要另行核对,不能视为已经回滚。
- 从
展示 Pi 的自定义 Extension 配置。
展开收起
核心回答
可以用一个
/inspect扩展展示完整配置:默认导出函数接收ExtensionAPI,注册命令切换检查工具集,再注册context事件处理器,为当轮模型输入补一条阶段说明。下面示例采用@earendil-works/pi-coding-agent 1.4.0的接口,/inspect off会恢复进入前的工具集合。扩展入口与加载方式。最小扩展文件
将文件保存为项目中的
.pi/extensions/inspect.ts:import type { UserMessage } from "@earendil-works/pi-ai"; import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; export default function inspectExtension(pi: ExtensionAPI) { let previousTools: string[] | undefined; const upgradeTask = { targetVersion: "3.0(示例)", compatibility: "保留旧接口入口;列出必须修改的调用点" }; pi.registerCommand("inspect", { description: "切换检查阶段;/inspect off 恢复原工具集", handler: async (args, ctx) => { if (args.trim() === "off") { if (previousTools) pi.setActiveTools(previousTools); previousTools = undefined; ctx.ui.notify("已退出检查阶段", "info"); return; } previousTools ??= pi.getActiveTools(); const available = new Set(pi.getAllTools().map((tool) => tool.name)); pi.setActiveTools(["read", "grep", "find", "ls"].filter((name) => available.has(name))); ctx.ui.notify(`检查工具:${pi.getActiveTools().join(", ")}`, "info"); } }); pi.on("context", (event) => { if (!previousTools) return; const notice: UserMessage = { role: "user", content: `[inspect 阶段说明] 目标 SDK 版本:${upgradeTask.targetVersion};兼容约束:${upgradeTask.compatibility}。当前只检查代码并提出建议,不修改文件或执行命令。`, timestamp: Date.now() }; const messages = [...event.messages]; const at = messages.at(-1)?.role === "user" ? messages.length - 1 : messages.length; messages.splice(at, 0, notice); return { messages }; }); }这里用了三个具体入口:
registerCommand()注册/inspect,命令回调保存原工具集合,再选取已注册的read、grep、find、ls;重复开启不会覆盖最初保存的集合。setActiveTools()改变向模型声明的活动工具;getAllTools()用于排除当前环境中不存在的工具名。它切换的是工具集合,不是重新注册工具。on("context", ...)在模型请求前返回新的消息列表,把明确标识的阶段说明放在最后一条用户问题之前。原历史数组没有被改写,当前问题也仍在末尾。API 与事件类型、context 事件执行。
context处理的是不含系统消息的对话列表,运行器会恢复系统提示和工具状态;示例没有调用持久化消息 API,只为当轮输入补充说明。previousTools是扩展实例内状态,若要支持重新加载和会话分支切换,需要另行保存并恢复这份状态。upgradeTask用示例值展示目标版本和兼容约束怎样进入当轮消息;业务接入时应从当前升级任务装载这些字段,切换任务或迁移分支后重新绑定,避免沿用上个任务的约束。阶段消息只补充模型输入,代码是否兼容仍由实际调用点和测试结果判断。怎样加载和触发
项目扩展在授予项目信任后加载;开发时也可以显式指定文件:
pi --extension ./.pi/extensions/inspect.ts不需要为这个本地
TypeScript文件单独打包。修改文件后,可在交互会话中执行/reload重新加载。项目扩展目录。验证时可按三个观察点检查:
- 输入
/inspect,界面应显示本环境可用的检查工具名;命令本身不需要发起模型请求。 - 触发一次
context事件时,返回列表增加一条阶段说明,原问题保持末尾,传入的历史列表保持原样。可以先用模拟事件检查,不必调用模型。 - 再次输入
/inspect后执行/inspect off,应恢复第一次进入前的工具集合;退出后context钩子不再增加说明。
单文件转译只能检查可转译性,模拟 API 能检查命令与事件逻辑;完整依赖类型检查、真实加载和模型行为需要分别验证。
import type在转译后会被移除,不会成为运行时模块导入。工具集合与执行权限的区别
这个示例定制了模型看到的工具和当前任务阶段,并不提供操作系统沙箱。该版本的
codemode、deferred工具即使不在活动集合中,仍可能由 codemode 脚本调用;需要约束真实动作时,还应按工具调用入口和执行环境设置相应策略。工具暴露与活动集合。参考资料
- 国内:JavaGuide:Harness Engineering:参考工具和钩子由运行系统提供的分工,将阶段说明与真正改变工具集合的代码分开。
- 国内:zero2Agent:工具管理:参考按任务阶段选择本轮工具定义,解释注册工具与激活工具的区别。
- 国内:Spring AI Alibaba:Hooks 和 Interceptors:参考在模型调用前通过钩子调整上下文的组织;示例接口采用
Pi自身的事件 API。 - 国内:WeThinkIn:Agent Harness 工程:参考任务动作、运行环境和预期结果分别定义,为工具集合、消息变化与退出恢复设置独立观察点。
- 国外:TypeScript:Modules Reference:依据类型导入被擦除的规则,说明示例的接口类型与运行时模块加载不同。
- 国外:TypeScript:isolatedModules:依据单文件转译的限制,区分转译检查、完整类型检查与实际运行验证。