workspace 全景 · 153 个成员怎么分层
codex-rs 的 members 列了 153 个 crate,按职责可以归成十二组:入口与终端界面、app-server 与传输、内核、工具与执行、沙箱与安全、扩展机制等。依赖方向很清楚:前端只认 app-server,app-server 驱动 codex-core,codex-core 再把工具、沙箱、MCP、模型调用、持久化分给几十个卫星 crate;TUI 甚至不直接依赖 codex-core。
workspace 全景 · 153 个成员怎么分层
打开 codex-rs/Cargo.toml,[workspace] 的 members 列了 153 项。第一次看会晕,但它们不是平铺的:按职责能归成十来组,依赖方向也很清楚。这一篇给出一张分层地图,后面任何一个功能,都能先在这张图上定位到 crate,再翻进去读。
代码量沿用专栏封面的口径:只数 .rs 文件的物理行,去掉 tests.rs、*_tests.rs、tests/ 目录与 #[cfg(test)] 块。153 个成员合计 4,770 个 .rs 文件、约 190 万行,去掉测试约 82 万行。
目录与命名
153 个成员里,108 个直接放在 codex-rs/ 下,其余是三个子目录:
utils/:26 个小工具库,如utils/absolute-path、utils/pty、utils/stream-parser;ext/:16 个扩展 crate,建在扩展接口codex-extension-api(就在ext/extension-api)之上,如ext/goal、ext/memories、ext/mcp;- 另有
memories/read、memories/write和测试辅助exec-server/tests/support。
crate 名的规则写在根目录 AGENTS.md 第一条:目录名加 codex- 前缀,core 目录就是 codex-core。这条规矩不靠自觉,CI 脚本会逐个核对:
def expected_package_name(path: Path) -> str | None:
parts = path.relative_to(CARGO_RS_ROOT).parts
if len(parts) == 2 and parts[1] == "Cargo.toml":
directory = parts[0]
return TOP_LEVEL_NAME_EXCEPTIONS.get(
directory,
directory if directory.startswith("codex-") else f"codex-{directory}",
)
if len(parts) == 3 and parts[0] == "utils" and parts[2] == "Cargo.toml":
directory = parts[1]
return UTILITY_NAME_EXCEPTIONS.get(directory, f"codex-utils-{directory}")
return None(.github/scripts/verify_cargo_workspace_manifests.py:211)
顶层目录是 codex-<目录名>(目录本身已带 codex- 前缀的,如 codex-api、codex-mcp,原样保留),utils/ 下是 codex-utils-<目录名>;登记在案的例外只有两个:windows-sandbox-rs 叫 codex-windows-sandbox,utils/path-utils 叫 codex-utils-path。ext/ 不在检查范围内,名字多是 codex-<名>-extension 的形式,例如 ext/goal 是 codex-goal-extension、ext/items 是 codex-extension-items,搜代码时要留意。
members 并不是全部。chatgpt、message-history、windows-sandbox-rs 三个目录没列在里面,但它们是 workspace 目录内的 path 依赖,Cargo 会自动把它们算作成员,测试辅助 crate core_test_support、app_test_support 也一样。其中 Windows 沙箱 windows-sandbox-rs 本身就有约 1.9 万行非测试代码,上面的总数没有计入它们。
按职责分成十二组
下面的分组是本文按职责划的,不是仓库里的正式分类,但每个成员都恰好落在一组里:
| 分组 | 成员数 | 非测试代码 | 代表成员 |
|---|---|---|---|
| 入口与终端界面 | 10 | 约 24.2 万行 | tui、cli、exec、cloud-tasks、file-search、实时语音的 realtime-webrtc 与 voice-host |
| app-server 与传输 | 12 | 约 8.4 万行 | app-server、app-server-protocol、app-server-transport、app-server-daemon、app-server-client、uds |
| 内核 | 7 | 约 13.8 万行 | core、protocol、core-api、context-fragments、prompts、history |
| 工具与执行 | 11 | 约 5.7 万行 | exec-server、tools、shell-command、apply-patch、file-system、code-mode 一族 |
| 沙箱、网络与安全 | 10 | 约 3.8 万行 | network-proxy、linux-sandbox、sandboxing、bwrap、execpolicy、guardian-context |
| 扩展机制 | 30 | 约 11.3 万行 | core-plugins、rmcp-client、codex-mcp、hooks、skills、plugin、ext/ 下 16 个、memories/*、external-agent-migration |
| 模型与认证 | 16 | 约 2.9 万行 | login、codex-api、codex-client、model-provider、models-manager、ollama、lmstudio |
| 配置 | 5 | 约 2.1 万行 | config、features、cloud-config |
| 持久化 | 5 | 约 4.5 万行 | thread-store、state、rollout、rollout-trace |
| 遥测与反馈 | 5 | 约 1.5 万行 | analytics、otel、feedback |
| 云端后端客户端 | 4 | 约 0.5 万行 | backend-client、codex-backend-openapi-models、cloud-tasks-client |
| 基础库与工程辅助 | 38 | 约 2.9 万行 | utils/ 下 26 个、http-client、arg0、install-context、git-utils、test-binary-support |
两个数字值得多看一眼。“入口与终端界面”一组占了近三成代码,几乎全是 tui 一个 crate;“扩展机制”成员最多,这是后面第 5 部分的地盘。
依赖怎么流
按各 crate Cargo.toml 的 [dependencies](含平台相关依赖,不含 dev-dependencies)把主干画出来,箭头指向被依赖的一方,图里只画了主干边:
从图里能读出几条贯穿全书的事实。
TUI 不依赖 codex-core。 codex-rs/tui/Cargo.toml 的依赖里有 codex-app-server-client、codex-app-server-protocol、codex-app-server-daemon,却没有 codex-core:会话里的一切都要变成对 app-server 的请求。还没迁完的几条启动与配置路径,经客户端 crate 里的一个过渡模块转手:
/// Transitional access to core-only embedded app-server types.
///
/// New TUI behavior should prefer the app-server protocol methods. This
/// module exists so clients can remove a direct `codex-core` dependency
/// while legacy startup/config paths are migrated to RPCs.
pub mod legacy_core {
pub mod config {
pub use codex_core::config::*;
pub mod edit {
pub use codex_core::config::edit::*;
}
}
}(codex-rs/app-server-client/src/lib.rs:71)
codex-exec 还直接依赖 codex-core,但它的运行同样经由进程内的 app-server。这个“前端只认 app-server”的结构,是一条消息的生命周期和整个第 1 部分的出发点。
codex-core 是依赖的枢纽。 它的 [dependencies] 里有 68 个本仓库 crate;153 个成员中有 23 个直接依赖它,直接依赖 codex-protocol 的则有 76 个。codex-protocol 定义了内核与外界之间的词汇:提交给内核的 Op、内核发出的 EventMsg、与模型往来的 ResponseItem,几乎人人都要用。
卫星 crate 是刻意拆出来的。 AGENTS.md 专门有一节要求抵制往 codex-core 里加代码(见读源码之前),于是工具规格(codex-tools)、上下文片段(codex-context-fragments)、提示词模板(codex-prompts)、扩展接口(codex-extension-api)都各自成 crate。扩展 crate 的依赖方向也值得注意:codex-extension-api 本身不依赖 codex-core;ext/ 下需要内核能力的扩展(goal、memories、mcp、web-search 等)依赖 codex-core,由 app-server 在 codex-rs/app-server/src/extensions.rs 里装配;codex-core 自己只直接依赖其中少数几个,如 codex-skills-extension、codex-guardian-reviewer。
Linux 沙箱是反着挂的。 不是 codex-sandboxing 依赖 codex-linux-sandbox,而是反过来:沙箱助手是 codex-arg0 在程序名为 codex-linux-sandbox 时调起的独立入口(见上一篇),它的 bubblewrap 由 codex-bwrap 从 codex-rs/vendor/bubblewrap 下的 C 源码编出来。
最大的几个 crate
| crate | 非测试代码 | 做什么 | 本专栏 |
|---|---|---|---|
codex-tui | 205,740 行 | 终端界面 | 第 6 部分 |
codex-core | 113,796 行 | agent 内核:线程、Session、run_turn、工具路由与编排 | 第 2、3 部分 |
codex-app-server | 43,575 行 | 所有前端共用的服务端 | 第 1 部分 |
codex-exec-server | 26,595 行 | 命令执行与文件操作服务,可以跑在另一台机器上 | exec-server |
codex-core-plugins | 22,632 行 | 插件与 marketplace | 插件 |
codex-app-server-protocol | 21,360 行 | app-server 协议类型与 schema 生成 | v2 协议 |
codex-cli | 20,670 行 | 命令行入口与各子命令 | 本部分 |
codex-protocol | 20,256 行 | 内核协议 Op、EventMsg、ResponseItem 等 | 全书 |
codex-config | 17,443 行 | 分层配置加载 | 配置系统 |
codex-thread-store | 16,649 行 | 线程存储 | 会话持久化 |
codex-network-proxy | 16,437 行 | 联网权限的代理 | 网络代理 |
codex-rmcp-client | 15,562 行 | MCP 客户端的传输与 OAuth | MCP 客户端 |
另一头是一批小 crate:前端与服务端之间的整个客户端层 codex-app-server-client 不到 1,900 行,codex-arg0 约 540 行;最小的 codex-collaboration-mode-templates 只有两行 Rust,用 include_str! 把 default.md、plan.md 两份协作模式模板编进二进制。
作为参照,Grok Build 的 workspace 是 96 个 crate,OpenCode 是 36 个 TypeScript 包。Codex 的成员数最多,从上面的分组看,很大一部分来自内核周边拆出来的小 crate 和 utils/ 下的工具库。