Pi 架构图解Pi, illustrated4130bd5
演示Slides
4130bd5earendil-works/pi · 2026-10-10

读懂 Pi:
一个可扩展的
编码智能体内核
Inside Pi:
an extensible coding-agent core

基于源码逐行核对的架构导读。12 张图覆盖包依赖、Agent 循环、工具执行、会话树、压缩、扩展、运行模式、Provider 与 TUI;每个结论都链接到对应提交中的具体代码行。An architecture walkthrough checked line by line against the source. Twelve diagrams cover packages, the agent loop, tool execution, the session tree, compaction, extensions, run modes, providers and the TUI — every claim links to the exact lines at this commit.

14
个 workspace 包workspace packages
181k
行 TypeScript 源码lines of TS source
191k
行测试代码lines of tests
42
个内置 LLM providerbuilt-in LLM providers
8
个内置工具built-in tools
41
种扩展事件extension events
33
种 RPC 命令RPC command types
4
种运行模式run modes

如何阅读这些图How to read the diagrams

Run modespi-coding-agentExtensionspi-agent-corepi-aipi-tuiInfra
  • 颜色 = 代码所在的层 / 包,所有图、表与幻灯片一致。Colour = the layer / package the code lives in, identical across every diagram, table and slide.
  • 结构与主流程自上而下(TB),短管道与映射从左到右(LR),时序图从左到右按层排列参与者。Structure and main flows run top-down (TB); short pipelines and mappings run left-to-right (LR); sequence participants are ordered by layer.
  • 实线 = 调用 / 依赖;虚线 = 事件、异步或 devDependency。Solid = call / dependency; dashed = event, async or devDependency.
  • 菱形 = 判断;深色胶囊 = 起止点;红色 = 错误路径。点击任意图可放大。Diamond = decision; dark pill = start / end; red = error path. Click any diagram to enlarge.
01pi-coding-agent

包依赖图Package dependency graph#

一句话结论Takeaway14 个 npm workspace 包,pi-coding-agent 位于顶端;运行时栈只有四层,其余包互相解耦。14 npm workspace packages with pi-coding-agent on top; the runtime stack is only four packages deep and everything else is decoupled.

内部依赖:实线 = dependencies,虚线 = devDependenciesInternal deps: solid = dependencies, dashed = devDependenciesTB
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • pi-coding-agent 直接依赖 pi-agent-core、pi-ai、pi-tui、pi-mcp、pi-codemode 和 chord。pi-coding-agent depends on pi-agent-core, pi-ai, pi-tui, pi-mcp, pi-codemode and chord.
  • pi-ai 唯一的内部依赖是 pi-telemetry;pi-tui、pi-mcp、pi-codemode、chord 没有任何内部依赖,可以独立使用。pi-ai's only internal dependency is pi-telemetry; pi-tui, pi-mcp, pi-codemode and chord have none and can be used standalone.
  • Durable 与远程会话是一组独立的包:pi-durable、pi-env、pi-protocol、pi-client、pi-server,都构建在 chord 之上。Durable and remote sessions form a separate cluster — pi-durable, pi-env, pi-protocol, pi-client, pi-server — all built on chord.
  • 虚线是 devDependencies:pi-coding-agent 只在测试中使用 client / protocol / server;pi-evals 依赖 coding-agent 与 ai。Dashed edges are devDependencies: pi-coding-agent only uses client / protocol / server in tests; pi-evals depends on coding-agent and ai.

工作原理How it works

  1. 根 package.json 用 npm workspaces 管理 packages/*。The root package.json manages packages/* with npm workspaces.
  2. npm run build 按拓扑顺序依次构建:chord → tui → telemetry → codemode → mcp → ai → durable → env → agent → protocol → client → server → coding-agent。npm run build builds in topological order: chord → tui → telemetry → codemode → mcp → ai → durable → env → agent → protocol → client → server → coding-agent.
  3. 图中的边直接取自每个包 package.json 里的 @earendil-works/* 依赖。Edges are read from each package's @earendil-works/* entries in package.json.

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
package.jsonL15scripts.buildworkspace 与构建顺序workspaces and build order
packages/coding-agent/package.jsonL51dependencies顶层 CLI 的依赖top-level CLI dependencies
packages/agent/package.jsonL25dependenciesagent core 只依赖 pi-aiagent core depends only on pi-ai
packages/ai/package.jsonL69dependenciesLLM 层与 vendor SDKLLM layer and vendor SDKs
02pi-coding-agent

分层架构Layered architecture#

一句话结论Takeaway请求自上而下穿过四层:运行模式 → AgentSession → Agent 循环 → pi-ai 统一事件流。A request passes down four layers: run mode → AgentSession → the Agent loop → pi-ai's normalized event stream.

自上而下的四层运行时栈The four-layer runtime stack, top to bottomTB
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • pi-ai:把 42 个内置 provider 归一为一套流式事件协议 AssistantMessageEventStream。pi-ai normalizes 42 built-in providers into one streaming protocol, AssistantMessageEventStream.
  • pi-agent-core:与具体产品无关的工具调用循环,所有策略通过 AgentLoopConfig 钩子注入。pi-agent-core is a product-agnostic tool-calling loop; all policy is injected through AgentLoopConfig hooks.
  • pi-coding-agent:AgentSession 组装会话、8 个内置工具、扩展、压缩、设置与模型解析。pi-coding-agent: AgentSession wires in sessions, 8 built-in tools, extensions, compaction, settings and model resolution.
  • 运行模式:interactive、print(text / json)和 rpc 共享同一个 AgentSessionRuntime;只有 interactive 使用 pi-tui。Run modes: interactive, print (text / json) and rpc share one AgentSessionRuntime; only interactive uses pi-tui.

工作原理How it works

  1. 模式层调用 AgentSessionRuntime,它持有当前 AgentSession,并在 new / switch / fork 时整体替换会话。Modes call AgentSessionRuntime, which owns the current AgentSession and replaces it wholesale on new / switch / fork.
  2. AgentSession 把自己的逻辑挂到 Agent 的钩子上:prepareNextTurn、prepareRequest、finishTurn、beforeToolCall 等。AgentSession attaches its logic to the Agent hooks: prepareNextTurn, prepareRequest, finishTurn, beforeToolCall and more.
  3. sdk.ts 把 streamFn 设为 modelRuntime.streamSimple(),由 Models 注册表找到 provider 和对应的 API 实现。sdk.ts sets streamFn to modelRuntime.streamSimple(); the Models registry resolves the provider and its API implementation.

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
packages/coding-agent/src/core/agent-session.tsL378AgentSession核心编排对象(约 4.4k 行)the core orchestrator (~4.4k lines)
packages/coding-agent/src/core/agent-session-runtime.tsL74AgentSessionRuntime会话生命周期与替换session lifecycle and replacement
packages/coding-agent/src/core/sdk.tsL420streamFn把 Agent 接到 ModelRuntimeconnects Agent to ModelRuntime
packages/agent/src/agent.tsL188Agent状态、队列与钩子state, queues and hooks
packages/ai/src/models.tsL250Modelsprovider / model 注册表provider / model registry
03pi-agent-core

Agent 循环Agent loop#

一句话结论TakeawayrunLoop 是两层嵌套循环:内层处理工具调用与 steering 消息,外层在 follow-up 消息到达时重新启动。runLoop is two nested loops: the inner one drains tool calls and steering messages, the outer one restarts when follow-up messages arrive.

runLoop():内层循环与外层循环runLoop(): inner and outer loopTB
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • Steering:Agent 运行中用户插入的消息,在当前轮的工具执行完成后、下一次 LLM 请求之前注入。Steering messages typed while the agent runs are injected after the current turn's tools finish, before the next LLM request.
  • Follow-up:只在 Agent 本该停止时才投递,触发外层循环。Follow-up messages are delivered only when the agent would otherwise stop, and drive the outer loop.
  • stopReason 为 length 时,该消息中所有工具调用直接判为失败,因为参数可能被截断。On stopReason: "length" every tool call in that message fails, because its arguments may be truncated.
  • finishTurn 可返回 end(立即结束)或 continue(在无新消息时追加一次仅含上下文的轮次)。finishTurn can return end (stop now) or continue (run one more context-only turn when nothing else is queued).

工作原理How it works

  1. 开始时先轮询一次 getSteeringMessages(),因为用户可能在等待时已经输入。It first polls getSteeringMessages(), since the user may have typed while waiting.
  2. 从第二轮起调用 prepareNextTurn();coding agent 在这里做阈值压缩并刷新工具清单。From the second turn it calls prepareNextTurn(); the coding agent compacts at threshold and refreshes the tool loadout here.
  3. prepareRequest() 可覆盖 model 与 thinking level,然后 streamAssistantResponse() 依次执行 transformContext → convertToLlm → getApiKey → streamFn。prepareRequest() may override model and thinking level; then streamAssistantResponse() runs transformContext → convertToLlm → getApiKey → streamFn.
  4. 若所有工具结果都带 terminate: true,内层循环在本轮后停止。If every tool result carries terminate: true, the inner loop stops after this turn.

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
packages/agent/src/agent-loop.tsL163runLoop()双层循环本体the two nested loops
packages/agent/src/agent-loop.tsL381streamAssistantResponse()上下文转换与流式请求context transform and streaming call
packages/agent/src/types.tsL193AgentLoopConfig所有循环钩子的类型types for every loop hook
packages/coding-agent/src/core/agent-session.tsL888finishTurncoding agent 的轮次收尾coding agent's end-of-turn hook
04pi-agent-core

工具调用执行Tool call execution#

一句话结论Takeaway每个工具调用都走同一条管道:查找 → 参数准备 → schema 校验 → beforeToolCall → 执行 → afterToolCall;任何失败都变成错误结果,而不是异常。Every tool call runs one pipeline — lookup → prepare → schema validation → beforeToolCall → execute → afterToolCall — and every failure becomes an error result, never a throw.

单个工具调用的处理管道The pipeline for one tool callTB
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • 参数用工具的 TypeBox schema 通过 validateToolArguments() 校验。Arguments are validated against the tool's TypeBox schema via validateToolArguments().
  • coding agent 把 beforeToolCall / afterToolCall 映射为扩展事件 tool_call(可阻止)和 tool_result(可改写结果)。The coding agent maps beforeToolCall / afterToolCall to the extension events tool_call (can block) and tool_result (can rewrite).
  • 模式为 ToolExecutionMode = "sequential" | "parallel";只要批次中有一个工具声明 executionMode: "sequential",整批就串行。ToolExecutionMode = "sequential" | "parallel"; if any tool in the batch declares executionMode: "sequential", the whole batch runs sequentially.
  • runToolCall() 对外导出,供调用其他工具的工具复用同一套钩子(例如权限检查)。runToolCall() is exported so tools that call other tools reuse the same hooks (e.g. permission checks).

工作原理How it works

  1. 并行模式下,准备阶段(含 beforeToolCall)按源顺序逐个进行,执行阶段并发。In parallel mode, preparation (including beforeToolCall) runs one by one in source order; execution runs concurrently.
  2. tool_execution_end 在每个工具完成时发出;ToolResultMessage 则按原始顺序统一发出。tool_execution_end fires as each tool finishes; ToolResultMessages are emitted afterwards in the original order.
  3. 执行中的 onUpdate 回调被转成 tool_execution_update 事件,用于流式显示 bash 输出等。onUpdate during execution becomes tool_execution_update events, used to stream bash output and similar.

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
packages/agent/src/agent-loop.tsL708prepareToolCall()查找、校验与 beforeToolCalllookup, validation, beforeToolCall
packages/agent/src/agent-loop.tsL586executeToolCallsParallel()并发执行与有序结果concurrent execution, ordered results
packages/agent/src/agent-loop.tsL811runToolCall()可复用的单次调用reusable single call
packages/coding-agent/src/core/agent-session.tsL668emitToolCall接入扩展事件bridges to extension events
packages/coding-agent/src/core/tools/index.tsL95ToolName8 个内置工具the 8 built-in tools
05pi-coding-agent

一次提示的完整时序One prompt, end to end#

一句话结论Takeaway一次用户输入会经过扩展拦截、两次 LLM 请求和一次工具执行;每条消息在 message_end 时写入会话文件。One user prompt passes extension interception, two LLM requests and a tool run; every message is persisted to the session file on message_end.

Interactive 模式下的一次提示(含一次 bash 工具调用)One prompt in interactive mode, with one bash tool callSEQ
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • AgentSession.prompt() 的顺序:扩展命令 → input 事件 → 展开 skill 与 prompt template → 若正在流式输出则排队为 steer / follow-up → 预检压缩 → before_agent_start → agent.prompt()。AgentSession.prompt() order: extension commands → input event → expand skills and prompt templates → queue as steer / follow-up if streaming → pre-prompt compaction check → before_agent_start → agent.prompt().
  • pi-ai 的流事件(text_delta、toolcall_delta 等)在 agent 层变成 message_update。pi-ai stream events (text_delta, toolcall_delta, …) become message_update agent events.
  • transformContext 在 coding agent 中调用 emitContext(),扩展可以在每次请求前改写消息。In the coding agent transformContext calls emitContext(), so extensions can rewrite messages before every request.

工作原理How it works

  1. Interactive 模式订阅会话事件,每个 delta 都触发一次(批处理后的)重绘。Interactive mode subscribes to session events; each delta triggers a (batched) re-render.
  2. AgentSession 在每个 message_end 时调用 sessionManager.appendMessage(),会话文件随每条完成的消息增量写入。AgentSession calls sessionManager.appendMessage() on every message_end, so the session file grows with each completed message.
  3. 工具结果追加到上下文后,循环自动发起下一次请求,直到模型以 stop 结束。After tool results join the context the loop issues the next request automatically, until the model ends with stop.

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
packages/coding-agent/src/core/agent-session.tsL1968AgentSession.prompt()提示预处理流水线prompt preprocessing pipeline
packages/coding-agent/src/core/agent-session.tsL1162appendMessagemessage_end 时持久化persists on message_end
packages/coding-agent/src/core/sdk.tsL439emitContexttransformContext → 扩展transformContext → extensions
packages/ai/src/types.tsL801AssistantMessageEvent12 种流事件the 12 stream event types
06pi-coding-agent

核心类型Key types#

一句话结论TakeawayAgentSessionRuntime 持有 AgentSession,后者组合 Agent、SessionManager、ExtensionRunner 和 ModelRuntime。AgentSessionRuntime owns an AgentSession, which composes Agent, SessionManager, ExtensionRunner and ModelRuntime.

主要类与接口及其组合关系Main classes and interfaces and how they composeCLASS
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • Agent 的钩子都是公开字段(beforeToolCall、prepareRequest、finishTurn …),由上层直接赋值或包装。Agent hooks are public fields (beforeToolCall, prepareRequest, finishTurn, …) that the layer above assigns or wraps.
  • ModelRuntime 实现 pi-ai 的 Models 接口,并允许扩展通过 registerProvider 注册 provider。ModelRuntime implements pi-ai's Models interface and lets extensions register providers via registerProvider.
  • ExtensionRunner 在 AgentSession 内部创建,bindCore() 把会话动作暴露给扩展。ExtensionRunner is created inside AgentSession; bindCore() exposes session actions to extensions.

工作原理How it works

  1. Agent 维护 steering 与 follow-up 两个队列(PendingMessageQueue),转化为循环的 getSteeringMessages / getFollowUpMessages。Agent keeps two queues (PendingMessageQueue) for steering and follow-up and exposes them to the loop as getSteeringMessages / getFollowUpMessages.
  2. AgentSessionRuntime.fork() / switchSession() 先发出 session_before_* 扩展事件,再拆除当前会话并创建新会话。AgentSessionRuntime.fork() / switchSession() emit session_before_* extension events, then tear down the current session and create a new one.

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
packages/agent/src/agent.tsL188Agent队列、状态与订阅queues, state, subscription
packages/coding-agent/src/core/agent-session-runtime.tsL262fork()会话替换session replacement
packages/coding-agent/src/core/session-manager.tsL987SessionManagerJSONL 树存储JSONL tree store
packages/coding-agent/src/core/extensions/runner.tsL357ExtensionRunner事件分发event dispatch
packages/coding-agent/src/core/model-runtime.tsL172ModelRuntimeimplements Modelsimplements Models
07pi-coding-agent

会话持久化与分支Session persistence & branching#

一句话结论Takeaway会话是只追加的 JSONL 文件;每个条目带 id 与 parentId,所以一个文件就是一棵树,leafId 指向当前位置。A session is an append-only JSONL file; every entry has id and parentId, so one file is a tree and leafId marks the current position.

一个会话文件中的条目树(示例)The entry tree inside one session file (example)TB
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • 第 1 行是 session 头(version 3、id、cwd、可选 parentSession)。Line 1 is the session header (version 3, id, cwd, optional parentSession).
  • 11 种条目:message、thinking_level_change、model_change、usage、compaction、branch_summary、custom、custom_message、label、session_info、context_edit。11 entry types: message, thinking_level_change, model_change, usage, compaction, branch_summary, custom, custom_message, label, session_info, context_edit.
  • 分支不会改写历史:把 leaf 移回旧条目,下一次追加自然形成兄弟分支。Branching never rewrites history: move the leaf to an older entry and the next append becomes a sibling branch.
  • context_edit 只追加,能把某个旧条目从模型上下文中删除(replacement: null)或替换其内容。context_edit is append-only and can drop an earlier entry from model context (replacement: null) or replace its content.

工作原理How it works

  1. branchWithSummary() 把 leaf 移到分支点,并追加一个 branch_summary(fromId 指向被放弃的 leaf),记录那条路径做过什么。branchWithSummary() moves the leaf to the branch point and appends a branch_summary (with fromId pointing at the abandoned leaf) describing that path.
  2. buildSessionContext() 从 leaf 走到根,应用最新的 compaction 与 context_edit,再转换为 LLM 消息。buildSessionContext() walks from leaf to root, applies the latest compaction and context edits, and converts entries to LLM messages.
  3. 文件位于 ~/.pi/agent/sessions/<cwd>/,custom 条目供扩展保存状态但不进入上下文,custom_message 则会进入。Files live under ~/.pi/agent/sessions/<cwd>/; custom entries store extension state outside the context, custom_message entries enter it.

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
packages/coding-agent/src/core/session-manager.tsL43SessionHeader文件头file header
packages/coding-agent/src/core/session-manager.tsL183SessionEntry条目联合类型entry union type
packages/coding-agent/src/core/session-manager.tsL1600branchWithSummary()带摘要的分支branch with summary
packages/coding-agent/src/core/session-manager.tsL175ContextEditEntry追加式上下文编辑append-only context edit
08pi-coding-agent

上下文压缩Compaction#

一句话结论Takeaway三种触发(manual / threshold / overflow)走同一条路径:保留最近的尾部,用 LLM 总结更早的部分,存为一个 compaction 条目。Three triggers (manual / threshold / overflow) share one path: keep the recent tail, summarize the rest with the LLM, store it as a compaction entry.

三种触发方式共享的压缩路径One compaction path shared by three triggersTB
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • threshold:contextTokens > contextWindow − reserveTokens(默认 reserveTokens = 16384)。threshold: contextTokens > contextWindow − reserveTokens (default reserveTokens = 16384).
  • overflow:provider 返回上下文溢出错误或可恢复的 length 截断;删除失败消息、压缩,并只重试一次。overflow: a context-overflow error or a recoverable length stop; drop the failed message, compact, and retry exactly once.
  • findCutPoint() 保留约 keepRecentTokens(默认 20000)的最近内容,并让切点落在轮次边界上。findCutPoint() keeps about keepRecentTokens (default 20000) of recent content and puts the cut on a turn boundary.
  • 扩展可在 session_before_compact 中取消压缩,或直接提供自己的压缩结果。Extensions can cancel compaction in session_before_compact or supply their own result.

工作原理How it works

  1. 自动检查发生在三处:agent_end 之后、提交新提示之前,以及 prepareNextTurn 中(同一次运行的轮次之间)。Automatic checks run in three places: after agent_end, before a new prompt, and inside prepareNextTurn (between turns of one run).
  2. token 数优先取最后一条 assistant 的 usage;出错或 usage 为 0 时按 chars/4 估算。Token counts come from the last assistant usage; on errors or zero usage they are estimated at chars/4.
  3. appendCompaction(summary, firstKeptEntryId, tokensBefore, …) 之后,上下文重建为“摘要 + 保留的消息”。After appendCompaction(summary, firstKeptEntryId, tokensBefore, …) the context is rebuilt as summary + kept messages.

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
packages/coding-agent/src/core/compaction/compaction.tsL267shouldCompact()阈值判断threshold test
packages/coding-agent/src/core/compaction/compaction.tsL446findCutPoint()选择保留的尾部chooses the kept tail
packages/coding-agent/src/core/agent-session.tsL2947_checkCompaction()三种自动情形the automatic cases
packages/coding-agent/src/core/agent-session.tsL3097_runAutoCompaction()扩展钩子与追加extension hook and append
packages/coding-agent/src/core/settings-defaults.tsL10SETTINGS_DEFAULTS默认 token 参数default token budgets
09Extensions

扩展生命周期Extension lifecycle#

一句话结论Takeaway扩展是用 jiti 加载的 TypeScript 模块:工厂函数拿到 ExtensionAPI 注册能力,ExtensionRunner 在 41 个事件上分发。Extensions are TypeScript modules loaded with jiti: a factory receives an ExtensionAPI to register capabilities, and ExtensionRunner dispatches 41 events.

从发现、加载到事件分发From discovery and loading to event dispatchSEQ
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • 发现来源:全局 ~/.pi/agent/extensions、项目 .pi/extensions、settings 中的 packages,以及命令行 -e。Sources: global ~/.pi/agent/extensions, project .pi/extensions, packages in settings, and CLI -e.
  • register* 家族:Tool、Command、Shortcut、Flag、Provider、McpServer、MessageRenderer、EntryRenderer、ToolRenderer、VirtualModel 等。The register* family: Tool, Command, Shortcut, Flag, Provider, McpServer, MessageRenderer, EntryRenderer, ToolRenderer, VirtualModel and more.
  • 可改写数据的事件:input、context、before_provider_request、tool_call(可阻止)、tool_result、session_before_compact(可取消)。Events that can change data: input, context, before_provider_request, tool_call (block), tool_result, session_before_compact (cancel).

工作原理How it works

  1. createAgentSessionServices() 创建 DefaultResourceLoader,reload() 解析路径并调用 loadExtensionsCached()。createAgentSessionServices() creates a DefaultResourceLoader; reload() resolves paths and calls loadExtensionsCached().
  2. 加载期间 ExtensionRuntime 的动作方法是会抛错的占位,registerProvider 则先排队;AgentSession 创建 ExtensionRunner 并 bindCore() 后换成真实动作。While loading, ExtensionRuntime action methods are throwing stubs and registerProvider calls are queued; once AgentSession creates the ExtensionRunner, bindCore() swaps in real actions.
  3. 模式调用 session.bindExtensions({ uiContext, mode }) 绑定 UI,随后发出 session_start 与 resources_discover。The mode calls session.bindExtensions({ uiContext, mode }) to bind UI, then session_start and resources_discover fire.

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
packages/coding-agent/src/core/extensions/loader.tsL828discoverAndLoadExtensions()目录发现directory discovery
packages/coding-agent/src/core/resource-loader.tsL699loadCurrentExtensionSet()实际加载路径the real load path
packages/coding-agent/src/core/extensions/types.tsL1564ExtensionAPI扩展可用的全部 APIthe full extension API
packages/coding-agent/src/core/extensions/runner.tsL410bindCore()接入会话动作binds session actions
packages/coding-agent/src/core/agent-session.tsL3258bindExtensions()UI 绑定与 session_startUI binding and session_start
10Run modes

运行模式Run modes#

一句话结论TakeawayresolveAppMode() 根据参数和 TTY 选出四种模式之一;它们都驱动同一个 AgentSessionRuntime。resolveAppMode() picks one of four modes from flags and TTY state; all of them drive the same AgentSessionRuntime.

resolveAppMode() 的分派Dispatch in resolveAppMode()LR
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • --mode rpc → rpc;--mode json → json;-p 或 stdin / stdout 非 TTY → print;否则 interactive。--mode rpc → rpc; --mode json → json; -p or non-TTY stdin / stdout → print; otherwise interactive.
  • RPC 模式读取 JSONL 命令(共 33 种:prompt、steer、follow_up、abort、compact、set_model、fork、get_tree、bash …),回写响应与事件。RPC mode reads JSONL commands (33 kinds: prompt, steer, follow_up, abort, compact, set_model, fork, get_tree, bash, …) and writes responses and events.
  • RPC 通过 extension_ui_request 把扩展的 UI 请求代理给宿主。RPC proxies extension UI requests to the host as extension_ui_request.

工作原理How it works

  1. 非 interactive 模式会先接管 stdout,避免日志污染协议输出。Non-interactive modes take over stdout first so logs cannot corrupt protocol output.
  2. 每种模式都调用 session.bindExtensions(),传入各自的 mode 和 UI 上下文。Each mode calls session.bindExtensions() with its own mode and UI context.
  3. interactive 模式通过 createInteractiveTui() 选择 TuiMainScreen(regular)或 TuiAltScreen(fullscreen)。Interactive mode uses createInteractiveTui() to pick TuiMainScreen (regular) or TuiAltScreen (fullscreen).

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
packages/coding-agent/src/main.tsL112resolveAppMode()模式选择mode selection
packages/coding-agent/src/modes/rpc/rpc-types.tsL20RpcCommandRPC 命令联合类型RPC command union
packages/coding-agent/src/modes/rpc/rpc-mode.tsL54runRpcMode()JSONL 服务循环JSONL service loop
packages/coding-agent/src/modes/print-mode.tsL33runPrintMode()一次性输出one-shot output
packages/coding-agent/src/modes/interactive/interactive-mode.tsL451InteractiveMode终端交互界面terminal UI
11pi-ai

Provider 抽象Provider abstraction#

一句话结论Takeawaypi-ai 把 Provider(身份、认证、模型目录)与 API(线协议)分开:42 个 provider 复用少数几种 API 实现。pi-ai separates a Provider (identity, auth, model catalog) from an API (wire protocol): 42 providers reuse a handful of API implementations.

Provider → API 实现 → 统一事件流Provider → API implementation → normalized event streamLR
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • 每个 provider 一个文件,用 createProvider() 创建,并在 providers/all.ts 中汇总。One file per provider, created with createProvider() and collected in providers/all.ts.
  • API 实现位于 api/,经 *.lazy.ts 中的 lazyApi() 包装:模块在第一次流式调用时才 import。API implementations live in api/, wrapped by lazyApi() in *.lazy.ts: a module is imported on its first stream call.
  • KnownApi 列出 10 种对话 API(另有 1 种图像 API、4 种分类器 API)。KnownApi lists 10 chat APIs (plus 1 image API and 4 classifier APIs).
  • 例:groq、deepseek 走 openai-completions;xai 走 openai-responses;openrouter 和 github-copilot 同时使用多种 API。e.g. groq and deepseek use openai-completions; xai uses openai-responses; openrouter and github-copilot use several APIs.

工作原理How it works

  1. 每个 API 导出 stream 与 streamSimple,返回 AssistantMessageEventStream。Every API exports stream and streamSimple, returning an AssistantMessageEventStream.
  2. 事件共 12 种:start、text_*、thinking_*、toolcall_*(各 start / delta / end)、done、error。There are 12 event types: start, text_*, thinking_*, toolcall_* (start / delta / end each), done, error.
  3. StreamFn 约定不抛异常:认证、模块加载或请求失败都编码为流中的 error 事件(见 lazyStream())。The StreamFn contract forbids throwing: auth, module-load and request failures become an error event in the stream (see lazyStream()).

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
packages/ai/src/models.tsL1041createProvider()provider 工厂provider factory
packages/ai/src/providers/all.tsL4providers/all.ts42 个内置 providerthe 42 built-in providers
packages/ai/src/api/anthropic-messages.tsL928streamSimple一个 API 实现示例an example API implementation
packages/ai/src/types.tsL61KnownApi10 种对话 APIthe 10 chat APIs
packages/ai/src/utils/event-stream.tsL97AssistantMessageEventStream统一事件流the normalized stream
packages/agent/src/types.tsL20StreamFn不抛异常的约定the no-throw contract
12pi-tui

TUI 渲染TUI rendering#

一句话结论Takeawaypi-tui 的组件只需实现 render(width) → string[];渲染器批量合并请求,每帧最多 16 ms 一次,只重写变化的行。A pi-tui component only implements render(width) → string[]; the renderer batches requests, caps frames at one per 16 ms, and rewrites only changed lines.

从按键 / 事件到终端输出的一帧One frame, from keypress or event to terminal outputLR
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →

要点Key points

  • Container 组合子组件;可选的 handleInput / handleMouse 处理输入。Container composes children; optional handleInput / handleMouse handle input.
  • 两种屏幕:TuiMainScreen(主屏,保留 scrollback)与 TuiAltScreen(备用屏全屏)。Two screens: TuiMainScreen (main buffer, keeps scrollback) and TuiAltScreen (alternate-screen fullscreen).
  • 内置组件:Editor、Input、Markdown、SelectList、Image、Loader、ScrollView、Stack 等。Built-in components: Editor, Input, Markdown, SelectList, Image, Loader, ScrollView, Stack and more.

工作原理How it works

  1. ProcessTerminal 用 StdinBuffer 把批量到达的输入拆成完整的按键序列,交给获得焦点的组件。ProcessTerminal uses StdinBuffer to split batched input into complete key sequences for the focused component.
  2. requestRender() 在 process.nextTick 中合并请求,scheduleRender() 保证两帧间隔 ≥ MIN_RENDER_INTERVAL_MS(16 ms);force 会立即渲染。requestRender() coalesces on process.nextTick; scheduleRender() keeps frames ≥ MIN_RENDER_INTERVAL_MS (16 ms) apart; force renders immediately.
  3. TuiMainScreen.doRender() 合成 overlay 后与 previousLines 比较,在同步输出(CSI ?2026h)中只写出变化的行。TuiMainScreen.doRender() composites overlays, diffs against previousLines, and writes only changed lines inside synchronized output (CSI ?2026h).

关键文件Key files · 4130bd5

文件File符号Symbol作用Role
packages/tui/src/tui.tsL117Component最小组件接口minimal component interface
packages/tui/src/tui.tsL1001requestRender()批量与节流batching and throttling
packages/tui/src/tui-main-screen.tsL124TuiMainScreen差分渲染differential rendering
packages/tui/src/terminal.tsL150ProcessTerminal终端 I/Oterminal I/O
packages/coding-agent/src/modes/interactive/tui-renderer.tsL22createInteractiveTui()选择屏幕类型picks the screen type
13Repository

仓库统计Repository stats#

一句话结论Takeaway约 181k 行源码、191k 行测试;coding-agent 一个包就占了 48% 的源码,测试代码量与源码相当。About 181k lines of source and 191k of tests; coding-agent alone is 48% of the source, and test code roughly matches source code.

统计自提交 4130bd5:只计 .ts(不含 .d.ts),排除 node_modules 与 dist;src = src/ 下,test = test*/ 下或 *.test.*。Counted at commit 4130bd5: .ts only (no .d.ts), node_modules and dist excluded; src = under src/, test = under test*/ or *.test.*.

源码行数src LOC测试行数test LOC
包Package源文件src files源码行src LOC测试文件test files测试行test LOC全部 .tsall .ts
coding-agent30386,11133271,535742
ai19827,23817443,102382
durable7222,42710235,081176
tui4619,5055120,07297
chord298,817309,72659
mcp183,24461,53225
codemode162,57851,34224
agent62,51974,05816
env62,073689713
server161,95861,05423
evals51,446782923
client81,135479913
telemetry693522438
protocol8869356712
合计Total737180,855735190,8371,613