概述
Pi Coding Agent 的扩展系统位于 F:\Pi\packages\coding-agent\src\core\extensions\,包含 5 个 TypeScript 文件,构成了一个完整的 生命周期事件驱动 + 自定义工具注册 的扩展框架。
扩展系统允许第三方模块:
- 订阅 agent 生命周期事件(会话开始、agent 循环、消息流、工具执行等)
- 注册 LLM 可调用的自定义工具
- 注册命令、键盘快捷键、CLI 标志
- 通过 UI 基元与用户交互(选择、确认、输入、通知)
- 注册/覆盖模型 provider
- 提供自定义消息渲染器
架构总览 — 依赖与抽象关系
文件的职责定位
| 文件 | 行数 | 角色 | 核心职责 |
|---|---|---|---|
types.ts | ~1615 行 | 类型定义层 (Foundation) | 定义所有类型接口、事件结构、API 契约 |
loader.ts | ~500 行 | 加载层 (Loader) | 从文件系统发现并加载扩展模块,创建运行时骨架 |
runner.ts | ~750 行 | 运行时层 (Runner) | 管理扩展生命周期、事件分发、上下文创建 |
wrapper.ts | ~30 行 | 适配层 (Adapter) | 将扩展工具定义包装为 AgentTool |
index.ts | ~80 行 | 入口层 (Barrel) | 统一重导出公共 API,隐藏内部实现 |
依赖关系图 (引用链):
types.ts ←───────────┬─── loader.ts
├─── runner.ts
├─── wrapper.ts
└─── index.ts
runner.ts ←────────── wrapper.ts
types.ts ←────────── index.ts
loader.ts ←────────── index.ts
runner.ts ←────────── index.ts
wrapper.ts ←────────── index.ts
关键设计决策:
- loader.ts 和 runner.ts 互不依赖 — 加载器和运行时是解耦的,通过
ExtensionRuntime接口桥接 - wrapper.ts 依赖 runner.ts — 包装器需要 runner 的
createContext()来传递一致的上下文 - index.ts 不导出 loader.ts 的内部函数(如
loadExtensionModule) — 只导出公共 API
外部包类型详解
扩展系统引用了 4 个外部包和多个内部模块的类型。本节逐一说明每个外部包的核心类型及其在扩展系统中的用途。
@earendil-works/pi-agent-core — Agent 核心类型
这是 agent 循环的底层核心包,定义了消息、工具执行、thinking 等基础类型。
| 类型 | 定义 | 用途说明 |
|---|---|---|
AgentMessage | LLM 与系统之间的消息单元(包含 role、content、tool_calls 等字段) | 在 ContextEvent 中传递用户对话消息给扩展修改;在 AgentEndEvent 中暴露最终消息列表;作为 ExtensionAPI.sendUserMessage() 的参数基础 |
AgentToolResult<TDetails> | 工具执行结果类型,泛型参数 TDetails 携带工具特定的详细信息 | ToolDefinition.execute() 的返回值类型,扩展必须返回此类型的结果 |
AgentToolUpdateCallback<TDetails> | 工具执行过程中的流式更新的回调函数类型 | 传递给 ToolDefinition.execute() 的 onUpdate 参数,扩展可调用它推送增量结果 |
ThinkingLevel | 模型思考级别(通常为 none、low、medium、high 等枚举) | 用于 ThinkingLevelSelectEvent 事件,以及 ExtensionAPI.getThinkingLevel() / setThinkingLevel() 的返回值/参数类型 |
ToolExecutionMode | 工具执行模式:"sequential" 或 "parallel" | 在 ToolDefinition.executionMode 字段中指定单工具的并行策略覆盖 |
AgentTool | agent-core 可执行工具接口(包含 execute() 方法) | wrapper.ts 的输出目标类型 — 扩展的 ToolDefinition 最终被包装为此类型供 agent-core 调用 |
在扩展系统中的流转路径:
ToolDefinition.execute() → AgentToolResult
↓ (wrapper.ts 包装)
AgentTool ←── ToolDefinition 适配
↓ (agent-core 调度)
AgentMessage ←── LLM 调用结果
@earendil-works/pi-ai — AI Provider 类型
定义 AI 模型的接口、消息内容的格式、OAuth 认证等。
| 类型 | 定义 | 用途说明 |
|---|---|---|
Api | API 类型标识字符串(如 "anthropic-messages"、"openai-chat"、"openai-responses" 等) | 在 ProviderConfig.api 和 ProviderModelConfig.api 中指定模型的 API 协议类型 |
Model<Api> | 模型的完整配置(包括 id、name、baseUrl、contextWindow、maxTokens、cost、input capabilities 等),泛型 Api 标记协议类型 | ExtensionContext.model 获取当前模型;ExtensionAPI.setModel() 的参数;ModelSelectEvent 的事件负载 |
AssistantMessageEvent | 流式助手消息中的一个事件(token delta、content block start/end、thinking delta 等) | 在 MessageUpdateEvent.assistantMessageEvent 中以流式增量形式传递,扩展可以逐帧观察 AI 输出 |
AssistantMessageEventStream | AsyncIterable<AssistantMessageEvent> 异步可迭代流 | ProviderConfig.streamSimple() 的返回值类型,用于自定义 API 的流处理器 |
Context | AI provider 上下文(包含消息历史、系统提示、配置等) | ProviderConfig.streamSimple() 的参数之一,提供执行上下文 |
TextContent | 文本内容块({ type: "text", text: string }) | 在 InputEvent.images 的替代内容中、ToolResultEvent.content 中、ExtensionAPI.sendUserMessage() 的参数中出现 |
ImageContent | 图片内容块({ type: "image", source: { type, data } }) | 用户输入中的图片附件;BeforeAgentStartEvent.images 中传递;InputEvent.images 中传递 |
ToolResultMessage | LLM 使用的工具结果消息格式 | TurnEndEvent.toolResults 的类型,包含该轮所有工具执行的结果数组 |
OAuthCredentials | OAuth 凭据存储类型(包含 access token、refresh token、到期时间等) | ProviderConfig.oauth.login() 的返回值类型,扩展的 OAuth 流程返回此类型 |
OAuthLoginCallbacks | OAuth 登录流程中提供给扩展的回调集(如 openUrl(url)) | ProviderConfig.oauth.login(callbacks) 的参数,扩展用它触发浏览器登录 |
SimpleStreamOptions | 简单流处理器的配置选项(如 signal 中止信号、onEvent 等) | ProviderConfig.streamSimple() 的可选参数 |
在扩展系统中的流转路径:
ProviderConfig.streamSimple → AssistantMessageEventStream
↓ (扩展注册 provider)
ModelRegistry ──持有──→ Model<Api> 列表
↓ (用户选择模型)
ExtensionContext.model ──→ Model<Api> | undefined
@earendil-works/pi-tui — 终端 UI 组件类型
定义了 TUI(终端用户界面)的全部组件系统,扩展用它构建自定义 UI。
| 类型 | 定义 | 用途说明 |
|---|---|---|
Component | 基础 UI 组件接口(包含 render()、onMount()、onUnmount() 等方法) | ExtensionUIContext.setWidget()、setFooter()、setHeader()、custom() 的组件工厂返回值;ToolDefinition.renderCall() / renderResult() 的返回值 |
TUI | TUI 核心接口(管理事件循环、屏幕渲染、图层等) | 传递给组件工厂(如 setFooter((tui, theme, data) => Component)),扩展可用它操作终端 |
KeyId | 键盘按键标识符字符串类型(如 "ctrl-p"、"escape"、"enter" 等) | ExtensionAPI.registerShortcut(shortcut, options) 的第一个参数;ExtensionShortcut.shortcut 字段的类型 |
AutocompleteItem | 自动补全建议项(包含 label、description、insertText 等) | RegisteredCommand.getArgumentCompletions() 的返回值元素类型 |
AutocompleteProvider | 自动补全提供者接口(getSuggestions(prefix) 等) | AutocompleteProviderFactory 的输入/输出类型 — 扩展可以包装当前 provider 叠加自定义补全 |
EditorComponent | 编辑器组件接口(继承 Component,添加 getText()、setText()、handleInput() 等) | EditorFactory 的工厂返回值类型 |
EditorTheme | 编辑器主题配置(包含颜色、边框样式、选中高亮等) | 传递给 EditorFactory(tui, theme, keybindings),扩展用它自定义编辑器外观 |
OverlayHandle | Overlay 控制器(包含 close()、update() 等方法) | ExtensionUIContext.custom() 的 onHandle 回调参数,扩展可用它控制 overlay 的显示/关闭 |
OverlayOptions | Overlay 配置选项(包含位置、尺寸、是否模态等) | ExtensionUIContext.custom() 的 overlayOptions 字段类型 |
组件工厂的数据流:
扩展调用 setWidget(key, factory)
↓
TUI 运行时调用 factory(tui, theme)
↓
Component 对象 (render, onMount, onUnmount)
↓
TUI 渲染到终端屏幕
typebox — 运行时类型校验
TypeBox 是一个 TypeScript 优先的运行时类型校验库,用于工具参数的 schema 声明与校验。
| 类型 | 定义 | 用途说明 |
|---|---|---|
TSchema | 所有 TypeBox schema 的基类/接口 | ToolDefinition.parameters: TParams 的泛型约束 TParams extends TSchema,确保参数是合法的 TypeBox schema |
Static<T> | 从 TypeBox schema T 中推断出其对应的静态 TypeScript 类型 | ToolDefinition.prepareArguments() 的返回值类型 Static<TParams>,确保参数预处理函数返回符合 schema 的数据 |
使用示例(在扩展代码中):
| |
内部模块引用
除了外部包,扩展系统还引用了大量内部模块的类型。这些是 Pi Coding Agent 的内部子系统:
| 源文件 | 导入类型 | 在扩展系统中的角色 |
|---|---|---|
../../config.ts | CONFIG_DIR_NAME, getAgentDir, isBunBinary | 加载器获取配置目录名称(.pi)、agent 目录路径,判断是否为 Bun 二进制运行模式 |
../event-bus.ts | EventBus | 扩展间通信的事件总线,ExtensionAPI.events 的类型 |
../exec.ts | ExecOptions, ExecResult, execCommand | ExtensionAPI.exec() 的执行引擎 |
../session-manager.ts | SessionManager, ReadonlySessionManager, BranchSummaryEntry, CompactionEntry, SessionEntry | 会话存储与导航,ExtensionContext.sessionManager 的类型 |
../model-registry.ts | ModelRegistry | 模型注册中心,管理所有 provider 的模型列表 |
../keybindings.ts | KeybindingsManager, AppKeybinding | 快捷键管理系统 |
../system-prompt.ts | BuildSystemPromptOptions | 系统提示的构建选项 |
../source-info.ts | SourceInfo, createSyntheticSourceInfo | 记录扩展/工具的来源元数据(文件路径、是否是内置等) |
../messages.ts | CustomMessage | 自定义消息类型,用于 ExtensionAPI.sendMessage() |
../tools/index.ts | BashToolDetails, ReadToolDetails, EditToolDetails 等 | 内置工具的详情类型,用于事件中的类型细化 |
../tools/bash.ts | BashOperations | UserBashEventResult.operations 的类型 |
../compaction/index.ts | CompactionPreparation, CompactionResult | 会话压缩的事件参数类型 |
../footer-data-provider.ts | ReadonlyFooterDataProvider | 底部栏数据提供者 |
../slash-commands.ts | SlashCommandInfo | 斜杠命令信息 |
../../modes/interactive/theme/theme.ts | Theme | 交互模式的主题类型 |
../diagnostics.ts | ResourceDiagnostic | 资源诊断信息(快捷键冲突、命令问题等) |
文件一:types.ts — 类型定义 (Foundation Layer)
角色与位置
这是整个扩展系统的类型基础。所有其他文件都从它导入类型。它定义了扩展系统的全部契约:事件结构、API 接口、工具定义、配置类型、上下文接口等。
导入分析
| |
抽象依赖:types.ts 引用了 4 个外部包(
pi-agent-core、pi-ai、pi-tui、typebox)和约 15 个内部模块。这体现了扩展系统位于应用架构的上层 — 它需要理解底层所有子系统的类型才能提供统一的扩展 API。
外部包类型逐项详解
下面的表格展示了每个外部类型的具体使用位置和功能含义,以及它们在扩展生命周期中的流转路径。
@earendil-works/pi-agent-core — Agent 核心包导入项:
| 类型 | 在扩展系统中的具体使用位置 | 作用描述 |
|---|---|---|
AgentMessage | ContextEvent.messages, AgentEndEvent.messages, MessageEndEvent.message, ExtensionAPI.sendUserMessage() | Agent 循环中的核心消息单元。包含 role(user/assistant/tool)、content、tool_calls 等字段。扩展可以通过 context 事件修改消息列表 |
AgentToolResult<TDetails> | ToolDefinition.execute() 返回值 | 工具执行的结果包装类型,包含 content(TextContent/ImageContent 数组)、isError(boolean)、details(TDetails 工具特定详情)三个字段 |
AgentToolUpdateCallback<TDetails> | ToolDefinition.execute() 的 onUpdate 参数 | 工具执行过程中推送增量结果的回调。扩展在长时间运行的工具(如文件搜索、API 调用)中可以多次调用它 |
ThinkingLevel | ThinkingLevelSelectEvent.level, ExtensionAPI.getThinkingLevel() / setThinkingLevel() | 模型思考级别的枚举。控制模型在生成回答前进行内部推理的深度。典型值:none、low、medium、high |
ToolExecutionMode | ToolDefinition.executionMode | 字面量联合类型 `“sequential” |
AgentTool | wrapper.ts 的输出目标 | agent-core 的可执行工具接口。ToolDefinition 经过 wrapper.ts 包装后最终成为 AgentTool,被 agent-core 调度执行 |
在扩展系统中的流转路径:
ToolDefinition.execute() → AgentToolResult
↓ (wrapper.ts 包装)
AgentTool ←── ToolDefinition 适配
↓ (agent-core 调度执行)
AgentMessage ←── LLM 调用结果中嵌入
@earendil-works/pi-ai — AI Provider 包导入项:
| 类型 | 在扩展系统中的具体使用位置 | 作用描述 |
|---|---|---|
Api | ProviderConfig.api, ProviderModelConfig.api | 标识 API 协议类型的字符串。常见值:"anthropic-messages"、"openai-chat"、"openai-responses" 等 |
Model<Api> | ExtensionContext.model, ModelSelectEvent.model/previousModel, ExtensionAPI.setModel() | 模型的完整配置对象。包含 id、name、baseUrl、contextWindow、maxTokens、cost(input/output/cacheRead/cacheWrite)、input(支持的输入类型 text/image)等 |
AssistantMessageEvent | MessageUpdateEvent.assistantMessageEvent | AI 流式响应中的增量事件联合体。包含 content_block_delta(文本 delta)、thinking_delta(思维链 delta)、content_block_start/stop 等多种变体 |
AssistantMessageEventStream | ProviderConfig.streamSimple() 返回值 | AsyncIterable<AssistantMessageEvent> 异步可迭代流。扩展实现自定义 API 时将任意协议的流式响应包装为此类型 |
Context | ProviderConfig.streamSimple() 的 context 参数 | AI 会话上下文,包含消息历史、系统提示、模型配置、中止信号等。由系统传入,扩展不需要构造 |
ImageContent | InputEvent.images, BeforeAgentStartEvent.images, ExtensionAPI.sendUserMessage() content | 多模态消息中的图片内容块。结构为 `{ type: “image”, source: { type: “base64” |
TextContent | ToolResultEvent.content, InputEvent 文本内容 | 消息中的纯文本内容块,结构为 { type: "text", text: string } |
ToolResultMessage | TurnEndEvent.toolResults | LLM 接收的工具结果消息格式。包含 role: “tool”、tool_call_id、content(执行结果)等字段 |
OAuthCredentials | ProviderConfig.oauth.login() 返回值 | OAuth 认证凭据存储对象。包含 access(访问令牌)、refresh(刷新令牌)、expiresAt(过期时间)等,由系统加密持久化 |
OAuthLoginCallbacks | ProviderConfig.oauth.login(callbacks) 参数 | OAuth 登录流程回调集。openUrl(url) 打开浏览器;pollForCallback() 轮询等待回调 |
SimpleStreamOptions | ProviderConfig.streamSimple() 可选参数 | 简单流处理器的配置选项,包含 signal(AbortSignal 中止信号)、onEvent 等 |
在扩展系统中的流转路径:
ProviderConfig.streamSimple → AssistantMessageEventStream
↓ (扩展注册 provider)
ModelRegistry ──持有──→ Model<Api>[] 列表
↓ (用户选择模型)
ExtensionContext.model ──→ Model<Api> | undefined
↓ (LLM 调用)
AssistantMessageEventStream ──stream──→ AssistantMessageEvent
@earendil-works/pi-tui — 终端 UI 组件包导入项:
| 类型 | 在扩展系统中的具体使用位置 | 作用描述 |
|---|---|---|
Component | ToolDefinition.renderCall/renderResult 返回值;setWidget/setFooter/setHeader/custom 的工厂返回值 | 终端 UI 组件的基接口,包含 render() 渲染方法、onMount/onUnmount 生命周期钩子。所有 UI 构建的最终产物 |
TUI | 组件工厂的参数,如 setFooter((tui, theme, data) => Component) | TUI 核心接口,扩展可通过它操作终端屏幕、注册图层、控制渲染循环、获取终端尺寸 |
KeyId | ExtensionAPI.registerShortcut(shortcut);ExtensionShortcut.shortcut | 标准化键盘按键标识符。格式如 "ctrl-p"、"alt-enter"、"escape" 等,全部小写 |
AutocompleteItem | RegisteredCommand.getArgumentCompletions() 返回值元素 | 自动补全建议项,包含 label(显示文本)、description(右侧描述)、insertText(选中后插入文本)等字段 |
AutocompleteProvider | AutocompleteProviderFactory 输入/输出类型 | 自动补全提供者接口(getSuggestions(prefix) 方法)。扩展可通过 addAutocompleteProvider() 包装它叠加自定义补全 |
EditorComponent | EditorFactory 返回值 | 编辑器组件接口,继承 Component。额外提供 getText()、setText()、handleInput(data) 等文本编辑方法 |
EditorTheme | EditorFactory(tui, theme, keybindings) 参数 | 编辑器的主题配置,包含边框样式(border)、选中颜色(selectionColor)、自动补全颜色(completionColor)等 |
OverlayHandle | ExtensionUIContext.custom() 的 onHandle 回调参数 | 浮层控制器,提供 close() 关闭方法、update(options) 更新方法,用于动态控制浮层 |
OverlayOptions | ExtensionUIContext.custom() 的 overlayOptions | 浮层的位置和大小配置。支持静态对象或动态函数 () => OverlayOptions(用于跟随光标等动态场景) |
组件工厂的数据流:
扩展调用 setWidget(key, factory)
↓
TUI 系统在适当时机调用 factory(tui, theme)
↓
Component 对象 { render(), onMount(), onUnmount(), dispose()? }
↓
TUI 渲染引擎调用 render() 输出到终端
typebox — 运行时类型校验库导入项:
| 类型 | 在扩展系统中的具体使用位置 | 作用描述 |
|---|---|---|
Static<T> | ToolDefinition.prepareArguments() 的返回值类型标注 Static<TParams> | 元编程类型工具:从 TypeBox schema T 推断对应的静态 TypeScript 类型。如 Static<typeof Type.Object({ name: Type.String() })> 结果是 { name: string } |
TSchema | ToolDefinition 泛型约束 TParams extends TSchema | 所有 TypeBox schema 的基接口,确保 ToolDefinition.parameters 字段是合法的 schema |
使用示例(在扩展代码中):
| |
内部模块引用分析
types.ts 中的内部模块导入提供了对其他 Pi Coding Agent 子系统的类型引用:
| 源文件 | 导入类型 | 在扩展系统中的具体使用 | 该类型的含义 |
|---|---|---|---|
../../modes/interactive/theme/theme.ts | Theme | ExtensionUIContext.theme;setTheme()/getTheme() | 完整主题配置,包含颜色、边框、组件样式等 |
../event-bus.ts | EventBus | ExtensionAPI.events 的类型 | 扩展间通信的事件总线,提供 on/emit/off 方法 |
../exec.ts | ExecOptions, ExecResult | ExtensionAPI.exec() 的参数和返回值 | 系统命令执行的选项(cwd、timeout、env)和结果(exitCode、stdout、stderr) |
../session-manager.ts | SessionManager, ReadonlySessionManager, SessionEntry, BranchSummaryEntry, CompactionEntry | ExtensionContext.sessionManager;SessionCompactEvent 事件负载 | 会话存储与导航系统,管理对话历史的读写、分支、压缩、持久化 |
../model-registry.ts | ModelRegistry | ExtensionContext.modelRegistry | 模型注册中心,管理所有 provider 注册的模型列表 |
../keybindings.ts | KeybindingsManager, AppKeybinding | setEditorComponent 的 keybindings 参数;重导出类型 | 快捷键管理系统,管理绑定、冲突解决、事件分发 |
../system-prompt.ts | BuildSystemPromptOptions | ExtensionCommandContext.getSystemPromptOptions();BeforeAgentStartEvent 字段 | 系统提示构建选项,包含 cwd、active tools、session info 等 |
../source-info.ts | SourceInfo | Extension.sourceInfo;RegisteredTool.sourceInfo | 来源元数据:标记扩展/工具来自哪个文件,是内置还是用户安装 |
../messages.ts | CustomMessage | ExtensionAPI.sendMessage() 的消息体 | 自定义消息结构:customType(类型标识)、content(内容)、display(显示选项)、details(附加数据) |
../tools/index.ts | 各内置工具详情和输入类型 | ToolCallEvent/ToolResultEvent 的细化类型字段 | 内置工具的类型声明,用于事件处理器中对特定工具的类型细化 |
../tools/bash.ts | BashOperations | UserBashEventResult.operations | bash 操作抽象接口,提供 exec/execScript/spawn 等方法 |
../compaction/index.ts | CompactionPreparation, CompactionResult | SessionBeforeCompactEvent 准备数据;SessionCompactEvent 结果 | 会话压缩的准备数据和结果,包含条目列表、摘要文本等 |
../footer-data-provider.ts | ReadonlyFooterDataProvider | setFooter() 中传递给组件工厂的参数 | 底部栏数据提供者,提供 git 分支、扩展状态等只读数据 |
../bash-executor.ts | BashResult | UserBashEventResult.result | bash 命令执行的完整结果:exitCode、stdout、stderr、cwd |
多层抽象:内部模块引用体现了扩展系统与内部实现解耦的设计。扩展系统不直接依赖这些模块的实现,只依赖它们的类型接口。这使得两者可以独立演进。
逐段解读
段 1:UI 上下文 (ExtensionUIContext)
| |
功能:定义了扩展与用户交互的全部 UI 基元。每个运行模式(TUI、RPC、print)提供自己的实现。
关键方法解析:
| 方法 | 签名 | 功能说明 |
|---|---|---|
select | (title, options, opts) => Promise<string | undefined> | 显示选择器,返回用户选中的选项 |
confirm | (title, message, opts) => Promise<boolean> | 确认对话框 |
input | (title, placeholder, opts) => Promise<string | undefined> | 文本输入框 |
notify | (message, type) => void | 推送通知(info/warning/error) |
onTerminalInput | (handler) => () => void | 监听原始终端输入,返回取消函数 |
setWidget | 两个重载:字符串数组版 / 组件工厂版 | 在编辑器上方或下方显示组件 |
setFooter | (factory | undefined) => void | 设置自定义底部组件 |
setHeader | (factory | undefined) => void | 设置自定义顶部组件 |
custom | (factory, options) => Promise<T> | 显示自定义组件,支持 overlay 模式 |
addAutocompleteProvider | (factory) => void | 叠加自动补全行为 |
setEditorComponent | (factory | undefined) => void | 替换编辑器组件(如 Vim 编辑器) |
设计模式:
ExtensionUIContext使用了策略模式 — 统一接口,不同模式提供不同实现。noOpUIContext(在 runner.ts 中)是空操作的缺省实现。
段 2:扩展上下文 (ExtensionContext)
| |
抽象与实现:
ExtensionContext定义了扩展在事件处理中能访问的只读环境。它的所有 getter 和方法最终都委托到ExtensionRunner的内部函数(如getModel、isIdleFn),这些函数通过bindCore()注入。
ExtensionCommandContext 继承了 ExtensionContext 并额外提供:
getSystemPromptOptions()— 系统提示构建选项waitForIdle()— 等待 agent 空闲newSession()、fork()、navigateTree()、switchSession()、reload()— 会话控制方法
安全设计:命令上下文只能在用户发起的命令中安全使用,因为它包含会话控制能力。
段 3:工具定义 (ToolDefinition)
| |
语法要点 — 泛型约束:
TParams extends TSchema— 参数必须符合 TypeBox schema 类型TDetails = unknown— 工具执行的详细结果类型(默认为 unknown)TState = any— 渲染状态类型
defineTool() 是一个类型辅助函数,用于在变量赋值时保留参数类型推断:
| |
功能:不使用
defineTool()时,TypeScript 会将放到数组中的ToolDefinition的params收窄为unknown。defineTool()返回交叉类型& AnyToolDefinition,既保留具体类型信息,又兼容any参数位置。
段 4:事件体系
事件系统定义了三层结构:
4.1 事件类型 — 按领域分组:
| 事件组 | 事件列表 | 触发时机 |
|---|---|---|
| Startup/Resource | project_trust, resources_discover | 启动时、重载时 |
| Session | session_start, session_before_switch, session_before_fork, session_before_compact, session_compact, session_shutdown, session_before_tree, session_tree | 会话生命周期 |
| Agent | context, before_provider_request, after_provider_response, before_agent_start, agent_start, agent_end | Agent 循环 |
| Turn/Message | turn_start, turn_end, message_start, message_update, message_end | 每次交互回合 |
| Tool | tool_execution_start, tool_execution_update, tool_execution_end, tool_call, tool_result | 工具执行 |
| Model | model_select, thinking_level_select | 模型切换 |
| Input | input | 用户输入 |
| User Bash | user_bash | 用户执行 bash 命令 |
4.2 事件结果类型 — 每个事件有对应的结果类型:
| |
设计模式:事件结果的
?可选字段允许扩展选择性地干涉,不影响正常流程。Result 类型体现的是观察者模式的增强变体 — 观察者不仅可以观察,还可以修改或阻止事件。
4.3 类型守卫函数
| |
语法要点 — 函数重载 + 类型守卫:
- 内置工具的
isToolCallEventType不需要类型参数,自动窄化- 自定义工具需要显式泛型参数:
isToolCallEventType<"my_tool", MyInput>("my_tool", event)- 重载签名确保了类型安全;实现签名使用
boolean返回
段 5:ExtensionAPI(扩展的入口对象)
| |
抽象与实现:
ExtensionAPI是扩展作者看到的pi对象。它的on()、registerTool()等注册方法将数据写入Extension对象的 collections(handlersMap、toolsMap 等)。动作方法(sendMessage()、setModel()等)委托给共享的ExtensionRuntime。
段 6:Provider 注册类型
| |
功能:允许扩展注册自定义模型 provider,支持:
- 纯 URL 覆盖(代理转发)
- 完整模型列表替换
- OAuth 认证流程
streamSimple自定义 API 兼容层
段 7:运行时和加载状态类型
| |
抽象关系:
ExtensionRuntime是共享单例(所有扩展共用),而Extension是按扩展实例化(每个加载的扩展有一个对象存储其注册内容)。这是关键的设计区分。
文件二:loader.ts — 加载器 (Loader Layer)
角色与位置
将 TypeScript/JavaScript 文件加载为扩展模块,创建 Extension 对象和共享的 ExtensionRuntime。不涉及事件分发或生命周期管理。
导入分析
| |
语法要点 — Bundled Imports 与 virtualModules:
import * as _bundledXxx使用import * as命名空间导入,将所有导出聚合为一个对象- 这些前缀
_bundled的导入必须是静态的,以便 Bun 打包时包含到二进制文件中VIRTUAL_MODULES对象映射包名到运行时对象,jiti 的virtualModules选项使它们在扩展代码中进行import时可用
逐段解读
段 1:VIRTUAL_MODULES 与 getAliases()
| |
功能:提供两种模块解析方式:
- Bun 二进制模式 (生产):
virtualModules— 预打包的模块直接注入,无文件系统解析- Node.js 开发模式:
aliases— 通过 jiti 的alias选项映射到node_modules路径- 两种模式都包含旧命名空间
@mariozechner/*的兼容映射
getAliases() 函数:
| |
语法要点 —
import.meta.resolve():
- 返回模块说明符在运行时的绝对路径(Node.js 21+ / Bun)
- 与
require.resolve()功能相似,但用于 ESM 环境
抽象与实现:
resolveWorkspaceOrImport()实现了工作区优先策略 — 在 monorepo 开发环境中优先使用本地构建产物,否则回退到node_modules。
段 2:createExtensionRuntime() — 运行时骨架
| |
设计模式:Two-Phase Initialization(两阶段初始化):
- 加载阶段:
createExtensionRuntime()创建运行时,所有动作方法都是抛出错误的桩函数- 绑定阶段:
runner.bindCore()用真实实现替换这些桩函数这种方式允许扩展在加载期间安全地调用注册方法(如
registerTool()),但动作方法(如sendMessage())只能在绑定后调用。
pendingProviderRegistrations 队列的作用:
- 加载期间扩展调用
registerProvider()→ 入队 bindCore()时统一刷新 → 调用modelRegistry.registerProvider()- 绑定后
registerProvider被替换为直接调用 → 立即生效
段 3:createExtensionAPI() — API 包装
| |
关键安全设计:每个
api方法调用前都会runtime.assertActive(),检测当前扩展是否已失效(因会话替换或重载导致)。
功能边界:
registerTool()写入extension.toolsMap 并调用runtime.refreshTools()通知系统更新;sendMessage()直接委托给运行时;getFlag()限制在扩展自己注册的标志范围内。
段 4:loadExtensionModule() — 模块加载
| |
语法要点 — jiti 的使用:
createJiti(import.meta.url)— 创建一个能直接运行 TypeScript 的运行时jiti.import(path, { default: true })— 加载模块并提取 default 导出moduleCache: false— 禁用 jiti 的模块缓存,因为 loader 有自己的缓存层tryNative: false— Bun 模式下禁用原生解析,完全使用 virtualModules
段 5:扩展发现系统
| |
抽象与实现:扩展发现实现了三层路径合并策略:
- 项目本地 → 全局 → 显式配置
- 去重保护(
seenSet)防止路径冲突discoverExtensionsInDir()是核心发现函数,支持文件、子目录、package.json 清单三种形式
段 6:缓存机制
| |
设计:缓存与工作目录绑定。切换工作目录自动清空缓存。
extensionCacheGeneration用作版本戳,配合isCurrentCacheToken()检查缓存是否有效。
文件三:runner.ts — 运行器 (Runtime Layer)
角色与位置
这是扩展系统的心脏。ExtensionRunner 类管理扩展生命周期、事件分发、上下文创建、快捷键解析和工具注册汇总。
导入分析
| |
引用关系:runner.ts 大量引用 types.ts 的类型定义,但不引用 loader.ts。这意味着 runner 只关心"已经加载好的扩展",不关心它们是如何加载的。
逐段解读
段 1:快捷键冲突检测
| |
功能:定义一组保留快捷键,扩展不能覆盖。这防止了扩展劫持核心功能(如 Ctrl+C 中断、提交输入等)。
buildBuiltinKeybindings() 将 KeybindingsConfig 解析为按键查找表:
| |
抽象与实现:当多个 action 绑定同一个键时,保留 action 优先。这通过
restrictOverride标志实现。
段 2:ExtensionRunner 类核心结构
| |
设计模式:Strategy Pattern + Dependency Injection — Runner 的所有行为依赖都是可替换的私有函数字段。外部通过
bindCore()、bindCommandContext()、setUIContext()注入具体实现。
段 3:bindCore() — 核心绑定
| |
关键时序:
- 加载阶段:
registerProvider()入队pendingProviderRegistrations- 绑定阶段:
bindCore()统一刷新所有排队注册- 绑定后:
registerProvider()被替换为直接调用modelRegistry.registerProvider(),立即生效
段 4:上下文创建
| |
语法要点 — 惰性 getter (Lazy Property Descriptors):
get ui()是Object.defineProperty的简写语法- 每次访问都重新求值,反映运行时的最新状态
- 所有访问都经过
assertActive()检查
与
createCommandContext()的区别:
createContext()返回ExtensionContext— 用于事件处理和工具执行createCommandContext()使用Object.defineProperties()+Object.getOwnPropertyDescriptors()继承createContext()的所有惰性 getter,再添加命令专用方法(waitForIdle()、newSession()等)
| |
语法要点 —
Object.getOwnPropertyDescriptors():
- 复制
createContext()返回对象的所有属性描述符(包括 getter)- 这是正确复制惰性 getter 的唯一方式;普通展开
{...obj}会求值 getter
段 5:事件分发系统
通用事件分发(emit()):
| |
设计模式:责任链模式 — 每个扩展依次处理事件,session_before_* 事件在处理返回 cancel 时提前终止。
专用事件分发(类型安全版本):
| |
功能:
message_end处理器可以修改最终消息内容。要求返回的消息必须保持相同角色(role),防止注入攻击。
| |
链式修改:
tool_result处理器可以逐步修改结果,每个处理器在之前处理器修改的基础上继续。多个扩展可以合作修改同一个工具结果。
| |
阻断机制:
tool_call处理器可以返回{ block: true }阻止工具执行。event.input是可变引用,处理器可以原地修改参数。
| |
三态结果:输入事件支持三种结果:
"continue"— 扩展未修改输入,按原样处理"transform"— 扩展修改了输入文本或图片"handled"— 扩展完全处理了输入,后续停止处理 “handled” 会短路径终止,“transform” 会链式累积修改。
段 6:工具注册管理
getAllRegisteredTools() — 将各扩展工具按名称去重合并:
| |
设计决策:同名工具先注册者获胜。这意味着内置工具优先于扩展工具,第一个加载的扩展优先于后续加载的。
段 7:命令去重
| |
抽象与实现:当多个扩展注册同名命令时,
invocationName自动生成带序号的后缀(如mycommand:1、mycommand:2),避免冲突。
段 8:失效机制 (Stale Invalidation)
| |
功能:会话切换、fork、重载后,旧的
ExtensionRunner实例被标记为 stale。所有后续操作抛出清晰的错误,防止使用已过期的上下文。错误消息指导用户将工作移到withSession回调中。
文件四:wrapper.ts — 包装器 (Adapter Layer)
角色与位置
将扩展定义的 ToolDefinition 转换为 agent-core 可执行的 AgentTool。这是适配器层的唯一文件,只有 30 行。
完整代码
| |
逐行解读
| 行 | 代码 | 含义 |
|---|---|---|
| 1-2 | 导入 AgentTool | agent-core 的类型,表示 LLM 可调用的工具 |
| 3 | 导入 wrapToolDefinition | tool-definition-wrapper.ts 中的通用包装函数 |
| 4-5 | 导入 ExtensionRunner 和 RegisteredTool | 本模块的类型依赖 |
| 7-9 | wrapRegisteredTool() | 将单个 RegisteredTool 包装为 AgentTool。第二个参数是一个惰性上下文工厂 () => runner.createContext() |
| 11-16 | wrapRegisteredTools() | 批量包装。调用 wrapToolDefinitions()(带 s 的复数版本),传入定义数组和共享工厂 |
关键设计 — 惰性上下文工厂:
() => runner.createContext()不是直接传递上下文对象,而是传递一个在工具执行时才调用的工厂- 这确保了
ExtensionContext在工具实际运行时反映最新状态(如当前工作目录、model、信号等)- 也避开了扩展生命周期问题:即使 runner 状态在注册后发生了变化,执行时仍能访问最新状态
引用与被引用
- 引用
runner.ts的ExtensionRunner— 需要 runner 的createContext()方法 - 引用
types.ts的RegisteredTool— 包装器的输入类型 - 引用
../tools/tool-definition-wrapper.ts— 实际的包装逻辑 - 被引用
index.ts重导出两个函数
抽象层次:wrapper.ts 是非常薄的一层适配,核心逻辑在
tool-definition-wrapper.ts中。这里只做 “如何获取上下文” 的决策。
文件五:index.ts — 入口 (Barrel Layer)
角色与位置
作为包的公共入口点,统一重导出所有需要暴露的类型和函数。这是唯一的公共 API 表面。
代码结构分析
重导出 loader.ts 的函数:
| |
注意:loader.ts 的 loadExtensionModule、createExtensionAPI、loadExtensionsInternal 等内部函数未导出,对包使用者不可见。
重导出 runner.ts 的类和类型:
| |
重导出 wrapper.ts 的函数:
| |
重导出 types.ts:
这是最庞大的部分,约 70 个类型重导出,涵盖:
- 事件类型(
SessionStartEvent,AgentStartEvent,ToolCallEvent等) - 结果类型(
ContextEventResult,ToolCallEventResult等) - 上下文类型(
ExtensionContext,ExtensionCommandContext,ExtensionUIContext等) - API 类型(
ExtensionAPI,ExtensionFactory等) - 配置类型(
ProviderConfig,ProviderModelConfig等) - 类型守卫(
isBashToolResult,isToolCallEventType等)
额外导出:
| |
设计:这些类型本不属于扩展系统,但由于扩展 API 中引用了它们(如
ExtensionAPI.getCommands()返回SlashCommandInfo[]),所以在这里重导出,方便扩展作者一次性导入所有需要的类型。
导出的分层结构
index.ts
├── 类型定义 (types.ts) → 约 70 个 types
├── 加载器函数 (loader.ts) → 4 个导出函数
├── 运行器类 (runner.ts) → ExtensionRunner (class) + 5 个类型
├── 包装器函数 (wrapper.ts) → 2 个导出函数
├── 额外类型 (../slash-commands.ts, ../source-info.ts) → 2 个 type
└── 类型守卫 (types.ts) → 8 个类型守卫函数
关系总结
引用链全景
┌─────────────────────────────────────────┐
│ types.ts │
│ (所有类型定义:事件、API、上下文、工具) │
└────────────┬────────────┬───────────────┘
│ │
┌───────────────────┘ ┌────────┘
▼ ▼
┌───────────────┐ ┌─────────────────┐
│ loader.ts │ │ runner.ts │
│ (加载扩展) │ │ (生命周期管理) │
└───────────────┘ └────────┬────────┘
│
▼
┌─────────────────┐
│ wrapper.ts │
│ (工具适配) │
└────────┬────────┘
│
┌─────────────────────────┘
▼
┌───────────────────┐
│ index.ts │
│ (公共 API 入口) │
└───────────────────┘
核心数据流
[扩展 .ts 文件]
│
▼ jiti.import()
loader.ts ────→ ExtensionFactory (default export 的函数)
│ │
│ ▼ factory(api)
│ Extension 对象 (handlers, tools, commands, etc.)
│ │
▼ ▼
ExtensionRuntime (共享单例, pendingProviderRegistrations 队列)
│
▼ bindCore()
runner.ts ────→ 替换 runtime 桩函数 → 刷新 provider 队列
│
├── createContext() → ExtensionContext (事件处理用)
├── createCommandContext() → ExtensionCommandContext (命令用)
├── emit(event) → 遍历扩展的 handlers 分派事件
├── getAllRegisteredTools() → 汇总所有工具(去重)
└── getShortcuts() → 汇总快捷键(冲突检测)
│
▼ wrapRegisteredTool()
wrapper.ts ────→ AgentTool (可被 agent-core 调用)
关键设计模式总结
| 模式 | 位置 | 说明 |
|---|---|---|
| 策略模式 | ExtensionUIContext | 不同运行模式(TUI/RPC/print)提供不同 UI 实现 |
| 两阶段初始化 | createExtensionRuntime() + bindCore() | 加载期用桩函数,绑定后替换为真实实现 |
| 责任链模式 | emit() 系列方法 | 所有扩展依次处理事件,可提前终止 |
| 观察者模式 | ExtensionAPI.on() | 扩展订阅事件,runner 分发 |
| 适配器模式 | wrapper.ts | ToolDefinition → AgentTool 的适配转换 |
| 惰性求值 | createContext() 的 getter | 上下文属性在访问时求值,反映最新状态 |
| 工厂模式 | createExtensionAPI() | 为每个扩展创建独立的 API 包装 |
抽象层次
高抽象层 ─── index.ts (公共 API 表面)
│
runner.ts (事件分发、上下文创建、生命周期)
│
loader.ts (模块解析、扩展发现、缓存)
│
wrapper.ts (工具适配桥接)
│
低抽象层 ─── types.ts (全部类型契约)
安全机制
| 安全机制 | 位置 | 说明 |
|---|---|---|
| Stale Invalidation | runner.ts + loader.ts | 会话切换后自动失效旧实例 |
| assertActive() | 整个 API 表面 | 每次操作前检查有效性 |
| 快捷键冲突检测 | runner.ts | 保留快捷键阻止覆盖(app.interrupt 等) |
| 命令重名自动编号 | runner.ts | 同名命令生成 name:n 后缀避免冲突 |
| 角色检查 | emitMessageEnd() | 防止扩展修改消息角色 |
| Flag 自检 | loader.ts createExtensionAPI() | 只能读自己注册的标志 |
附录:文件一览
| 文件 | 路径 | 行数 | 角色 |
|---|---|---|---|
types.ts | extensions/types.ts | 1615 | 全部类型定义 |
loader.ts | extensions/loader.ts | ~500 | 模块加载与发现 |
runner.ts | extensions/runner.ts | ~750 | 生命周期与事件分发 |
wrapper.ts | extensions/wrapper.ts | ~30 | 工具适配 |
index.ts | extensions/index.ts | ~80 | 公共入口 |
文档生成时间:2026-06-27 分析对象:F:\Pi\packages\coding-agent\src\core\extensions/