第 35 讲:Extension API 扩展层——贡献者插槽与三级状态存储

Codex 源码解析 · 第 35 讲

Codex 源码解析系列 第 35 讲:Extension API 扩展层——贡献者插槽与三级状态存储 基于 OpenAI Codex 源码 · 2026-10-29 💡 本讲一句话: Codex 的扩展 API 就是给 core 留的一套"USB 插槽"——web-search、生图、Goals、消息队列各自实现几个 trait,把自己注册进同一个 ExtensionRegistry;core 只认插槽不认功能。读完你能看懂任何新扩展是怎么"插"进来的。 一、为什么需要一层 Extension API 前面几讲拆过 core(会话/轮次循环)、tools(工具注册与路由)、guardian(安全审查)。但你会发现一个矛盾:web-search、image-generation、Goals、消息队列这些功能,代码全在 ext/ 下的独立 crate 里,可 core 的 turn 循环又必须知道"这一轮有哪些工具可用""prompt 要拼哪些片段""审批该问谁"。 如果让 core 直接 use codex_web_search_extension ,每加一个功能就要改一次 core——依赖方向反了。Codex 的解法是 codex-extension-api (约 3K LOC):core 只依赖这一层抽象 trait;每个功能 crate 实现其中几个 trait,通过统一的 install() 函数把自己"插"进注册表。core 从头到尾不知道具体是谁在贡献工具。 为什么这样设计: 这是典型的依赖倒置——把"功能清单"从 core 的编译期依赖降级为运行期的注册表内容。收益有三:① 新功能可以完全在 ext/ 下开发,core 零改动;② 不同宿主(CLI、app-server)可以装不同的扩展组合;③ trait 默认实现全是空操作,一个功能只需要实现自己关心的插槽。 二、ExtensionRegistryBuilder:先注册,后冻结 注册表分两个阶段: Builder(可变) 负责收集贡献者, build() 之后变成 不可变的 ExtensionRegistry ,用 Arc 共享给整个线程运行时。看注册表本体(ext/extension-api/src/registry.rs 第 146-162 行): 📄 codex-rs/ext/extension-api/src/registry.rs (第 146-162 行) /// Immutable typed registry produced after extensions are installed. // 注释点明:build() 之后注册表不可变,只能读 pub struct ExtensionRegistry<C: Sync> { // C 是宿主配置类型(core 里就是 Config),Sync 保证跨线程共享安全 event_sink: Arc<dyn ExtensionEventSink>, // 事件出口:扩展想通知 UI/客户端时走这里,而不是自己开连接 turn_start_admission: Option<Arc<dyn TurnStartAdmission>>, // 宿主级"准入门禁":关机排空期间拒绝新轮次启动(可缺省) thread_lifecycle_contributors: Vec<Arc<dyn ThreadLifecycleContributor<C>>>, // 线程生命周期插槽:start/ready/resume/idle/stop 五个钩子 turn_lifecycle_contributors: Vec<Arc<dyn TurnLifecycleContributor>>, // 轮次生命周期插槽:turn start/item completed/stop/abort/error config_contributors: Vec<Arc<dyn ConfigContributor<C>>>, // 配置变更插槽:线程配置提交后回调,扩展借此刷新自己的快照 token_usage_contributors: Vec<Arc<dyn TokenUsageContributor>>, // token 用量插槽:模型…

阅读全文:Codex 源码解析 第 35 讲