10 · Codex 与 Claude 适配
对比双向 JSON-RPC app-server 与 Agent SDK 两种接入方式,理解统一接口下的不同压力。
同一接口,两种协议性格
Codex 和 Claude 都能完成对话、工具执行、审批和流式输出,但宿主接口不同:Codex 暴露长期运行的 app-server JSON-RPC;
Claude adapter 构建在 Agent SDK 与其 async 消息/permission 机制上。T3 Code 没有把两者塞进同一底层 transport,
而是在各自最自然的协议层之上对齐 ProviderAdapter。
Codex:Effect 化的双向 JSON-RPC
独立包 packages/effect-codex-app-server
负责 stdio framing、Schema、request/response、notification 和错误。生成的 metadata/Schema 与手写 client/rpc 分离,
让协议更新可以机械同步,同时保留 Effect 资源管理与类型化接口。
Codex 是双向 RPC:Server 不只调用 Codex,Codex 也可能向 client 发 approval/user input 等请求。这要求 connection 层同时维护:
- outbound request ID 与 Deferred;
- inbound request handler;
- notification stream;
- process exit 时使所有 pending request 失败;
- Schema decode error 与 transport error 的区分。
CodexSessionRuntime.ts
在协议 client 之上维护 T3 需要的 session 目录、turn 关联和 canonical event 转换;CodexAdapter.ts 暴露统一控制面。
Codex 启动参数与 home
codexLaunchArgs.ts
集中构造启动参数,而不是在 session 方法中拼字符串。CodexHomeLayout.ts 处理自定义 Codex home,
CodexDeveloperInstructions.ts 负责 T3 注入/协调的开发者指令边界。
集中化很重要,因为参数会同时影响 sandbox、approval policy、模型、工作目录、协作/多 Agent 能力和协议版本。 字符串顺序或重复 flag 都可能造成难以诊断的行为差异,专门测试比在端到端测试中发现更便宜。
Codex 协作事件
源码包含 CodexCollabRuntime.integration.test.ts、wire tests 和 multi-agent fixture,说明 adapter 不只处理普通文本,
还要面对协作 Agent 的嵌套事件。正确策略不是把原始 wire payload 直通 UI,而是:
- 保留父/子 Agent 或 activity 的关联 ID;
- 将可通用的生命周期映射到 canonical activity;
- 对 Codex 特有 metadata 做可选扩展;
- 未知新 notification 要可观察但不崩溃整个 session。
Claude:SDK 流与权限回调
ClaudeProvider.ts
和 ClaudeAdapter.ts
围绕 Claude Agent SDK 管理 query/session。SDK 已封装进程协议,但产品仍需处理:
- SDK iterator 的消费与取消;
- permission request 转成可跨端持久化 approval;
- resume/session ID 对齐;
- model、permission mode、system/developer options;
- stream 结束、异常和 interrupt 的生命周期结算。
SDK callback 天然倾向于“在当前 Promise 里等待用户回答”。T3 Code 必须把它桥接成持久领域状态:callback request 被赋予稳定 ID, approval 投影到客户端,用户响应再路由回对应 pending callback。连接断开不应自动拒绝,只要 Server/SDK session 仍存活。
Claude executable、home 与 skills
Claude 集成专门拆出:
ClaudeExecutable.ts:解析/探测可执行文件;ClaudeHome.ts:实例 home 与配置隔离;ClaudeSkills.ts:发现 skills 并形成 snapshot;ClaudeCapabilitiesProbe.ts:检测运行能力。
这些模块说明 Provider snapshot 不只是“命令是否存在”。用户期望在创建 thread 前看到账号/能力/skills,且多实例之间不能串配置。
两者的共性映射
| 产品语义 | Codex 原生线索 | Claude 原生线索 | T3 规范结果 |
|---|---|---|---|
| 会话开始 | thread/session RPC | SDK query/session | session lifecycle event |
| 文本流 | item/content delta notification | assistant stream message | assistant segment delta |
| 工具 | item/tool events | tool use/result | activity start/update/complete |
| 审批 | server request/approval | permission callback | pending approval + response |
| 完成 | turn completed/status | result/stream end | turn settled + session idle |
| 中断 | turn interrupt RPC | Abort/interrupt | interrupt intent + runtime result |
差异最大的地方是原生事件粒度和回调方向,而不是 UI 最终要展示的对象。
错误与退出
Child process/SDK stream 可能在任何阶段退出。Adapter 要保证:
- 所有 pending request/callback 得到失败,不悬挂;
- event stream 发出可被 ingestion 结算的错误/stop;
- process stderr 可进入诊断日志,但不直接当用户消息;
- session directory 清除失效条目;
- 下次 turn 可以选择恢复、重建或返回明确不可用。
直接 catch 后只写 console,会让投影永远保持 running,是多端产品中最危险的“看起来还在工作”。
可迁移经验
- 协议包负责 wire correctness,Provider 层负责 session semantics,Adapter 负责统一控制接口。
- SDK 已经封装 transport,不代表它已经符合产品的持久生命周期。
- Callback 型审批必须桥接成有 ID、可恢复、跨端可响应的领域对象。
- 启动参数、home 和 executable 探测独立测试,避免所有错误都在长链路末端表现为“Agent 启动失败”。