如何阅读这些图How to read the diagrams
- 颜色 = 代码所在的层 / 包,所有图、表与幻灯片一致。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.
包依赖图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.
要点Key points
pi-coding-agent直接依赖pi-agent-core、pi-ai、pi-tui、pi-mcp、pi-codemode和chord。pi-coding-agentdepends onpi-agent-core,pi-ai,pi-tui,pi-mcp,pi-codemodeandchord.pi-ai唯一的内部依赖是pi-telemetry;pi-tui、pi-mcp、pi-codemode、chord没有任何内部依赖,可以独立使用。pi-ai's only internal dependency ispi-telemetry;pi-tui,pi-mcp,pi-codemodeandchordhave 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 onchord. - 虚线是
devDependencies:pi-coding-agent只在测试中使用 client / protocol / server;pi-evals依赖 coding-agent 与 ai。Dashed edges aredevDependencies:pi-coding-agentonly uses client / protocol / server in tests;pi-evalsdepends on coding-agent and ai.
工作原理How it works
- 根
package.json用 npm workspaces 管理packages/*。The rootpackage.jsonmanagespackages/*with npm workspaces. npm run build按拓扑顺序依次构建:chord → tui → telemetry → codemode → mcp → ai → durable → env → agent → protocol → client → server → coding-agent。npm run buildbuilds in topological order: chord → tui → telemetry → codemode → mcp → ai → durable → env → agent → protocol → client → server → coding-agent.- 图中的边直接取自每个包
package.json里的@earendil-works/*依赖。Edges are read from each package's@earendil-works/*entries inpackage.json.
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| package.jsonL15 | scripts.build | workspace 与构建顺序workspaces and build order |
| packages/coding-agent/package.jsonL51 | dependencies | 顶层 CLI 的依赖top-level CLI dependencies |
| packages/agent/package.jsonL25 | dependencies | agent core 只依赖 pi-aiagent core depends only on pi-ai |
| packages/ai/package.jsonL69 | dependencies | LLM 层与 vendor SDKLLM layer and vendor SDKs |
分层架构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.
要点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 throughAgentLoopConfighooks. - pi-coding-agent:
AgentSession组装会话、8 个内置工具、扩展、压缩、设置与模型解析。pi-coding-agent:AgentSessionwires 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 oneAgentSessionRuntime; only interactive usespi-tui.
工作原理How it works
- 模式层调用
AgentSessionRuntime,它持有当前AgentSession,并在 new / switch / fork 时整体替换会话。Modes callAgentSessionRuntime, which owns the currentAgentSessionand replaces it wholesale on new / switch / fork. AgentSession把自己的逻辑挂到Agent的钩子上:prepareNextTurn、prepareRequest、finishTurn、beforeToolCall等。AgentSessionattaches its logic to theAgenthooks:prepareNextTurn,prepareRequest,finishTurn,beforeToolCalland more.sdk.ts把streamFn设为modelRuntime.streamSimple(),由Models注册表找到 provider 和对应的 API 实现。sdk.tssetsstreamFntomodelRuntime.streamSimple(); theModelsregistry resolves the provider and its API implementation.
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/coding-agent/src/core/agent-session.tsL378 | AgentSession | 核心编排对象(约 4.4k 行)the core orchestrator (~4.4k lines) |
| packages/coding-agent/src/core/agent-session-runtime.tsL74 | AgentSessionRuntime | 会话生命周期与替换session lifecycle and replacement |
| packages/coding-agent/src/core/sdk.tsL420 | streamFn | 把 Agent 接到 ModelRuntimeconnects Agent to ModelRuntime |
| packages/agent/src/agent.tsL188 | Agent | 状态、队列与钩子state, queues and hooks |
| packages/ai/src/models.tsL250 | Models | provider / model 注册表provider / model registry |
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.
要点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时,该消息中所有工具调用直接判为失败,因为参数可能被截断。OnstopReason: "length"every tool call in that message fails, because its arguments may be truncated.finishTurn可返回end(立即结束)或continue(在无新消息时追加一次仅含上下文的轮次)。finishTurncan returnend(stop now) orcontinue(run one more context-only turn when nothing else is queued).
工作原理How it works
- 开始时先轮询一次
getSteeringMessages(),因为用户可能在等待时已经输入。It first pollsgetSteeringMessages(), since the user may have typed while waiting. - 从第二轮起调用
prepareNextTurn();coding agent 在这里做阈值压缩并刷新工具清单。From the second turn it callsprepareNextTurn(); the coding agent compacts at threshold and refreshes the tool loadout here. prepareRequest()可覆盖 model 与 thinking level,然后streamAssistantResponse()依次执行transformContext→convertToLlm→getApiKey→streamFn。prepareRequest()may override model and thinking level; thenstreamAssistantResponse()runstransformContext→convertToLlm→getApiKey→streamFn.- 若所有工具结果都带
terminate: true,内层循环在本轮后停止。If every tool result carriesterminate: true, the inner loop stops after this turn.
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/agent/src/agent-loop.tsL163 | runLoop() | 双层循环本体the two nested loops |
| packages/agent/src/agent-loop.tsL381 | streamAssistantResponse() | 上下文转换与流式请求context transform and streaming call |
| packages/agent/src/types.tsL193 | AgentLoopConfig | 所有循环钩子的类型types for every loop hook |
| packages/coding-agent/src/core/agent-session.tsL888 | finishTurn | coding agent 的轮次收尾coding agent's end-of-turn hook |
工具调用执行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.
要点Key points
- 参数用工具的 TypeBox schema 通过
validateToolArguments()校验。Arguments are validated against the tool's TypeBox schema viavalidateToolArguments(). - coding agent 把
beforeToolCall/afterToolCall映射为扩展事件tool_call(可阻止)和tool_result(可改写结果)。The coding agent mapsbeforeToolCall/afterToolCallto the extension eventstool_call(can block) andtool_result(can rewrite). - 模式为
ToolExecutionMode = "sequential" | "parallel";只要批次中有一个工具声明executionMode: "sequential",整批就串行。ToolExecutionMode = "sequential" | "parallel"; if any tool in the batch declaresexecutionMode: "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
- 并行模式下,准备阶段(含
beforeToolCall)按源顺序逐个进行,执行阶段并发。In parallel mode, preparation (includingbeforeToolCall) runs one by one in source order; execution runs concurrently. tool_execution_end在每个工具完成时发出;ToolResultMessage则按原始顺序统一发出。tool_execution_endfires as each tool finishes;ToolResultMessages are emitted afterwards in the original order.- 执行中的
onUpdate回调被转成tool_execution_update事件,用于流式显示 bash 输出等。onUpdateduring execution becomestool_execution_updateevents, used to stream bash output and similar.
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/agent/src/agent-loop.tsL708 | prepareToolCall() | 查找、校验与 beforeToolCalllookup, validation, beforeToolCall |
| packages/agent/src/agent-loop.tsL586 | executeToolCallsParallel() | 并发执行与有序结果concurrent execution, ordered results |
| packages/agent/src/agent-loop.tsL811 | runToolCall() | 可复用的单次调用reusable single call |
| packages/coding-agent/src/core/agent-session.tsL668 | emitToolCall | 接入扩展事件bridges to extension events |
| packages/coding-agent/src/core/tools/index.tsL95 | ToolName | 8 个内置工具the 8 built-in tools |
一次提示的完整时序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.
要点Key points
AgentSession.prompt()的顺序:扩展命令 →input事件 → 展开 skill 与 prompt template → 若正在流式输出则排队为 steer / follow-up → 预检压缩 →before_agent_start→agent.prompt()。AgentSession.prompt()order: extension commands →inputevent → 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, …) becomemessage_updateagent events. transformContext在 coding agent 中调用emitContext(),扩展可以在每次请求前改写消息。In the coding agenttransformContextcallsemitContext(), so extensions can rewrite messages before every request.
工作原理How it works
- Interactive 模式订阅会话事件,每个 delta 都触发一次(批处理后的)重绘。Interactive mode subscribes to session events; each delta triggers a (batched) re-render.
AgentSession在每个message_end时调用sessionManager.appendMessage(),会话文件随每条完成的消息增量写入。AgentSessioncallssessionManager.appendMessage()on everymessage_end, so the session file grows with each completed message.- 工具结果追加到上下文后,循环自动发起下一次请求,直到模型以
stop结束。After tool results join the context the loop issues the next request automatically, until the model ends withstop.
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/coding-agent/src/core/agent-session.tsL1968 | AgentSession.prompt() | 提示预处理流水线prompt preprocessing pipeline |
| packages/coding-agent/src/core/agent-session.tsL1162 | appendMessage | message_end 时持久化persists on message_end |
| packages/coding-agent/src/core/sdk.tsL439 | emitContext | transformContext → 扩展transformContext → extensions |
| packages/ai/src/types.tsL801 | AssistantMessageEvent | 12 种流事件the 12 stream event types |
核心类型Key types#
一句话结论TakeawayAgentSessionRuntime 持有 AgentSession,后者组合 Agent、SessionManager、ExtensionRunner 和 ModelRuntime。AgentSessionRuntime owns an AgentSession, which composes Agent, SessionManager, ExtensionRunner and ModelRuntime.
要点Key points
Agent的钩子都是公开字段(beforeToolCall、prepareRequest、finishTurn…),由上层直接赋值或包装。Agenthooks are public fields (beforeToolCall,prepareRequest,finishTurn, …) that the layer above assigns or wraps.ModelRuntime实现 pi-ai 的Models接口,并允许扩展通过registerProvider注册 provider。ModelRuntimeimplements pi-ai'sModelsinterface and lets extensions register providers viaregisterProvider.ExtensionRunner在AgentSession内部创建,bindCore()把会话动作暴露给扩展。ExtensionRunneris created insideAgentSession;bindCore()exposes session actions to extensions.
工作原理How it works
Agent维护 steering 与 follow-up 两个队列(PendingMessageQueue),转化为循环的getSteeringMessages/getFollowUpMessages。Agentkeeps two queues (PendingMessageQueue) for steering and follow-up and exposes them to the loop asgetSteeringMessages/getFollowUpMessages.AgentSessionRuntime.fork()/switchSession()先发出session_before_*扩展事件,再拆除当前会话并创建新会话。AgentSessionRuntime.fork()/switchSession()emitsession_before_*extension events, then tear down the current session and create a new one.
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/agent/src/agent.tsL188 | Agent | 队列、状态与订阅queues, state, subscription |
| packages/coding-agent/src/core/agent-session-runtime.tsL262 | fork() | 会话替换session replacement |
| packages/coding-agent/src/core/session-manager.tsL987 | SessionManager | JSONL 树存储JSONL tree store |
| packages/coding-agent/src/core/extensions/runner.tsL357 | ExtensionRunner | 事件分发event dispatch |
| packages/coding-agent/src/core/model-runtime.tsL172 | ModelRuntime | implements Modelsimplements Models |
会话持久化与分支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.
要点Key points
- 第 1 行是
session头(version3、id、cwd、可选parentSession)。Line 1 is thesessionheader (version3,id,cwd, optionalparentSession). - 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_editis append-only and can drop an earlier entry from model context (replacement: null) or replace its content.
工作原理How it works
branchWithSummary()把 leaf 移到分支点,并追加一个branch_summary(fromId指向被放弃的 leaf),记录那条路径做过什么。branchWithSummary()moves the leaf to the branch point and appends abranch_summary(withfromIdpointing at the abandoned leaf) describing that path.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.- 文件位于
~/.pi/agent/sessions/<cwd>/,custom条目供扩展保存状态但不进入上下文,custom_message则会进入。Files live under~/.pi/agent/sessions/<cwd>/;customentries store extension state outside the context,custom_messageentries enter it.
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/coding-agent/src/core/session-manager.tsL43 | SessionHeader | 文件头file header |
| packages/coding-agent/src/core/session-manager.tsL183 | SessionEntry | 条目联合类型entry union type |
| packages/coding-agent/src/core/session-manager.tsL1600 | branchWithSummary() | 带摘要的分支branch with summary |
| packages/coding-agent/src/core/session-manager.tsL175 | ContextEditEntry | 追加式上下文编辑append-only context edit |
上下文压缩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.
要点Key points
- threshold:
contextTokens > contextWindow − reserveTokens(默认reserveTokens= 16384)。threshold:contextTokens > contextWindow − reserveTokens(defaultreserveTokens= 16384). - overflow:provider 返回上下文溢出错误或可恢复的
length截断;删除失败消息、压缩,并只重试一次。overflow: a context-overflow error or a recoverablelengthstop; drop the failed message, compact, and retry exactly once. findCutPoint()保留约keepRecentTokens(默认 20000)的最近内容,并让切点落在轮次边界上。findCutPoint()keeps aboutkeepRecentTokens(default 20000) of recent content and puts the cut on a turn boundary.- 扩展可在
session_before_compact中取消压缩,或直接提供自己的压缩结果。Extensions can cancel compaction insession_before_compactor supply their own result.
工作原理How it works
- 自动检查发生在三处:
agent_end之后、提交新提示之前,以及prepareNextTurn中(同一次运行的轮次之间)。Automatic checks run in three places: afteragent_end, before a new prompt, and insideprepareNextTurn(between turns of one run). - 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.
appendCompaction(summary, firstKeptEntryId, tokensBefore, …)之后,上下文重建为“摘要 + 保留的消息”。AfterappendCompaction(summary, firstKeptEntryId, tokensBefore, …)the context is rebuilt as summary + kept messages.
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/coding-agent/src/core/compaction/compaction.tsL267 | shouldCompact() | 阈值判断threshold test |
| packages/coding-agent/src/core/compaction/compaction.tsL446 | findCutPoint() | 选择保留的尾部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.tsL10 | SETTINGS_DEFAULTS | 默认 token 参数default token budgets |
扩展生命周期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.
要点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 等。Theregister*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
createAgentSessionServices()创建DefaultResourceLoader,reload()解析路径并调用loadExtensionsCached()。createAgentSessionServices()creates aDefaultResourceLoader;reload()resolves paths and callsloadExtensionsCached().- 加载期间
ExtensionRuntime的动作方法是会抛错的占位,registerProvider则先排队;AgentSession创建ExtensionRunner并bindCore()后换成真实动作。While loading,ExtensionRuntimeaction methods are throwing stubs andregisterProvidercalls are queued; onceAgentSessioncreates theExtensionRunner,bindCore()swaps in real actions. - 模式调用
session.bindExtensions({ uiContext, mode })绑定 UI,随后发出session_start与resources_discover。The mode callssession.bindExtensions({ uiContext, mode })to bind UI, thensession_startandresources_discoverfire.
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/coding-agent/src/core/extensions/loader.tsL828 | discoverAndLoadExtensions() | 目录发现directory discovery |
| packages/coding-agent/src/core/resource-loader.tsL699 | loadCurrentExtensionSet() | 实际加载路径the real load path |
| packages/coding-agent/src/core/extensions/types.tsL1564 | ExtensionAPI | 扩展可用的全部 APIthe full extension API |
| packages/coding-agent/src/core/extensions/runner.tsL410 | bindCore() | 接入会话动作binds session actions |
| packages/coding-agent/src/core/agent-session.tsL3258 | bindExtensions() | UI 绑定与 session_startUI binding and session_start |
运行模式Run modes#
一句话结论TakeawayresolveAppMode() 根据参数和 TTY 选出四种模式之一;它们都驱动同一个 AgentSessionRuntime。resolveAppMode() picks one of four modes from flags and TTY state; all of them drive the same AgentSessionRuntime.
要点Key points
--mode rpc→ rpc;--mode json→ json;-p或 stdin / stdout 非 TTY → print;否则 interactive。--mode rpc→ rpc;--mode json→ json;-por 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 asextension_ui_request.
工作原理How it works
- 非 interactive 模式会先接管 stdout,避免日志污染协议输出。Non-interactive modes take over stdout first so logs cannot corrupt protocol output.
- 每种模式都调用
session.bindExtensions(),传入各自的mode和 UI 上下文。Each mode callssession.bindExtensions()with its ownmodeand UI context. - interactive 模式通过
createInteractiveTui()选择TuiMainScreen(regular)或TuiAltScreen(fullscreen)。Interactive mode usescreateInteractiveTui()to pickTuiMainScreen(regular) orTuiAltScreen(fullscreen).
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/coding-agent/src/main.tsL112 | resolveAppMode() | 模式选择mode selection |
| packages/coding-agent/src/modes/rpc/rpc-types.tsL20 | RpcCommand | RPC 命令联合类型RPC command union |
| packages/coding-agent/src/modes/rpc/rpc-mode.tsL54 | runRpcMode() | JSONL 服务循环JSONL service loop |
| packages/coding-agent/src/modes/print-mode.tsL33 | runPrintMode() | 一次性输出one-shot output |
| packages/coding-agent/src/modes/interactive/interactive-mode.tsL451 | InteractiveMode | 终端交互界面terminal UI |
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.
要点Key points
- 每个 provider 一个文件,用
createProvider()创建,并在providers/all.ts中汇总。One file per provider, created withcreateProvider()and collected inproviders/all.ts. - API 实现位于
api/,经*.lazy.ts中的lazyApi()包装:模块在第一次流式调用时才 import。API implementations live inapi/, wrapped bylazyApi()in*.lazy.ts: a module is imported on its first stream call. KnownApi列出 10 种对话 API(另有 1 种图像 API、4 种分类器 API)。KnownApilists 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 useopenai-completions; xai usesopenai-responses; openrouter and github-copilot use several APIs.
工作原理How it works
- 每个 API 导出
stream与streamSimple,返回AssistantMessageEventStream。Every API exportsstreamandstreamSimple, returning anAssistantMessageEventStream. - 事件共 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. StreamFn约定不抛异常:认证、模块加载或请求失败都编码为流中的error事件(见lazyStream())。TheStreamFncontract forbids throwing: auth, module-load and request failures become anerrorevent in the stream (seelazyStream()).
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/ai/src/models.tsL1041 | createProvider() | provider 工厂provider factory |
| packages/ai/src/providers/all.tsL4 | providers/all.ts | 42 个内置 providerthe 42 built-in providers |
| packages/ai/src/api/anthropic-messages.tsL928 | streamSimple | 一个 API 实现示例an example API implementation |
| packages/ai/src/types.tsL61 | KnownApi | 10 种对话 APIthe 10 chat APIs |
| packages/ai/src/utils/event-stream.tsL97 | AssistantMessageEventStream | 统一事件流the normalized stream |
| packages/agent/src/types.tsL20 | StreamFn | 不抛异常的约定the no-throw contract |
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.
要点Key points
Container组合子组件;可选的handleInput/handleMouse处理输入。Containercomposes children; optionalhandleInput/handleMousehandle input.- 两种屏幕:
TuiMainScreen(主屏,保留 scrollback)与TuiAltScreen(备用屏全屏)。Two screens:TuiMainScreen(main buffer, keeps scrollback) andTuiAltScreen(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
ProcessTerminal用StdinBuffer把批量到达的输入拆成完整的按键序列,交给获得焦点的组件。ProcessTerminalusesStdinBufferto split batched input into complete key sequences for the focused component.requestRender()在process.nextTick中合并请求,scheduleRender()保证两帧间隔 ≥MIN_RENDER_INTERVAL_MS(16 ms);force会立即渲染。requestRender()coalesces onprocess.nextTick;scheduleRender()keeps frames ≥MIN_RENDER_INTERVAL_MS(16 ms) apart;forcerenders immediately.TuiMainScreen.doRender()合成 overlay 后与previousLines比较,在同步输出(CSI ?2026h)中只写出变化的行。TuiMainScreen.doRender()composites overlays, diffs againstpreviousLines, and writes only changed lines inside synchronized output (CSI ?2026h).
关键文件Key files · 4130bd5
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/tui/src/tui.tsL117 | Component | 最小组件接口minimal component interface |
| packages/tui/src/tui.tsL1001 | requestRender() | 批量与节流batching and throttling |
| packages/tui/src/tui-main-screen.tsL124 | TuiMainScreen | 差分渲染differential rendering |
| packages/tui/src/terminal.tsL150 | ProcessTerminal | 终端 I/Oterminal I/O |
| packages/coding-agent/src/modes/interactive/tui-renderer.tsL22 | createInteractiveTui() | 选择屏幕类型picks the screen type |
仓库统计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.*.
| 包Package | 源文件src files | 源码行src LOC | 测试文件test files | 测试行test LOC | 全部 .tsall .ts |
|---|---|---|---|---|---|
| coding-agent | 303 | 86,111 | 332 | 71,535 | 742 |
| ai | 198 | 27,238 | 174 | 43,102 | 382 |
| durable | 72 | 22,427 | 102 | 35,081 | 176 |
| tui | 46 | 19,505 | 51 | 20,072 | 97 |
| chord | 29 | 8,817 | 30 | 9,726 | 59 |
| mcp | 18 | 3,244 | 6 | 1,532 | 25 |
| codemode | 16 | 2,578 | 5 | 1,342 | 24 |
| agent | 6 | 2,519 | 7 | 4,058 | 16 |
| env | 6 | 2,073 | 6 | 897 | 13 |
| server | 16 | 1,958 | 6 | 1,054 | 23 |
| evals | 5 | 1,446 | 7 | 829 | 23 |
| client | 8 | 1,135 | 4 | 799 | 13 |
| telemetry | 6 | 935 | 2 | 243 | 8 |
| protocol | 8 | 869 | 3 | 567 | 12 |
| 合计Total | 737 | 180,855 | 735 | 190,837 | 1,613 |