这个包是什么What this package ispi-agent-core 是一个与“编码”无关的通用 Agent 内核:一个 Agent 类管状态和队列,一个 runLoop() 驱动“请求 → 工具 → 下一轮”,所有策略(上下文变换、权限、压缩、何时停)都通过 AgentLoopConfig 钩子注入。pi-agent-core is a general agent kernel that knows nothing about coding: an Agent class owns state and queues, runLoop() drives request → tools → next turn, and every policy (context transforms, permissions, compaction, when to stop) is injected through AgentLoopConfig hooks.
位于 pi-ai(模型与流)之上、pi-coding-agent(产品)之下。只有一个运行时依赖:pi-ai。Sits above pi-ai (models and streams) and below pi-coding-agent (the product). One runtime dependency: pi-ai.
模块结构Module structure#
一句话结论Takeaway6 个源文件、单一导出入口:agent.ts(有状态的外壳)调用 agent-loop.ts(无状态的循环),两者共享 types.ts;proxy.ts 与 stream-fn.ts 提供可替换的 StreamFn。Six source files and a single export entry: agent.ts (the stateful shell) calls agent-loop.ts (the stateless loop), both share types.ts; proxy.ts and stream-fn.ts provide swappable StreamFns.
要点Key points
index.ts只有 5 行:export *四个模块,再单独导出setDefaultStreamFn(getDefaultStreamFn不导出)。index.tsis five lines:export *from four modules plussetDefaultStreamFnalone (getDefaultStreamFnstays internal).- 循环本身不持有状态:
agentLoop()返回EventStream<AgentEvent, AgentMessage[]>,runAgentLoop()接受一个emit回调——Agent用的是后者。The loop holds no state:agentLoop()returns anEventStream<AgentEvent, AgentMessage[]>,runAgentLoop()takes anemitcallback —Agentuses the latter. - 对
pi-ai的依赖集中在四处:transcript 工具(工具声明 / system 基线)、validateToolArguments、EventStream与类型。Thepi-aidependency is concentrated in four places: transcript helpers (tool declarations, system baseline),validateToolArguments,EventStream, and types.
工作原理How it works
- 宿主构造
new Agent({ initialState, streamFn, convertToLlm, … })。The host constructsnew Agent({ initialState, streamFn, convertToLlm, … }). prompt()/continue()把当前状态拍成AgentContext快照,连同由选项组装的AgentLoopConfig交给runAgentLoop()。prompt()/continue()snapshot state into anAgentContextand pass it, with anAgentLoopConfigassembled from the options, torunAgentLoop().- 循环通过
emit发出AgentEvent;Agent.processEvents()先更新自身状态,再依次 await 订阅者。The loop emitsAgentEvents viaemit;Agent.processEvents()updates its own state first, then awaits subscribers in order.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/agent/src/index.tsL4 | export * | 唯一入口the only entry point |
| packages/agent/src/agent.tsL188 | Agent | 状态、队列、生命周期state, queues, lifecycle |
| packages/agent/src/agent-loop.tsL102 | runAgentLoop | 循环入口(emit 版本)loop entry (emit flavour) |
| packages/agent/src/stream-fn.tsL11 | setDefaultStreamFn | 全局默认 StreamFnglobal default StreamFn |
| packages/agent/src/types.tsL193 | AgentLoopConfig | 所有钩子的定义every hook's definition |
公共 APIPublic API#
一句话结论Takeaway只有一个导入路径 @earendil-works/pi-agent-core。日常只需要 Agent;需要自己管理状态时直接用 agentLoop() / runAgentLoop();runToolCall() 让宿主在循环之外复用同一套工具管道。There is a single import path, @earendil-works/pi-agent-core. Day to day you only need Agent; manage state yourself with agentLoop() / runAgentLoop(); runToolCall() lets a host reuse the same tool pipeline outside the loop.
由构建脚本扫描 export 声明生成,每个符号都链接到 v1.1.0 中的定义行。getDefaultStreamFn 未从 index.ts 导出。Generated by scanning export declarations at build time; each symbol links to its definition at v1.1.0. getDefaultStreamFn is not re-exported from index.ts.
运行生命周期Run lifecycle#
一句话结论Takeaway同一时刻最多一个活动运行:runWithLifecycle() 建立 AbortController 与 isStreaming,执行失败会被转换成一条合成的 error 助手消息而不是向上抛出,finishRun() 总在最后清理。At most one active run at a time: runWithLifecycle() sets up the AbortController and isStreaming; executor failures become a synthesized error assistant message instead of propagating, and finishRun() always cleans up.
要点Key points
prompt()在运行中调用会抛错,提示改用steer()或followUp()——这是唯一的并发入口约束。prompt()throws while a run is active, pointing atsteer()orfollowUp()— the single concurrency rule at the entry.isStreaming在agent_end监听器全部完成后才变回 false,所以waitForIdle()返回时 UI 一定已经处理完最后的事件。isStreamingturns false only after allagent_endlisteners settle, so whenwaitForIdle()resolves the UI has handled the final events.handleRunFailure()依次发出message_start/end(error 消息)、turn_end、agent_end,订阅者看到的事件序列与正常结束同构。handleRunFailure()emitsmessage_start/end(the error message),turn_endandagent_end, so subscribers see the same event shape as a normal ending.reset()运行中抛错;空闲时清空消息与队列,但保留 system 基线消息。reset()throws while running; when idle it clears messages and queues but keeps the system baseline message.
工作原理How it works
prompt(input)把字符串 / 图像规范化成 user 消息,调用runPromptMessages()。prompt(input)normalizes string / images into user messages and callsrunPromptMessages().runWithLifecycle(executor)创建AbortController,置isStreaming = true,执行executor(signal)。runWithLifecycle(executor)creates anAbortController, setsisStreaming = true, runsexecutor(signal).- 异常 →
handleRunFailure(error, aborted);stopReason取aborted或error。On exception →handleRunFailure(error, aborted);stopReasonbecomesabortedorerror. finally→finishRun():isStreaming = false、清streamingMessage/pendingToolCalls、resolve 空闲等待者。finally→finishRun():isStreaming = false, clearstreamingMessage/pendingToolCalls, resolve idle waiters.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/agent/src/agent.tsL373 | prompt() | 入口与并发检查entry and concurrency check |
| packages/agent/src/agent.tsL384 | continue() | 从现有上下文恢复resume from existing context |
| packages/agent/src/agent.tsL507 | runWithLifecycle() | AbortController 与 isStreamingAbortController and isStreaming |
| packages/agent/src/agent.tsL532 | handleRunFailure() | 合成 error 消息synthesize the error message |
| packages/agent/src/agent.tsL550 | finishRun() | 清理并唤醒等待者cleanup and wake waiters |
| packages/agent/src/agent.tsL355 | reset() | 保留 system 基线keeps the system baseline |
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.
本节沿用第一版的图(原位于总览页),已按 v1.1.0 重新核对。This section keeps its first-edition diagram (formerly on the overview page), re-checked against v1.1.0.
要点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 · v1.1.0
| 文件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 |
钩子调用顺序Hook call order#
一句话结论Takeaway一轮 = 一次 LLM 响应 + 它请求的工具。钩子的调用顺序是固定的:prepareRequest → transformContext → convertToLlm → getApiKey → streamFn,工具侧 prepareArguments → validate → beforeToolCall → execute → afterToolCall,轮末 finishTurn → getSteeringMessages → prepareNextTurn。A turn = one LLM response + the tools it asks for. Hooks fire in a fixed order: prepareRequest → transformContext → convertToLlm → getApiKey → streamFn; for tools prepareArguments → validate → beforeToolCall → execute → afterToolCall; at turn end finishTurn → getSteeringMessages → prepareNextTurn.
要点Key points
transformContext作用于AgentMessage[](可含自定义消息),convertToLlm再把它们转成 pi-ai 的Message[]——压缩、裁剪放前者,格式转换放后者。transformContextworks onAgentMessage[](custom messages included);convertToLlmthen turns them into pi-aiMessage[]— pruning belongs in the former, format conversion in the latter.getApiKey每次请求都调用,让短期 OAuth token 在长时间工具执行期间也能刷新。getApiKeyis called on every request, so short-lived OAuth tokens can refresh during long tool runs.finishTurn返回{ action: "end" }可以在本轮后直接结束(跳过 steering 轮询);prepareNextTurn可以替换模型、工具、上下文。finishTurnreturning{ action: "end" }stops after this turn (skipping the steering poll);prepareNextTurncan swap model, tools or context.- 工具集合变化时,
declareToolChanges在发送前插入带toolsAdded/Removed的 system 消息,保证 transcript 自描述。When the tool set changes,declareToolChangesinserts a system message withtoolsAdded/Removedbefore sending, keeping the transcript self-describing.
工作原理How it works
- 开始:发
agent_start、turn_start,把提示消息作为message_start/end发出。Start: emitagent_start,turn_start, then the prompt messages asmessage_start/end. - 请求:
streamAssistantResponse()依次调用请求侧钩子,把 pi-ai 事件映射为message_*。Request:streamAssistantResponse()runs the request-side hooks and maps pi-ai events ontomessage_*. - 工具:每个 toolCall 经
prepareToolCall()→executePreparedToolCall()→finalizeExecutedToolCall()。Tools: each toolCall goes throughprepareToolCall()→executePreparedToolCall()→finalizeExecutedToolCall(). - 轮末:
turn_end,轮询 steering;有消息或有工具结果就进入下一轮,否则检查 follow-up,最后agent_end。Turn end:turn_end, poll steering; continue if there are messages or tool results, else check follow-ups, thenagent_end.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/agent/src/agent-loop.tsL163 | runLoop() | 内外两层循环the inner and outer loop |
| packages/agent/src/agent-loop.tsL381 | streamAssistantResponse() | 请求侧钩子与事件映射request hooks and event mapping |
| packages/agent/src/agent-loop.tsL333 | declareToolChanges() | 工具变化写入 transcripttool changes into the transcript |
| packages/agent/src/agent-loop.tsL708 | prepareToolCall() | 查找 · 参数 · 校验 · beforeToolCallfind · args · validate · beforeToolCall |
| packages/agent/src/agent-loop.tsL859 | finalizeExecutedToolCall() | afterToolCall 字段合并afterToolCall field merge |
| packages/agent/src/types.tsL155 | FinishTurn | end | continueend | continue |
工具调用执行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.
本节沿用第一版的图(原位于总览页),已按 v1.1.0 重新核对。This section keeps its first-edition diagram (formerly on the overview page), re-checked against v1.1.0.
要点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 · v1.1.0
| 文件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 |
工具批的并发与顺序Tool batches: concurrency and order#
一句话结论Takeaway默认并行:准备阶段按顺序(含 beforeToolCall),执行并发,afterToolCall 后逐个发 tool_execution_end,所有结果到齐再按原顺序写 toolResult。任一工具声明 executionMode: "sequential" 时整批退化为顺序执行。Parallel by default: preparation runs in order (including beforeToolCall), execution is concurrent, tool_execution_end fires as each finalizes, and toolResults are written in source order once all settle. If any tool declares executionMode: "sequential" the whole batch runs sequentially.
要点Key points
- 立即结果(未知工具、参数校验失败、被
beforeToolCall阻止)不进入执行,直接得到 error 结果。Immediate outcomes (unknown tool, failed validation, blocked bybeforeToolCall) skip execution and get an error result directly. - 1.1.0 新增:每个结果带
durationMs(performance.now()计时),出现在AgentToolCallOutcome与tool_execution_end上。New in 1.1.0: every result carriesdurationMs(timed withperformance.now()), onAgentToolCallOutcomeandtool_execution_end. - 只有批内每个最终结果都
terminate: true时,循环才会在本批后停止——一个工具不能单方面结束对话。The loop stops after a batch only if every finalized result hasterminate: true— a single tool can't end the conversation on its own. - 输出被截断(
stopReason: "length")时,不执行任何工具调用,而是为每个调用写入失败结果。When output was truncated (stopReason: "length"), no tool call runs; each gets a failure result instead.
工作原理How it works
executeToolCalls()根据config.toolExecution与各工具的executionMode选择顺序或并行。executeToolCalls()picks sequential or parallel fromconfig.toolExecutionand each tool'sexecutionMode.- 并行:逐个
prepareToolCall(),把可执行的放入Promise.all。Parallel:prepareToolCall()one by one, putting runnable calls intoPromise.all. - 每个完成后
finalizeExecutedToolCall()并立即发tool_execution_end。Each completion is finalized and immediately emitstool_execution_end. - 全部完成后按原顺序
createToolResultMessage()并发出消息事件。After all settle,createToolResultMessage()in source order and emit the message events.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/agent/src/agent-loop.tsL508 | executeToolCalls() | 选择执行模式chooses the execution mode |
| packages/agent/src/agent-loop.tsL586 | executeToolCallsParallel() | 并行批the parallel batch |
| packages/agent/src/agent-loop.tsL530 | executeToolCallsSequential() | 顺序批the sequential batch |
| packages/agent/src/agent-loop.tsL690 | shouldTerminateToolBatch() | 全体 terminate 才停止stop only if all terminate |
| packages/agent/src/agent-loop.tsL478 | failToolCallsFromTruncatedMessage() | 截断时的处理handling truncation |
| packages/agent/src/agent-loop.tsL811 | runToolCall() | 无事件地复用同一管道reuse the pipeline without events |
AgentEvent 事件流The AgentEvent stream#
一句话结论Takeaway所有可观察的行为都是事件:运行、轮次、消息、工具执行四个层次,严格嵌套。pi-ai 的 12 种流事件在这一层被压缩成 message_start / update / end 三种。Everything observable is an event, in four strictly nested levels: run, turn, message, tool execution. pi-ai's 12 stream event types collapse into message_start / update / end here.
要点Key points
message_update同时携带assistantMessageEvent(原始 pi-ai 事件),UI 可以按 delta 精细渲染。message_updatealso carriesassistantMessageEvent(the raw pi-ai event), so a UI can render per delta.- 并行工具模式下
tool_execution_end按完成顺序发出,但 toolResult 消息按调用的原始顺序发出。In parallel tool modetool_execution_endfires in completion order, but toolResult messages are emitted in source order. Agent的归约器在调用订阅者之前更新状态,所以监听器里读agent.state总是最新的。Agent's reducer updates state before calling subscribers, soagent.stateread inside a listener is always current.
工作原理How it works
- 流开始(
start):把 partial 推入上下文并发message_start。Stream start (start): push the partial into context and emitmessage_start. - 每个 delta:用新的 partial 替换上下文末尾,发
message_update。Each delta: replace the context tail with the new partial and emitmessage_update. done/error:取result(),盖上thinkingLevel,发message_end。done/error: takeresult(), stampthinkingLevel, emitmessage_end.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/agent/src/types.tsL516 | AgentEvent | 全部事件联合类型the full event union |
| packages/agent/src/agent.tsL565 | processEvents() | 状态归约器the state reducer |
| packages/agent/src/agent.tsL266 | subscribe() | 按顺序 await 的监听器listeners awaited in order |
| packages/agent/src/agent-loop.tsL153 | createAgentStream() | agentLoop() 的 EventStreamthe EventStream behind agentLoop() |
Steering 与 Follow-upSteering and follow-up#
一句话结论Takeaway运行中用户的新输入有两种语义:steer() 在当前轮工具结束后、下一次请求前插入;followUp() 等到 Agent 本来要停下时才开始新一轮。两个队列都可以一次取一条或全部。New input during a run has two meanings: steer() lands after the current turn's tools and before the next request; followUp() waits until the agent would otherwise stop. Each queue drains one-at-a-time or all at once.
要点Key points
- 默认模式都是
one-at-a-time:每个轮询点只取一条,剩下的留到下一轮,避免一次塞入大量插话。Both default toone-at-a-time: each poll takes one message and leaves the rest for later turns, so a burst of nudges doesn't arrive at once. - steering 有三个轮询点:运行开始、
turn_end之后、prepareNextTurn之后(仅当前一次为空)。Steering is polled at three points: run start, afterturn_end, and afterprepareNextTurn(only if the previous poll was empty). continue()遇到末尾是 assistant 时会先消费 steering,否则消费 follow-up,两者都空则抛错。continue()with an assistant tail consumes steering first, then follow-ups, and throws if both are empty.
工作原理How it works
steer(m)/followUp(m)只是enqueue,不会打断正在进行的流或工具。steer(m)/followUp(m)justenqueue; they never interrupt the in-flight stream or tool.- 循环通过
getSteeringMessages/getFollowUpMessages钩子拉取,Agent用两个私有PendingMessageQueue实现它们。The loop pulls through thegetSteeringMessages/getFollowUpMessageshooks, whichAgentimplements with two privatePendingMessageQueues. - 取到的消息作为 user 消息写入上下文并发出
message_start/end,然后开始新一轮请求。Drained messages enter the context as user messages withmessage_start/end, then a new request starts.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/agent/src/agent.tsL143 | PendingMessageQueue | one-at-a-time | allone-at-a-time | all |
| packages/agent/src/agent.tsL299 | steer() | 运行中插话nudge mid-run |
| packages/agent/src/agent.tsL304 | followUp() | 停下之后再做after it stops |
| packages/agent/src/types.tsL55 | QueueMode | 队列模式queue mode |
StreamFn 与代理StreamFn and the proxy#
一句话结论TakeawayAgent 从不直接依赖某个 provider:它只调用一个 StreamFn。可以传入 models.streamSimple,也可以用 streamProxy 把请求转发到你自己的服务端——客户端从 SSE 事件重建 partial,得到与直连相同的 AssistantMessageEventStream。The agent never depends on a provider directly: it only calls a StreamFn. Pass models.streamSimple, or use streamProxy to forward requests to your own server — the client rebuilds the partial from SSE events and gets the same AssistantMessageEventStream as a direct call.
要点Key points
- 解析顺序:
AgentOptions.streamFn→setDefaultStreamFn()安装的全局默认 → 都没有则抛出 “No default stream function configured”。Resolution:AgentOptions.streamFn→ the global default fromsetDefaultStreamFn()→ otherwise throw “No default stream function configured”. - 1.1.0 破坏性变更:
streamProxy与所有StreamFn必须返回AssistantMessageEventStream(以便统一计时durationMs)。Breaking in 1.1.0:streamProxyand everyStreamFnmust returnAssistantMessageEventStream(sodurationMsis timed uniformly). - 代理只序列化 11 个可序列化选项;服务端发的事件不含 partial,以节省带宽。The proxy serializes only 11 serializable options; server events omit the partial to save bandwidth.
工作原理How it works
streamProxy(model, ctx, { proxyUrl, authToken })向{proxyUrl}/api/stream发 POST。streamProxy(model, ctx, { proxyUrl, authToken })POSTs to{proxyUrl}/api/stream.- 读取 SSE
data:行,processProxyEvent()把每个事件应用到本地 partial 上并推入流。Read SSEdata:lines;processProxyEvent()applies each event to the local partial and pushes it. - HTTP 错误、EOF 无终止事件、abort 都被编码成 error 事件,不会抛出。HTTP errors, EOF without a terminal event, and aborts are all encoded as error events — never thrown.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/agent/src/proxy.tsL107 | streamProxy() | 代理 StreamFnthe proxy StreamFn |
| packages/agent/src/proxy.tsL257 | processProxyEvent() | 客户端重建 partialrebuild the partial client-side |
| packages/agent/src/proxy.tsL91 | buildProxyRequestOptions() | 可序列化选项serializable options |
| packages/agent/src/stream-fn.tsL15 | getDefaultStreamFn() | 未配置时抛错throws when unset |
| packages/agent/src/types.tsL33 | StreamFn | 不抛异常的约定the never-throw contract |
数据类型Data types#
一句话结论TakeawayAgentMessage 是 pi-ai Message 与可声明合并的 CustomAgentMessages 的联合:宿主可以把自己的消息类型(如 artifact、通知)放进会话,只要 convertToLlm 知道如何把它们转成 LLM 消息。AgentMessage is the union of pi-ai Message and the declaration-mergeable CustomAgentMessages: hosts can put their own message kinds (artifacts, notifications) into the session, as long as convertToLlm knows how to turn them into LLM messages.
要点Key points
AgentState的tools与messages赋值时复制数组;pendingToolCalls每次替换为新的Set,方便 UI 做引用比较。AgentState.toolsandmessagescopy on assignment;pendingToolCallsis replaced with a newSeteach time so UIs can compare by reference.AgentTool继承 pi-aiTool,增加label、execute()、可选prepareArguments()与executionMode。AgentToolextends pi-aiToolwithlabel,execute(), optionalprepareArguments()andexecutionMode.AfterToolCallResult按字段合并:替换content但不给structuredContent时,旧的structuredContent会被丢弃,避免两者不一致。AfterToolCallResultmerges field by field: replacingcontentwithoutstructuredContentdrops the oldstructuredContent, so the two never disagree.
工作原理How it works
- 扩展消息类型:
declare module "@earendil-works/pi-agent-core" { interface CustomAgentMessages { … } }。Extend messages:declare module "@earendil-works/pi-agent-core" { interface CustomAgentMessages { … } }. - 工具返回
AgentToolResult<T>:content给模型,details给 UI / 日志。Tools returnAgentToolResult<T>:contentfor the model,detailsfor UI / logs.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/agent/src/types.tsL382 | AgentState | 公开状态public state |
| packages/agent/src/types.tsL365 | CustomAgentMessages | 声明合并扩展点declaration-merge extension point |
| packages/agent/src/types.tsL466 | AgentTool | 工具定义tool definition |
| packages/agent/src/types.tsL424 | AgentToolResult | content + detailscontent + details |
| packages/agent/src/types.tsL449 | AgentToolCallOutcome | 含 durationMswith durationMs |
| packages/agent/src/types.tsL66 | BeforeToolCallResult | block / terminateblock / terminate |
扩展点Extension points#
streamFn替换模型调用:直连、代理、录制回放、测试桩。Swap the model call: direct, proxy, record/replay, test stubs.convertToLlm · transformContext自定义消息 → LLM 消息;压缩、裁剪、注入上下文。Custom messages → LLM messages; compaction, pruning, context injection.beforeToolCall · afterToolCall权限审批、阻止、改写结果、终止。Approval, blocking, rewriting results, terminating.prepareRequest · prepareNextTurn · finishTurn每轮改模型 / 工具 / 选项,决定是否继续。Change model / tools / options per turn and decide whether to continue.getApiKey每次请求取凭据(短期 OAuth token)。Fetch credentials per request (short-lived OAuth tokens).CustomAgentMessages声明合并,增加会话里的消息种类。Declaration merging adds message kinds to the session.AgentTool.prepareArguments · executionMode修正模型给出的参数;强制顺序执行。Repair model-supplied args; force sequential execution.subscribe()按顺序 await 的事件监听器,拿到运行的 AbortSignal。In-order awaited listeners that receive the run's AbortSignal.测试Tests#
一句话结论Takeaway4 个测试文件覆盖循环(钩子顺序、队列、并行工具、截断)、Agent 生命周期、端到端与代理流。Four test files cover the loop (hook order, queues, parallel tools, truncation), the Agent lifecycle, end-to-end runs and the proxy stream.
| 测试文件Test file | 用例cases |
|---|---|
| packages/agent/test/agent-loop.test.ts | 36 |
| packages/agent/test/agent.test.ts | 34 |
| packages/agent/test/e2e.test.ts | 10 |
| packages/agent/test/proxy.test.ts | 3 |
用例数 = 源码中 it( / test( 的出现次数(静态统计,不运行测试)。Cases = occurrences of it( / test( in the source (static count; tests were not run).