T3 Code 源码拆解
Provider 与执行

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 适配层
stdio JSON-RPC notifications / requests ProviderCommandReactor CodexAdapter CodexSessionRuntime effect-codex client codex app-server Canonical events ProviderRuntimeIngestion

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,而是:

  1. 保留父/子 Agent 或 activity 的关联 ID;
  2. 将可通用的生命周期映射到 canonical activity;
  3. 对 Codex 特有 metadata 做可选扩展;
  4. 未知新 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 的生命周期结算。
Claude 适配层
async stream permission callback approval response ProviderCommandReactor ClaudeAdapter ClaudeProvider Claude Agent SDK query Canonical events Runtime Ingestion

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 RPCSDK query/sessionsession lifecycle event
文本流item/content delta notificationassistant stream messageassistant segment delta
工具item/tool eventstool use/resultactivity start/update/complete
审批server request/approvalpermission callbackpending approval + response
完成turn completed/statusresult/stream endturn settled + session idle
中断turn interrupt RPCAbort/interruptinterrupt 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 启动失败”。

On this page