OpenAI Codex 中文教程

Codex App Server

用 app-server 协议把 Codex 嵌入到你自己的产品里

Codex app-server 是 Codex 面向交互能力更强的客户端提供的接口,例如 Codex VS Code 扩展。当你希望在自己的产品中做深度集成时,就应该使用它:认证、对话历史、审批,以及流式传输的智能体事件都属于它的能力范围。

app-server 的实现已在 Codex GitHub 仓库中开源(openai/codex/codex-rs/app-server)。如需查看 Codex 的完整开源组件列表,请参见开源页面。

协议

MCP 一样,codex app-server 支持通过 JSON-RPC 2.0 消息进行双向通信,只是在实际传输时省略了 "jsonrpc":"2.0" 这个字段。

支持以下传输方式:

  • stdio--listen stdio://,默认):使用按行分隔的 JSON,也就是 JSONL。
  • websocket--listen ws://IP:PORT,实验性且不受支持):每个 WebSocket 文本帧承载一条 JSON-RPC 消息。
  • Unix socket(--listen unix://--listen unix://PATH):通过 Codex 默认 app-server control socket 或自定义 Unix socket 路径建立 WebSocket 连接,使用标准 HTTP Upgrade 握手。
  • off--listen off):不暴露本地传输。

使用 --listen ws://IP:PORT 运行时,同一个监听器也会提供基础 HTTP 健康检查:

  • GET /readyz:监听器接受新连接后返回 200 OK
  • GET /healthz:请求不包含 Origin header 时返回 200 OK
  • Origin header 的请求会被拒绝并返回 403 Forbidden

WebSocket transport 仍是实验性且不受支持。ws://127.0.0.1:PORT 这类本地 listener 适合 localhost 和 SSH 端口转发工作流。非 loopback WebSocket listener 在 rollout 期间默认允许未认证连接,因此在远程暴露之前请先配置 WebSocket auth。

支持的 WebSocket auth flags:

  • --ws-auth capability-token --ws-token-file /absolute/path
  • --ws-auth capability-token --ws-token-sha256 HEX
  • --ws-auth signed-bearer-token --ws-shared-secret-file /absolute/path

对于 signed bearer tokens,也可以设置 --ws-issuer--ws-audience--ws-max-clock-skew-seconds。客户端会在 WebSocket handshake 期间以 Authorization: Bearer <token> 提供凭据,app-server 会在 JSON-RPC initialize 之前强制校验 auth。

优先使用 --ws-token-file,不要在命令行上传入原始 bearer token。只有当客户端把原始高熵 token 保存在独立本地 secret store 中时,才使用 --ws-token-sha256;这个 hash 只是 verifier,客户端仍然需要原始 token。

在 WebSocket 模式下,app-server 使用有界队列。当请求入口队列已满时,服务端会拒绝新的请求,并返回 JSON-RPC 错误码 -32001 和消息 "Server overloaded; retry later."。客户端应采用带抖动的指数退避策略重试。

消息结构

请求消息包含 method、params 和 id:

{ "method": "thread/start", "id": 10, "params": { "model": "gpt-5.4" } }

响应会回显同一个 id,并携带 result 或 error:

{ "id": 10, "result": { "thread": { "id": "thr_123" } } }
{ "id": 10, "error": { "code": 123, "message": "Something went wrong" } }

通知消息不带 id,只包含 method 和 params:

{ "method": "turn/started", "params": { "turn": { "id": "turn_456" } } }

你也可以通过 CLI 直接生成 TypeScript schema 或 JSON Schema bundle。生成结果与当前运行的 Codex 版本严格对应:

codex app-server generate-ts --out ./schemas
codex app-server generate-json-schema --out ./schemas

快速开始

最小握手流程如下:

  1. 使用 codex app-server 启动服务端(默认 stdio 传输),或者使用 codex app-server --listen ws://127.0.0.1:4500(TCP WebSocket)或 codex app-server --listen unix://(默认 Unix socket)启动服务端。
  2. 通过所选传输方式连接客户端,然后先发送 initialize,再发送 initialized 通知。
  3. 启动一个 thread 和一个 turn,然后持续从当前传输流中读取通知。

下面是一个最小 Node.js / TypeScript 示例:

import { spawn } from "node:child_process";
import readline from "node:readline";

const proc = spawn("codex", ["app-server"], {
  stdio: ["pipe", "pipe", "inherit"],
});
const rl = readline.createInterface({ input: proc.stdout });

const send = (message: unknown) => {
  proc.stdin.write(`${JSON.stringify(message)}\n`);
};

let threadId: string | null = null;

rl.on("line", (line) => {
  const msg = JSON.parse(line) as any;
  console.log("server:", msg);

  if (msg.id === 1 && msg.result?.thread?.id && !threadId) {
    threadId = msg.result.thread.id;
    send({
      method: "turn/start",
      id: 2,
      params: {
        threadId,
        input: [{ type: "text", text: "Summarize this repo." }],
      },
    });
  }
});

send({
  method: "initialize",
  id: 0,
  params: {
    clientInfo: {
      name: "my_product",
      title: "My Product",
      version: "0.1.0",
    },
  },
});
send({ method: "initialized", params: {} });
send({ method: "thread/start", id: 1, params: { model: "gpt-5.4" } });

核心抽象

  • Thread:用户与 Codex 智能体之间的一条对话。Thread 包含多个 turn。
  • Turn:一次用户请求,以及之后智能体执行的整段工作。Turn 包含多个 item,并会流式产出增量更新。
  • Item:输入或输出的一个单元,例如用户消息、智能体消息、命令执行、文件修改、工具调用等。

通过 thread 相关 API 创建、列出或归档对话;通过 turn 相关 API 驱动对话,并从 turn 通知中流式读取进度。

生命周期概览

  • 每条连接只初始化一次:在打开传输连接后,立即发送带有客户端元数据的 initialize 请求,然后发出 initialized。在完成这次握手前,服务端会拒绝该连接上的其他任何请求。
  • 启动或恢复线程:新对话使用 thread/start;继续已有对话使用 thread/resume;要把现有历史分叉为新的 thread id,则使用 thread/fork
  • 开始一个 turn:调用 turn/start,传入目标 threadId 和用户输入。可选字段可以覆盖 model、personality、cwd、沙箱策略等设置。
  • 引导进行中的 turn:调用 turn/steer,在不创建新 turn 的前提下,把更多用户输入追加到当前仍在进行中的 turn。
  • 流式读取事件:调用 turn/start 后,持续从当前传输流中读取通知,例如 thread/archivedthread/unarchiveditem/starteditem/completeditem/agentMessage/delta、工具执行进度等。
  • 结束 turn:模型完成后,或在收到 turn/interrupt 取消后,服务端会发出带最终状态的 turn/completed

初始化

每条传输连接都必须先发送一次 initialize,然后再通过 initialized 通知确认。在初始化完成前调用其他方法,会收到 Not initialized;在同一连接上重复发送 initialize,则会收到 Already initialized

服务端会返回它向上游服务声明的 User-Agent 字符串,以及描述运行目标的 platformFamilyplatformOs。你应通过 clientInfo 来标识自己的集成。

initialize.params.capabilities 还支持 optOutNotificationMethods,允许你为当前连接关闭某些指定通知。匹配是精确匹配,不支持通配符;未知方法名会被静默忽略。

重要:请使用 clientInfo.name 作为你在 OpenAI Compliance Logs Platform 中的客户端标识。如果你正在开发面向企业的全新 Codex 集成,请联系 OpenAI,将它加入已知客户端列表。相关背景可参考 Codex logs reference

示例:

{
  "method": "initialize",
  "id": 0,
  "params": {
    "clientInfo": {
      "name": "codex_vscode",
      "title": "Codex VS Code Extension",
      "version": "0.1.0"
    }
  }
}

关闭部分通知的示例:

{
  "method": "initialize",
  "id": 1,
  "params": {
    "clientInfo": {
      "name": "my_client",
      "title": "My Client",
      "version": "0.1.0"
    },
    "capabilities": {
      "experimentalApi": true,
      "optOutNotificationMethods": ["thread/started", "item/agentMessage/delta"]
    }
  }
}

实验性 API 选项

部分 app-server 的方法和字段被有意放在 experimentalApi 能力开关后面。

  • 省略 capabilities,或显式将 experimentalApi 设为 false,表示你只使用稳定 API;此时服务端会拒绝实验性方法和字段。
  • capabilities.experimentalApi 设为 true,则可启用实验性方法和字段。

示例:

{
  "method": "initialize",
  "id": 1,
  "params": {
    "clientInfo": {
      "name": "my_client",
      "title": "My Client",
      "version": "0.1.0"
    },
    "capabilities": {
      "experimentalApi": true
    }
  }
}

如果客户端在未启用 experimentalApi 的情况下发送实验性方法或字段,app-server 会拒绝请求,并返回:

<descriptor> requires experimentalApi capability

API 概览

  • thread/start:创建新线程;会发出 thread/started,并自动为当前线程订阅该线程的 turn / item 事件。
  • thread/resume:按 id 重新打开已有线程,使后续 turn/start 可继续追加到该线程中。
  • thread/fork:通过复制已保存的历史,把一条线程分叉到新的 thread id;会为新线程发出 thread/started。返回的 thread 会在可用时包含 forkedFromId
  • thread/read:按 id 读取已保存线程而不恢复它;设置 includeTurns 可返回完整 turn 历史。返回的 thread 对象包含运行时 status
  • thread/list:分页列出已保存线程;支持基于 cursor 的分页,以及 modelProviderssourceKindsarchivedcwdsearchTerm 过滤。返回的 thread 对象包含运行时 status
  • thread/turns/list:在不恢复线程的情况下分页列出已保存线程的 turn 历史。itemsView 控制是否省略、摘要化或完整加载 turn items。
  • thread/turns/items/list:预留用于分页加载 turn item;当前返回 unsupported。
  • thread/loaded/list:列出当前已加载到内存中的 thread id。
  • thread/name/set:为已加载线程或已持久化的会话记录设置或更新用户可见名称;会发出 thread/name/updated
  • thread/goal/set:为线程设置目标;会发出 thread/goal/updated
  • thread/goal/get:读取线程的当前目标。
  • thread/goal/clear:清除线程目标;会发出 thread/goal/cleared
  • thread/metadata/update:patch SQLite-backed 已保存线程元数据;当前支持持久化的 gitInfo
  • thread/archive:把线程的日志文件移动到归档目录;成功时返回 {},并发出 thread/archived
  • thread/unsubscribe:取消当前连接对线程的 turn / item 事件订阅。如果这是最后一个订阅者,服务端会在无订阅者且无活动的宽限期后卸载该线程,并发出 thread/closed
  • thread/unarchive:把归档线程的会话记录恢复回活跃会话目录;返回恢复后的 thread,并发出 thread/unarchived
  • thread/status/changed:某个已加载线程的运行时 status 发生变化时发出的通知。
  • thread/compact/start:触发线程的会话历史压缩;立即返回 {},进度通过 turn/*item/* 通知流出。
  • thread/shellCommand:对某条线程执行用户主动发起的 shell 命令。该命令会在沙箱外以完全访问权限运行,不继承线程原有沙箱策略。
  • thread/backgroundTerminals/clean:停止与某条线程关联的所有后台终端(实验性;需要 capabilities.experimentalApi)。
  • thread/rollback:从内存上下文中移除最后 N 个 turn,并持久化一条 rollback 标记;返回更新后的 thread
  • turn/start:向线程追加用户输入并开始 Codex 生成;响应中会返回初始 turn,并继续流式发送事件。对于 collaborationModesettings.developer_instructions: null 表示“使用该模式的内建指令”。
  • thread/inject_items:在不启动用户 turn 的情况下,把原始 Responses API items 追加到已加载线程的模型可见历史中。
  • turn/steer:向当前仍在进行中的 turn 追加用户输入;返回已接受的 turnId
  • turn/interrupt:请求取消一个仍在执行中的 turn;成功时返回 {},该 turn 最终会以 status: "interrupted" 结束。
  • review/start:为某条线程启动 Codex 评审流程;会发出 enteredReviewModeexitedReviewMode item。
  • command/exec:在不创建 thread / turn 的前提下,在服务端沙箱中执行单条命令。
  • command/exec/write:向正在运行的 command/exec 会话写入 stdin 字节,或关闭 stdin
  • command/exec/resize:调整一个基于 PTY 的 command/exec 会话尺寸。
  • command/exec/terminate:停止一个正在运行的 command/exec 会话。
  • command/exec/outputDelta(通知):从 streaming command/exec 会话发出 base64 编码的 stdout / stderr chunk。
  • process/spawn:在 Codex 沙箱之外启动显式 process 会话(实验性;需要 capabilities.experimentalApi)。
  • process/writeStdin:向正在运行的 process/spawn 会话写入 stdin 字节,或关闭 stdin(实验性)。
  • process/resizePty:调整正在运行的 PTY-backed process 会话尺寸(实验性)。
  • process/kill:终止正在运行的 process 会话(实验性)。
  • process/outputDeltaprocess/exited(通知):流式传输 process 输出和 process 退出状态(实验性)。
  • model/list:列出可用模型;可通过 includeHidden: true 包含带 hidden: true 的条目,并返回 effort 选项、可选 upgradeinputModalities
  • modelProvider/capabilities/read:读取模型 / provider 组合的 provider 能力边界(实验性;需要 capabilities.experimentalApi)。
  • experimentalFeature/list:列出功能开关及其生命周期阶段元数据,并支持 cursor 分页。
  • experimentalFeature/enablement/set:patch 支持的功能键(例如 appsplugins)在内存运行时中的设置。
  • collaborationMode/list:列出协作模式预设(实验性,不分页)。
  • skills/list:按一个或多个 cwd 列出可用技能(支持 forceReload 和可选的 perCwdExtraUserRoots)。
  • skills/changed(通知):被 watch 的本地技能文件发生变化时发出。
  • marketplace/add:添加远程插件 marketplace,并持久化到用户 marketplace 配置中。
  • marketplace/upgrade:刷新一个已配置的 Git marketplace;如果省略 marketplace 名称,则刷新所有已配置的 Git marketplaces。
  • plugin/list:列出已发现的插件市场与插件状态,包括安装 / 认证策略元数据、marketplace 加载错误、featured plugin id,以及 local、Git 或 remote plugin source 元数据。
  • plugin/read:按 marketplace path 或远程 marketplace 名称和插件名读取单个插件的详细信息;可用时会包含其内置技能、应用和 MCP 服务名称。
  • plugin/install:从 marketplace path 或远程 marketplace 名称安装插件。
  • plugin/uninstall:卸载已安装的插件。
  • app/list:分页列出可用 apps(connectors),并返回可访问性与启用状态等元数据。
  • skills/config/write:按路径启用或禁用某个技能。
  • mcpServer/oauth/login:为已配置的 MCP 服务启动 OAuth 登录;会返回授权 URL,并在完成后发出 mcpServer/oauthLogin/completed
  • tool/requestUserInput:为工具调用向用户发起 1 到 3 个简短问题的交互请求(实验性);问题可通过 isOther 提供自由输入选项。
  • config/mcpServer/reload:从磁盘重新加载 MCP 服务配置,并为已加载线程排队刷新。
  • mcpServerStatus/list:列出 MCP 服务、工具、资源和认证状态(支持 cursor + limit 分页)。可通过 detail: "full" 获取完整数据,或用 detail: "toolsAndAuthOnly" 省略资源。
  • mcpServer/resource/read:通过已初始化的 MCP 服务读取单个 MCP resource。
  • mcpServer/tool/call:在某条线程配置的 MCP server 上调用工具。
  • mcpServer/startupStatus/updated(通知):某个已配置 MCP server 在已加载线程上的启动状态变化时发出。
  • windowsSandbox/setupStart:为 elevatedunelevated 模式启动 Windows 沙箱初始化;会快速返回,稍后发出 windowsSandbox/setupCompleted
  • feedback/upload:提交反馈报告(分类、可选原因 / 日志 / 对话 id,以及可选 extraLogFiles 附件)。
  • config/read:读取磁盘上的最终生效配置,结果已经过配置分层合并。
  • externalAgentConfig/detect:检测可被迁移的外部智能体配置工件,支持 includeHome 和可选的 cwds;每个检测结果都包含 cwd(home 级配置时为 null)。
  • externalAgentConfig/import:通过显式传入 migrationItems 及其 cwd(home 级为 null),应用选中的外部智能体迁移项。支持的条目类型包括 config、skills、AGENTS.md、plugins、MCP server config、subagents、hooks、commands 和 sessions;插件导入会发出 externalAgentConfig/import/completed
  • config/value/write:把单个配置 key / value 写入用户磁盘上的 config.toml
  • config/batchWrite:以原子方式把多项配置编辑写入用户磁盘上的 config.toml
  • configRequirements/read:读取 requirements.toml 和 / 或 MDM 中的管理员要求,包括允许列表、固定的 featureRequirements,以及驻留 / 网络要求(如果未配置则返回 null)。
  • fs/readFilefs/writeFilefs/createDirectoryfs/getMetadatafs/readDirectoryfs/removefs/copyfs/watchfs/unwatchfs/changed(通知):通过 app-server v2 文件系统 API 对绝对路径执行文件操作。

Plugin summary 包含一个 source union。本地插件返回 { "type": "local", "path": ... },Git-backed marketplace 条目返回 { "type": "git", "url": ..., "path": ..., "refName": ..., "sha": ... },remote catalog 条目返回 { "type": "remote" }。对于 remote-only catalog 条目,PluginMarketplaceEntry.path 可以是 null;读取或安装这些插件时,请传 remoteMarketplaceName,而不是 marketplacePath

模型

列出模型(model/list)

在渲染模型选择器、推理强度选择器或 personality 选择器前,先调用 model/list 获取当前可用模型和能力。

{ "method": "model/list", "id": 6, "params": { "limit": 20, "includeHidden": false } }
{ "id": 6, "result": {
  "data": [{
    "id": "gpt-5.4",
    "model": "gpt-5.4",
    "displayName": "GPT-5.4",
    "hidden": false,
    "defaultReasoningEffort": "medium",
    "supportedReasoningEfforts": [{
      "reasoningEffort": "low",
      "description": "Lower latency"
    }],
    "inputModalities": ["text", "image"],
    "supportsPersonality": true,
    "isDefault": true
  }],
  "nextCursor": null
} }

每条模型记录可能包含:

  • supportedReasoningEfforts:该模型支持的 effort 选项。
  • defaultReasoningEffort:客户端可采用的建议默认 effort。
  • upgrade:可选的推荐升级模型 id,适合在客户端里做迁移提示。
  • upgradeInfo:可选的升级元数据,适合在客户端里做迁移提示。
  • hidden:该模型是否在默认选择器列表中隐藏。
  • inputModalities:模型支持的输入类型,例如 textimage
  • supportsPersonality:模型是否支持 /personality 这类人格指令。
  • isDefault:该模型是否是推荐默认值。

默认情况下,model/list 只返回适合在选择器中展示的模型;如果你想在客户端自行过滤,设置 includeHidden: true

如果某个旧版模型目录没有 inputModalities,为兼容起见,应把它视为 ["text", "image"]

列出实验性功能(experimentalFeature/list)

这个接口用于发现实验功能开关及其生命周期:

{ "method": "experimentalFeature/list", "id": 7, "params": { "limit": 20 } }
{ "id": 7, "result": {
  "data": [{
    "name": "unified_exec",
    "stage": "beta",
    "displayName": "Unified exec",
    "description": "Use the unified PTY-backed execution tool.",
    "announcement": "Beta rollout for improved command execution reliability.",
    "enabled": false,
    "defaultEnabled": false
  }],
  "nextCursor": null
} }

stage 的可选值包括:betaunderDevelopmentstabledeprecatedremoved。对于非 beta 的功能开关,displayNamedescriptionannouncement 可能为 null

线程

  • thread/read 会读取已保存线程,但不会自动订阅它;设置 includeTurns 可把 turns 一并返回。
  • thread/turns/list 会在不恢复线程的情况下分页列出已保存线程的 turn 历史。使用 itemsView 选择是否省略、摘要化或完整加载 turn items。
  • thread/list 支持基于 cursor 的分页,以及 modelProviderssourceKindsarchivedcwdsearchTerm 过滤。
  • thread/loaded/list 返回当前已加载到内存中的 thread id。
  • thread/archive 会把线程的持久化 JSONL 日志移动到 archived 目录。
  • thread/metadata/update 会 patch 已保存线程元数据,当前支持持久化的 gitInfo
  • thread/unsubscribe 会取消当前连接对已加载线程的订阅,并可能在无活动宽限期后触发 thread/closed
  • thread/unarchive 会把归档线程的会话记录恢复到活跃会话目录。
  • thread/compact/start 会触发压缩,并立即返回 {}
  • thread/rollback 会从内存上下文中移除最后 N 个 turn,并在线程的持久化 JSONL 日志中写入 rollback 标记。
  • thread/inject_items 会在不启动用户 turn 的情况下,把原始 Responses API items 追加到已加载线程的模型可见历史中。

启动或恢复线程

当你需要开启一个全新的 Codex 对话时,调用 thread/start

{ "method": "thread/start", "id": 10, "params": {
  "model": "gpt-5.4",
  "cwd": "/Users/me/project",
  "approvalPolicy": "never",
  "sandbox": "workspaceWrite",
  "personality": "friendly",
  "serviceName": "my_app_server_client"
} }
{ "id": 10, "result": {
  "thread": {
    "id": "thr_123",
    "sessionId": "thr_123",
    "preview": "",
    "ephemeral": false,
    "modelProvider": "openai",
    "createdAt": 1730910000
  }
} }
{ "method": "thread/started", "params": { "thread": { "id": "thr_123" } } }

serviceName 是可选的。当你希望让 app-server 用你的服务名为线程级指标打标签时,再设置它。

thread.sessionId 用来标识当前 live session tree 的根。根 thread 会使用自己的 thread id 作为 session id;fork 出来的 thread 会保留它所属根 thread 的 session id。客户端应从 thread.sessionId 读取 session id,而不是从 thread id 推导。

如果你想继续某条已保存会话,请使用之前记录下来的 thread.id 调用 thread/resume。它的响应结构与 thread/start 相同;你也可以继续传入 personality 这类与 thread/start 相同的配置覆盖项:

{ "method": "thread/resume", "id": 11, "params": {
  "threadId": "thr_123",
  "personality": "friendly"
} }
{ "id": 11, "result": { "thread": { "id": "thr_123", "name": "Bug bash notes", "ephemeral": false } } }

仅仅执行 thread/resume 本身不会更新 thread.updatedAt,也不会修改持久化记录文件的时间戳;只有开始新的 turn 后,时间戳才会更新。

如果某个已启用的 MCP 服务同时被标记为 required,但它初始化失败,那么 thread/startthread/resume 都会直接失败,而不会在缺少该服务的前提下继续运行。

thread/start 上的 dynamicTools 是实验性字段,需要 capabilities.experimentalApi = true。Codex 会把这些 dynamic tools 持久化到线程的会话记录元数据中;当你调用 thread/resume 且没有重新提供新的 dynamic tools 时,它们也会被恢复出来。

如果你用与原始会话记录中记录不同的模型去恢复线程,Codex 会发出一条警告,并在下一次 turn 中注入一次性模型切换说明。

管理线程目标

使用 thread/goal/setthread/goal/getthread/goal/clear 管理 TUI 中 /goal 展示的同一份持久化目标状态。

{ "method": "thread/goal/set", "id": 13, "params": {
  "threadId": "thr_123",
  "objective": "Finish the migration and keep tests green",
  "status": "active",
  "tokenBudget": 40000
} }
{ "id": 13, "result": { "goal": {
  "threadId": "thr_123",
  "objective": "Finish the migration and keep tests green",
  "status": "active",
  "tokenBudget": 40000,
  "tokensUsed": 0,
  "timeUsedSeconds": 0
} } }
{ "method": "thread/goal/updated", "params": {
  "threadId": "thr_123",
  "goal": {
    "threadId": "thr_123",
    "objective": "Finish the migration and keep tests green",
    "status": "active",
    "tokenBudget": 40000,
    "tokensUsed": 0,
    "timeUsedSeconds": 0
  }
} }

目标 objective 必须非空,且最多 4,000 个字符。提供新的 objective 会替换目标并重置用量统计。提供当前未终止的 objective,或省略 objective,则会在保留用量历史的同时更新状态或 token 预算。

如果你希望从某个已保存会话分叉出新线程,可对它的 thread.id 调用 thread/fork。这会创建一个新的 thread id,并为该新线程发出 thread/started 通知:

{ "method": "thread/fork", "id": 12, "params": { "threadId": "thr_123" } }
{ "id": 12, "result": { "thread": { "id": "thr_456", "sessionId": "thr_123", "forkedFromId": "thr_123" } } }
{ "method": "thread/started", "params": { "thread": { "id": "thr_456" } } }

当用户可见标题已设置后,app-server 会在 thread/listthread/readthread/resumethread/unarchivethread/rollback 的响应中补全 thread.name。而 thread/startthread/fork 在创建时,则可能暂时省略 name,或返回 null

读取已保存线程(不恢复)

如果你只想读取已保存线程的数据,但不想恢复该线程,也不想订阅它的事件,请使用 thread/read

  • includeTurns:为 true 时,响应会包含该线程的 turns;为 false 或省略时,只返回线程摘要。
  • 返回的 thread 对象会带运行时 status,取值可能是 notLoadedidlesystemError,或带 activeFlagsactive
{ "method": "thread/read", "id": 19, "params": { "threadId": "thr_123", "includeTurns": true } }
{ "id": 19, "result": { "thread": { "id": "thr_123", "name": "Bug bash notes", "ephemeral": false, "status": { "type": "notLoaded" }, "turns": [] } } }

thread/resume 不同,thread/read 不会把 thread 加载进内存,也不会触发 thread/started

列出线程 turns

使用 thread/turns/list 可以在不恢复线程的情况下分页读取已保存线程的 turn 历史。结果默认按最新优先返回,因此客户端可以用 nextCursor 获取更早的 turns。响应还包含 backwardsCursor;将它作为 cursor 并传入 sortDirection: "asc",可以获取比上一页第一个条目更新的 turns。

{ "method": "thread/turns/list", "id": 20, "params": {
  "threadId": "thr_123",
  "limit": 50,
  "sortDirection": "desc"
} }
{ "id": 20, "result": {
  "data": [],
  "nextCursor": "older-turns-cursor-or-null",
  "backwardsCursor": "newer-turns-cursor-or-null"
} }

列出线程(支持分页与过滤)

thread/list 适合驱动历史会话列表界面。结果默认按 createdAt 倒序排列,并且会先应用过滤,再执行分页。

常用参数包括:

  • cursor:上一次响应返回的不透明字符串;第一页时可省略。
  • limit:如果不设置,服务端会使用一个合理的默认分页大小。
  • sortKeycreated_atupdated_at
  • modelProviders:把结果限制为特定模型提供方;未设置、null 或空数组表示不过滤。
  • sourceKinds:把结果限制为特定 thread 来源。若省略或传 [],服务端默认只返回交互式来源,也就是 clivscode
  • archived:为 true 时只列出归档线程;为 false 或省略时,只列出未归档线程。
  • cwd:只返回会话工作目录与该路径完全一致的线程。
  • searchTerm:分页前先搜索已保存线程摘要和元数据。

sourceKinds 接受以下取值:

  • cli
  • vscode
  • exec
  • appServer
  • subAgent
  • subAgentReview
  • subAgentCompact
  • subAgentThreadSpawn
  • subAgentOther
  • unknown

示例:

{ "method": "thread/list", "id": 20, "params": {
  "cursor": null,
  "limit": 25,
  "sortKey": "created_at"
} }

nextCursornull 时,表示已经到达最后一页。

更新已保存线程元数据

使用 thread/metadata/update 可以在不恢复线程的情况下 patch 已保存线程元数据。当前支持持久化的 gitInfo;省略的字段会保持不变,显式传 null 会清除已保存值。

{ "method": "thread/metadata/update", "id": 21, "params": {
  "threadId": "thr_123",
  "gitInfo": { "branch": "feature/sidebar-pr" }
} }
{ "id": 21, "result": {
  "thread": {
    "id": "thr_123",
    "gitInfo": { "sha": null, "branch": "feature/sidebar-pr", "originUrl": null }
  }
} }

跟踪线程状态变化

只要某个已加载线程的运行时状态发生变化,服务端就会发出 thread/status/changed

{
  "method": "thread/status/changed",
  "params": {
    "threadId": "thr_123",
    "status": { "type": "active", "activeFlags": ["waitingOnApproval"] }
  }
}

列出已加载线程

thread/loaded/list 返回当前已载入内存的 thread id 列表:

{ "method": "thread/loaded/list", "id": 21 }
{ "id": 21, "result": { "data": ["thr_123", "thr_456"] } }

取消订阅已加载线程

thread/unsubscribe 会移除当前连接对某条 thread 的订阅。响应中的 status 可能是以下几种:

  • unsubscribed:当前连接原本订阅了这条 thread,现在已被移除。
  • notSubscribed:当前连接原本就没有订阅这条 thread。
  • notLoaded:这条 thread 当前并未加载。

如果这次移除的是最后一个订阅者,服务端会让该 thread 继续加载,直到它连续 30 分钟没有订阅者且没有线程活动。宽限期结束后,app-server 会卸载该 thread,并发出一条状态切换到 notLoadedthread/status/changed,随后再发出 thread/closed

{ "method": "thread/unsubscribe", "id": 22, "params": { "threadId": "thr_123" } }
{ "id": 22, "result": { "status": "unsubscribed" } }

如果该线程之后过期:

{ "method": "thread/status/changed", "params": {
    "threadId": "thr_123",
    "status": { "type": "notLoaded" }
} }
{ "method": "thread/closed", "params": { "threadId": "thr_123" } }

归档线程

thread/archive 会把持久化线程日志(磁盘上的 JSONL)移动到 archived sessions 目录。

{ "method": "thread/archive", "id": 22, "params": { "threadId": "thr_b" } }
{ "id": 22, "result": {} }
{ "method": "thread/archived", "params": { "threadId": "thr_b" } }

除非你显式传入 archived: true,否则后续的 thread/list 不会再返回这些已归档线程。

取消归档线程

thread/unarchive 会把归档线程移回活跃会话目录:

{ "method": "thread/unarchive", "id": 24, "params": { "threadId": "thr_b" } }
{ "id": 24, "result": { "thread": { "id": "thr_b", "name": "Bug bash notes" } } }
{ "method": "thread/unarchived", "params": { "threadId": "thr_b" } }

触发线程压缩

thread/compact/start 会触发一次手动历史压缩。请求会立即返回 {}

app-server 会在同一个 threadId 上通过标准 turn/*item/* 通知流出进度,其中包括一组 contextCompaction item 生命周期事件(先 item/started,再 item/completed)。

{ "method": "thread/compact/start", "id": 25, "params": { "threadId": "thr_b" } }
{ "id": 25, "result": {} }

对线程执行 shell 命令

当你需要让某条线程执行用户主动发起的 shell 命令时,可以使用 thread/shellCommand。请求会立即返回 {},后续进度会继续通过标准 turn/*item/* 通知流出。

这个接口会在沙箱外以完全访问权限执行命令,不会继承线程原有的沙箱策略,因此客户端只应把它暴露给明确由用户主动发起的命令。

如果该线程当前已有活跃 turn,命令会作为该 turn 的辅助动作运行,它的格式化输出也会注入这次 turn 的消息流;如果线程当前空闲,app-server 会为这条 shell 命令启动一个独立 turn。

{ "method": "thread/shellCommand", "id": 26, "params": { "threadId": "thr_b", "command": "git status --short" } }
{ "id": 26, "result": {} }

清理后台终端

使用 thread/backgroundTerminals/clean 可以停止与某条线程关联的所有后台终端。这个方法属于实验性能力,需要 capabilities.experimentalApi = true

{ "method": "thread/backgroundTerminals/clean", "id": 27, "params": { "threadId": "thr_b" } }
{ "id": 27, "result": {} }

回滚最近的回合

thread/rollback 会从内存上下文中移除最后 numTurns 个 turn,并在持久化日志中写入 rollback 标记。返回的 thread 会反映 rollback 之后的状态。

{ "method": "thread/rollback", "id": 26, "params": { "threadId": "thr_b", "numTurns": 1 } }
{ "id": 26, "result": { "thread": { "id": "thr_b", "name": "Bug bash notes", "ephemeral": false } } }

回合

turn/startturn/steer 负责真正驱动对话。

input 支持三种常见 item:

  • { "type": "text", "text": "Explain this diff" }
  • { "type": "image", "url": "https://.../design.png" }
  • { "type": "localImage", "path": "/tmp/screenshot.png" }

你可以按 turn 覆盖配置设置,例如 model、effort、personality、cwd、沙箱策略和 summary。只要在某个 turn 中显式指定,这些设置就会成为同一条 thread 后续 turn 的默认值。outputSchema 只对当前 turn 生效。

sandboxPolicy.type = "externalSandbox" 时,应将 networkAccess 设为 restrictedenabled;当类型是 workspaceWrite 时,networkAccess 仍然是布尔值。

对于 turn/start.collaborationMode,如果设置 settings.developer_instructions: null,表示“使用所选模式的内建 instructions”,而不是清空该模式的 instructions。

沙箱读权限(ReadOnlyAccess)

sandboxPolicy 支持显式声明读权限控制:

  • readOnly:可选 access;默认是 { "type": "fullAccess" },也可以改成受限根目录。
  • workspaceWrite:可选 readOnlyAccess;默认也是 { "type": "fullAccess" },也可以改成受限根目录。

受限读权限的结构如下:

{
  "type": "restricted",
  "includePlatformDefaults": true,
  "readableRoots": ["/Users/me/shared-read-only"]
}

在 macOS 上,includePlatformDefaults: true 会为受限读会话追加一套精心挑选的平台默认 Seatbelt 策略。这样可以提升工具兼容性,同时避免宽泛地放开整个 /System

示例:

{ "type": "readOnly", "access": { "type": "fullAccess" } }
{
  "type": "workspaceWrite",
  "writableRoots": ["/Users/me/project"],
  "readOnlyAccess": {
    "type": "restricted",
    "includePlatformDefaults": true,
    "readableRoots": ["/Users/me/shared-read-only"]
  },
  "networkAccess": false
}

启动回合

{ "method": "turn/start", "id": 30, "params": {
  "threadId": "thr_123",
  "input": [ { "type": "text", "text": "Run tests" } ],
  "cwd": "/Users/me/project",
  "approvalPolicy": "unlessTrusted",
  "sandboxPolicy": {
    "type": "workspaceWrite",
    "writableRoots": ["/Users/me/project"],
    "networkAccess": true
  },
  "model": "gpt-5.4",
  "effort": "medium",
  "summary": "concise",
  "personality": "friendly",
  "outputSchema": {
    "type": "object",
    "properties": { "answer": { "type": "string" } },
    "required": ["answer"],
    "additionalProperties": false
  }
} }

向线程注入 items

使用 thread/inject_items 可以把预先构造好的 Responses API items 追加到已加载线程的提示词历史中,而不启动用户 turn。这些 items 会持久化到 rollout 中,并被纳入后续模型请求。

{ "method": "thread/inject_items", "id": 31, "params": {
  "threadId": "thr_123",
  "items": [
    {
      "type": "message",
      "role": "assistant",
      "content": [{ "type": "output_text", "text": "Previously computed context." }]
    }
  ]
} }
{ "id": 31, "result": {} }

引导进行中的回合

使用 turn/steer 可以向当前仍在进行中的 turn 继续追加用户输入。

  • 请求中必须带上 expectedTurnId,并且它必须与当前活动 turn 的 id 一致。
  • 如果该线程当前没有活动中的 turn,请求会失败。
  • turn/steer 不会发出新的 turn/started 通知。
  • turn/steer 不接受 turn 级覆盖项,例如 modelcwdsandboxPolicyoutputSchema
{ "method": "turn/steer", "id": 32, "params": {
  "threadId": "thr_123",
  "input": [ { "type": "text", "text": "Actually focus on failing tests first." } ],
  "expectedTurnId": "turn_456"
} }
{ "id": 32, "result": { "turnId": "turn_456" } }

启动回合(调用技能)

如果你要显式调用某个技能,推荐同时使用两种信号:

  • 在文本输入中写入 $<skill-name>
  • 同时附带一个 skill 类型的输入项。
{ "method": "turn/start", "id": 33, "params": {
  "threadId": "thr_123",
  "input": [
    { "type": "text", "text": "$skill-creator Add a new skill for triaging flaky CI and include step-by-step usage." },
    { "type": "skill", "name": "skill-creator", "path": "/Users/me/.codex/skills/skill-creator/SKILL.md" }
  ]
} }

中断回合

{ "method": "turn/interrupt", "id": 31, "params": { "threadId": "thr_123", "turnId": "turn_456" } }
{ "id": 31, "result": {} }

成功后,该 turn 会以 status: "interrupted" 结束。

评审

review/start 会对某条 thread 启动 Codex 评审流程,并流式发送评审相关条目。

支持的评审目标包括:

  • uncommittedChanges
  • baseBranch(与某个分支做 diff)
  • commit(评审某个特定 commit)
  • custom(自由输入说明)

默认使用 delivery: "inline",即直接在现有 thread 中产出评审结果;如果你想把评审隔离到新线程,可使用 delivery: "detached"

请求 / 响应示例:

{ "method": "review/start", "id": 40, "params": {
  "threadId": "thr_123",
  "delivery": "inline",
  "target": { "type": "commit", "sha": "1234567deadbeef", "title": "Polish tui colors" }
} }
{ "id": 40, "result": {
  "turn": {
    "id": "turn_900",
    "status": "inProgress",
    "items": [
      { "type": "userMessage", "id": "turn_900", "content": [ { "type": "text", "text": "Review commit 1234567: Polish tui colors" } ] }
    ],
    "error": null
  },
  "reviewThreadId": "thr_123"
} }

如果要做 detached review,请使用 "delivery": "detached"。响应结构保持不变,但 reviewThreadId 会是新建评审线程的 id,因此会与原始 threadId 不同。服务端也会在开始流出评审 turn 之前,先为这个新线程发出一条 thread/started 通知。

Codex 会先流出常规的 turn/started 通知,随后再发出一条带有 enteredReviewMode item 的 item/started

{
  "method": "item/started",
  "params": {
    "item": {
      "type": "enteredReviewMode",
      "id": "turn_900",
      "review": "current changes"
    }
  }
}

当评审智能体 reviewer 完成后,服务端会发出 item/starteditem/completed,其中都包含一个带最终评审文本的 exitedReviewMode item:

{
  "method": "item/completed",
  "params": {
    "item": {
      "type": "exitedReviewMode",
      "id": "turn_900",
      "review": "Looks solid overall..."
    }
  }
}

客户端应使用这类通知来渲染评审智能体输出。

进程执行

process/* 是实验性的显式进程控制 API。它需要 capabilities.experimentalApi = true,并且会在 Codex 沙箱之外运行。只有当你的客户端刻意暴露无沙箱的本地进程控制能力时,才使用它。

使用 process/spawn 启动进程,并提供一个 processHandle,后续 stdin、resize 和 kill 请求都使用这个 handle。输出会通过 process/outputDelta 通知流出,完成状态会通过 process/exited 流出。

{ "method": "process/spawn", "id": 48, "params": {
  "command": ["python3", "-m", "pytest", "-q"],
  "processHandle": "pytest-1",
  "cwd": "/Users/me/project",
  "tty": true
} }
{ "id": 48, "result": {} }
{ "method": "process/outputDelta", "params": {
  "processHandle": "pytest-1",
  "stream": "stdout",
  "deltaBase64": "Li4u"
} }
{ "method": "process/exited", "params": {
  "processHandle": "pytest-1",
  "exitCode": 0
} }

使用 process/writeStdin 通过 deltaBase64closeStdin 或二者组合发送输入。使用 process/resizePty 发送 PTY 尺寸变化事件,使用 process/kill 终止运行中的进程。

命令执行

command/exec 用于在不创建 thread 的情况下,直接在服务端沙箱内执行一条命令:

{ "method": "command/exec", "id": 50, "params": {
  "command": ["ls", "-la"],
  "cwd": "/Users/me/project",
  "sandboxPolicy": { "type": "workspaceWrite" },
  "timeoutMs": 10000
} }
{ "id": 50, "result": { "exitCode": 0, "stdout": "...", "stderr": "" } }

如果你的服务端进程本身已经运行在外部沙箱内,可以使用 sandboxPolicy.type = "externalSandbox",让 Codex 跳过它自带的沙箱。对于外部沙箱,networkAccess 可设为 restricted(默认)或 enabled;对于 readOnlyworkspaceWrite,则沿用上面介绍过的 access / readOnlyAccess 结构。

补充说明:

  • 服务端会拒绝空的 command 数组。
  • sandboxPolicy 接受与 turn/start 相同的结构,例如 dangerFullAccessreadOnlyworkspaceWriteexternalSandbox
  • 省略 timeoutMs 时,会回退到服务端默认超时时间。
  • tty: true 时,响应里会返回 processId,并可继续配合 command/exec/writecommand/exec/resizecommand/exec/terminate
  • streamStdoutStderr: true 时,stdout / stderr 会通过 command/exec/outputDelta 通知持续流出。

读取管理员要求(configRequirements/read)

使用 configRequirements/read 可以查看从 requirements.toml 和 / 或 MDM 加载的当前生效管理员要求。

{ "method": "configRequirements/read", "id": 52, "params": {} }
{ "id": 52, "result": {
  "requirements": {
    "allowedApprovalPolicies": ["onRequest", "unlessTrusted"],
    "allowedSandboxModes": ["readOnly", "workspaceWrite"],
    "featureRequirements": {
      "personality": true,
      "unified_exec": false
    },
    "network": {
      "enabled": true,
      "allowedDomains": ["api.openai.com"],
      "allowUnixSockets": ["/tmp/example.sock"],
      "dangerouslyAllowAllUnixSockets": false
    }
  }
} }

如果当前没有配置任何 requirements,result.requirements 会是 null。有关支持的键和值,请点击查看 requirements.toml

Windows 沙箱设置(windowsSandbox/setupStart)

自定义 Windows 客户端可以异步触发沙箱初始化,而不是在启动时同步阻塞:

{ "method": "windowsSandbox/setupStart", "id": 53, "params": { "mode": "elevated" } }
{ "id": 53, "result": { "started": true } }

随后服务端会发出完成通知:

{
  "method": "windowsSandbox/setupCompleted",
  "params": { "mode": "elevated", "success": true, "error": null }
}

模式值包括:

  • elevated:执行提升权限的 Windows 沙箱初始化路径。
  • unelevated:执行旧版的初始化 / 预检路径。

文件系统

v2 文件系统 API 作用于绝对路径。当客户端需要在文件或目录变化后让 UI 状态失效时,可以使用 fs/watch

{ "method": "fs/watch", "id": 54, "params": {
  "watchId": "0195ec6b-1d6f-7c2e-8c7a-56f2c4a8b9d1",
  "path": "/Users/me/project/.git/HEAD"
} }
{ "id": 54, "result": { "path": "/Users/me/project/.git/HEAD" } }
{ "method": "fs/changed", "params": {
  "watchId": "0195ec6b-1d6f-7c2e-8c7a-56f2c4a8b9d1",
  "changedPaths": ["/Users/me/project/.git/HEAD"]
} }
{ "method": "fs/unwatch", "id": 55, "params": {
  "watchId": "0195ec6b-1d6f-7c2e-8c7a-56f2c4a8b9d1"
} }
{ "id": 55, "result": {} }

Watch 文件时,文件通过替换或重命名完成更新也会为该文件路径发出 fs/changed

事件

事件通知是服务端主动发出的流,用来描述线程生命周期、turn 生命周期,以及其中各个 item 的状态变化。在线程启动或恢复后,客户端应持续从当前传输流中读取 thread/startedthread/archivedthread/unarchivedthread/closedthread/status/changedturn/*item/*serverRequest/resolved 这些通知。

通知退订

客户端可以通过在 initialize.params.capabilities.optOutNotificationMethods 中传入精确的方法名,按连接关闭某些指定通知。

  • 只支持精确匹配:例如 item/agentMessage/delta 只会关闭这一种方法。
  • 未知的方法名会被忽略。
  • 适用于当前连接上的 thread/*turn/*item/* 以及相关的 v2 通知。
  • 不适用于请求、响应或错误。

模糊文件搜索事件(实验性)

模糊文件搜索会为每次查询发出:

  • fuzzyFileSearch/sessionUpdated{ sessionId, query, files },表示当前查询的匹配结果。
  • fuzzyFileSearch/sessionCompleted{ sessionId },表示该查询的索引与匹配工作已完成。

Windows 沙箱设置事件

Windows 沙箱初始化相关的异步通知是:

  • windowsSandbox/setupCompleted{ mode, success, error },表示某次 windowsSandbox/setupStart 请求已经完成。

回合事件

常见 turn 事件包括:

  • turn/started{ turn },其中包含 turn id、空的 items,以及 status: "inProgress"
  • turn/completed{ turn },其中 turn.status 可能是 completedinterruptedfailed;失败时会带 { error: { message, codexErrorInfo?, additionalDetails? } }
  • turn/diff/updated{ threadId, turnId, diff },表示该 turn 目前聚合出的最新 unified diff。
  • turn/plan/updated{ turnId, explanation?, plan },表示智能体新增或更新了计划;每个 plan 条目都是 { step, status },其中 status 取值为 pendinginProgresscompleted
  • thread/tokenUsage/updated:当前活动线程的用量更新。

当前版本里,turn/diff/updatedturn/plan/updated 即便在 item 事件正在流出时,items 数组通常也是空的。因此客户端应以 item/* 事件作为 turn items 的真实来源。

条目

ThreadItem 是 turn 响应和 item/* 通知里携带的联合类型。常见 item 包括:

  • userMessage{ id, content },其中 content 是用户输入条目列表(textimagelocalImage)。
  • agentMessage{ id, text, phase? },表示智能体当前累计输出;当存在 phase 时,使用 Responses API 的线协议取值(commentaryfinal_answer)。
  • plan{ id, text },表示 plan mode 下提出的计划文本。
  • reasoning{ id, summary, content },其中 summary 是流式推送的推理摘要,content 是原始 reasoning block。
  • commandExecution{ id, command, cwd, status, commandActions, aggregatedOutput?, exitCode?, durationMs? }
  • fileChange{ id, changes, status },表示建议的文件修改;changes 中每项都是 { path, kind, diff }
  • mcpToolCall{ id, server, tool, status, arguments, result?, error? }
  • dynamicToolCall{ id, tool, arguments, status, contentItems?, success?, durationMs? },表示由客户端执行的动态工具调用。
  • collabToolCall{ id, tool, status, senderThreadId, receiverThreadId?, newThreadId?, prompt?, agentStatus? }
  • webSearch{ id, query, action? },表示智能体发起的网页搜索。
  • imageView{ id, path },表示智能体调用了图像查看器。
  • enteredReviewMode{ id, review },表示评审模式已开始。
  • exitedReviewMode{ id, review },表示评审模式已结束,并附带最终评审文本。
  • contextCompaction{ id },表示 Codex 正在压缩会话历史。

对于 webSearch.action,其 type 可能是:

  • search(可带 query?queries?
  • openPage(可带 url?
  • findInPage(可带 url?pattern?

旧的 thread/compacted 通知已弃用;现在应使用 contextCompaction item。

所有 items 都遵循统一生命周期:

  • item/started:某个新工作单元开始时,发出完整 item;其中的 item.id 会与增量事件中的 itemId 对应。
  • item/completed:该工作单元结束时,发出最终 item;应把它视为该 item 的权威最终状态。

条目增量

细粒度增量事件包括:

  • item/agentMessage/delta:流式追加智能体消息文本。
  • item/plan/delta:流式追加计划文本;最终 plan item 不一定与所有 delta 的简单拼接完全一致。
  • item/reasoning/summaryTextDelta:流式追加可读的推理摘要文本;summaryIndex 会在开启新摘要段时递增。
  • item/reasoning/summaryPartAdded:标记一段新的推理摘要分段开始。
  • item/reasoning/textDelta:流式追加原始推理文本(如果模型支持)。
  • item/commandExecution/outputDelta:按顺序流出命令的 stdout / stderr 增量。
  • item/fileChange/outputDelta:面向旧版 apply_patch 文本输出的已弃用兼容通知。当前 app-server 版本不再发出它;请改用 fileChange items 和 turn/diff/updated

错误

如果某个 turn 失败,服务端会先发出一个 error 事件,内容为 { error: { message, codexErrorInfo?, additionalDetails? } },然后再以 status: "failed" 结束该 turn。

如果存在上游 HTTP 状态码,它会出现在 codexErrorInfo.httpStatusCode 中。

常见的 codexErrorInfo 取值包括:

  • ContextWindowExceeded
  • UsageLimitExceeded
  • HttpConnectionFailed(上游 4xx/5xx 错误)
  • ResponseStreamConnectionFailed
  • ResponseStreamDisconnected
  • ResponseTooManyFailedAttempts
  • BadRequestUnauthorizedSandboxErrorInternalServerErrorOther

当存在上游 HTTP 状态码时,服务端会把它透传到对应 codexErrorInfo 变体上的 httpStatusCode 字段。

审批

根据用户当前的 Codex 设置,命令执行和文件变更都可能需要审批。app-server 会向客户端主动发起 JSON-RPC 请求,客户端再返回决策负载。

  • 命令执行的决策包括:acceptacceptForSessiondeclinecancel,或 { "acceptWithExecpolicyAmendment": { "execpolicy_amendment": ["cmd", "..."] } }

  • 文件变更的决策包括:acceptacceptForSessiondeclinecancel

  • 这些请求都会带上 threadIdturnId,客户端应据此将界面状态绑定到当前会话。

  • 服务端会根据决策恢复或拒绝相应工作,并以 item/completed 结束该 item。

命令执行审批

消息顺序如下:

  1. item/started 会发出待审批的 commandExecution item,其中包含 commandcwd 等字段。
  2. item/commandExecution/requestApproval 会包含 itemIdthreadIdturnId,以及可选的 reasoncommandcwdcommandActionsproposedExecpolicyAmendmentnetworkApprovalContextavailableDecisions。当 initialize.params.capabilities.experimentalApi = true 时,负载还可能包含实验性的 additionalPermissions,用于描述按命令申请的额外 sandbox 权限。additionalPermissions 中的任何文件系统路径在线路上传输时都为绝对路径。
  3. 客户端返回上述某个命令执行决策。
  4. serverRequest/resolved 确认该待处理请求已得到答复或被清理。
  5. item/completed 返回最终的 commandExecution item,其 statuscompleted | failed | declined

当存在 networkApprovalContext 时,这个提示针对的是托管网络访问,而不是普通的 shell 命令审批。当前 v2 schema 会暴露目标 hostprotocol;客户端应渲染网络专用提示,不要依赖 command 作为对用户有意义的 shell 命令预览。

Codex 会按目标(host、协议和端口)对并发网络审批提示进行分组。因此,app-server 可能只发送一个提示,就放行多个发往同一目标的排队请求;但同一主机上的不同端口仍会分别处理。

文件变更审批

消息顺序如下:

  1. item/started 会发出一个 fileChange item,其中包含建议的 changes,且 status: "inProgress"
  2. item/fileChange/requestApproval 会包含 itemIdthreadIdturnId,以及可选的 reasongrantRoot
  3. 客户端返回上述某个文件变更决策。
  4. serverRequest/resolved 确认该待处理请求已得到答复或被清理。
  5. item/completed 返回最终的 fileChange item,其 statuscompleted | failed | declined

tool/requestUserInput

当客户端响应 item/tool/requestUserInput 时,app-server 会发出带有 { threadId, requestId }serverRequest/resolved

如果在客户端答复之前,该待处理请求因 turn 启动、turn 完成或 turn 中断而被清理,服务端也会针对这次清理发出同样的通知。

动态工具调用(实验性)

thread/start 上的 dynamicTools 以及对应的 item/tool/call 请求 / 响应流程都属于实验性 API。

动态工具名和 namespace 名必须符合 Responses API 命名约束。请避免使用 Codex 内建工具保留的 namespace 名称。

当某个动态工具在 turn 期间被调用时,app-server 会依次发出:

  1. item/started,其中 item.type = "dynamicToolCall"status = "inProgress",并带上 toolarguments
  2. item/tool/call,作为服务端发给客户端的请求。
  3. 客户端响应负载,其中包含返回的 content items。
  4. item/completed,其中 item.type = "dynamicToolCall",并带上最终 status,以及任何返回的 contentItemssuccess 值。

MCP 工具调用审批(Apps)

App(connector)工具调用也可能需要审批。当某个 app 工具调用带有副作用时,服务端可能通过 tool/requestUserInput 发起审批,并提供 AcceptDeclineCancel 等选项。即使某个工具同时声明了权限更低的提示,只要它带有 destructive 工具注解,仍然一定会触发审批。

如果用户选择拒绝或取消,相关 mcpToolCall item 会以错误结束,而不会执行该工具。

技能

要调用某个技能,可在用户文本输入中加入 $<skill-name>。建议同时附带一个 skill 输入项,这样服务端会直接注入完整的技能说明,而不是依赖模型自行解析这个名称。

{
  "method": "turn/start",
  "id": 101,
  "params": {
    "threadId": "thread-1",
    "input": [
      {
        "type": "text",
        "text": "$skill-creator Add a new skill for triaging flaky CI."
      },
      {
        "type": "skill",
        "name": "skill-creator",
        "path": "/Users/me/.codex/skills/skill-creator/SKILL.md"
      }
    ]
  }
}

如果你省略 skill 输入项,模型仍会尝试解析 $skill-name 并查找对应技能,但这通常会增加延迟。

示例:

$skill-creator Add a new skill for triaging flaky CI and include step-by-step usage.

使用 skills/list 可获取当前可用技能;你可以按 cwds 作用域查询,也可以通过 forceReload 强制刷新。perCwdExtraUserRoots 则允许你为特定 cwd 增加额外的用户级技能目录。对于那些 cwd 不在 cwds 列表中的条目,app-server 会直接忽略。

服务端会按 cwd 维护缓存;如果你希望强制重新扫描磁盘,设置 forceReload: true。当存在 SKILL.json 时,服务端还会读取其中的 interfacedependencies

被 watch 的本地技能文件变化时,服务端也会发出 skills/changed 通知。把它视为失效信号;需要时用当前参数重新调用 skills/list

要按路径启用或禁用某个技能,可使用:

{
  "method": "skills/config/write",
  "id": 26,
  "params": {
    "path": "/Users/me/.codex/skills/skill-creator/SKILL.md",
    "enabled": false
  }
}

Apps(连接器)

使用 app/list 可获取当前可用的 apps。在 CLI / TUI 中,面向用户的选择器是 /apps;在自定义客户端中,则应直接调用 app/list。每个条目都会同时包含 isAccessible(用户是否可访问)和 isEnabled(是否在 config.toml 中启用),这样客户端就能区分安装 / 访问状态与本地启用状态。app 条目还可能包含可选的 brandingappMetadatalabels 字段。

示例:

{ "method": "app/list", "id": 50, "params": {
  "cursor": null,
  "limit": 50,
  "threadId": "thread-1",
  "forceRefetch": false
} }
{ "id": 50, "result": {
  "data": [
    {
      "id": "demo-app",
      "name": "Demo App",
      "description": "Example connector for documentation.",
      "logoUrl": "https://example.com/demo-app.png",
      "logoUrlDark": null,
      "distributionChannel": null,
      "branding": null,
      "appMetadata": null,
      "labels": null,
      "installUrl": "https://chatgpt.com/apps/demo-app/demo-app",
      "isAccessible": true,
      "isEnabled": true
    }
  ],
  "nextCursor": null
} }

如果你传入了 threadId,app 的功能开关判定(features.apps)会基于该 thread 的配置快照;如果不传,则使用最新的全局配置。

app/list 会在“用户可访问的 apps”和“目录来源的 apps”都加载完成后返回。若你想绕过缓存,可设置 forceRefetch: true;缓存只有在刷新成功时才会被替换。

服务端还会在任一来源(用户可访问的 apps 或目录来源的 apps)完成加载时发出 app/list/updated 通知。每条通知都会带上最新合并后的 app 列表。

{
  "method": "app/list/updated",
  "params": {
    "data": [
      {
        "id": "demo-app",
        "name": "Demo App",
        "description": "Example connector for documentation.",
        "logoUrl": "https://example.com/demo-app.png",
        "logoUrlDark": null,
        "distributionChannel": null,
        "branding": null,
        "appMetadata": null,
        "labels": null,
        "installUrl": "https://chatgpt.com/apps/demo-app/demo-app",
        "isAccessible": true,
        "isEnabled": true
      }
    ]
  }
}

要调用某个 app,可在文本输入中插入 $<app-slug>,并添加一个 mention input item,其 pathapp://<id>(推荐):

{
  "method": "turn/start",
  "id": 51,
  "params": {
    "threadId": "thread-1",
    "input": [
      {
        "type": "text",
        "text": "$demo-app Pull the latest updates from the team."
      },
      {
        "type": "mention",
        "name": "Demo App",
        "path": "app://demo-app"
      }
    ]
  }
}

用于 App 设置的 Config RPC 示例

可使用 config/readconfig/value/writeconfig/batchWrite 查看或更新 config.toml 中的 app 控制项。

读取当前生效的 app 配置结构(包括 _default 和按工具覆盖的配置):

{ "method": "config/read", "id": 60, "params": { "includeLayers": false } }
{ "id": 60, "result": {
  "config": {
    "apps": {
      "_default": {
        "enabled": true,
        "destructive_enabled": true,
        "open_world_enabled": true
      },
      "google_drive": {
        "enabled": true,
        "destructive_enabled": false,
        "default_tools_approval_mode": "prompt",
        "tools": {
          "files/delete": { "enabled": false, "approval_mode": "approve" }
        }
      }
    }
  }
} }

更新单个 app 设置:

{
  "method": "config/value/write",
  "id": 61,
  "params": {
    "keyPath": "apps.google_drive.default_tools_approval_mode",
    "value": "prompt",
    "mergeStrategy": "replace"
  }
}

以原子方式一次应用多项 app 修改:

{
  "method": "config/batchWrite",
  "id": 62,
  "params": {
    "edits": [
      {
        "keyPath": "apps._default.destructive_enabled",
        "value": false,
        "mergeStrategy": "upsert"
      },
      {
        "keyPath": "apps.google_drive.tools.files/delete.approval_mode",
        "value": "approve",
        "mergeStrategy": "upsert"
      }
    ]
  }
}

检测并导入外部智能体配置

使用 externalAgentConfig/detect 可以发现可被迁移的外部智能体配置项,然后将选中的条目传给 externalAgentConfig/import

探测示例:

{ "method": "externalAgentConfig/detect", "id": 63, "params": {
  "includeHome": true,
  "cwds": ["/Users/me/project"]
} }
{ "id": 63, "result": {
  "items": [
    {
      "itemType": "AGENTS_MD",
      "description": "Import /Users/me/project/CLAUDE.md to /Users/me/project/AGENTS.md.",
      "cwd": "/Users/me/project"
    },
    {
      "itemType": "SKILLS",
      "description": "Copy skill folders from /Users/me/.claude/skills to /Users/me/.agents/skills.",
      "cwd": null
    }
  ]
} }

导入示例:

{ "method": "externalAgentConfig/import", "id": 64, "params": {
  "migrationItems": [
    {
      "itemType": "AGENTS_MD",
      "description": "Import /Users/me/project/CLAUDE.md to /Users/me/project/AGENTS.md.",
      "cwd": "/Users/me/project"
    }
  ]
} }
{ "id": 64, "result": {} }

当请求包含插件导入时,服务端会在导入完成后发出 externalAgentConfig/import/completed。这个通知可能紧跟响应到达,也可能在后台远程导入完成后到达。

支持的 itemType 取值包括 AGENTS_MDCONFIGSKILLSPLUGINSMCP_SERVER_CONFIG。对于 PLUGINS 条目,details.plugins 会列出每个 marketplaceName 以及 Codex 可尝试迁移的 pluginNames。探测结果只会返回仍有待处理工作的条目。例如,如果 AGENTS.md 已存在且非空,Codex 就会跳过 AGENTS 迁移;技能导入也不会覆盖已存在的技能目录。

.claude/settings.json 检测插件时,Codex 会从 extraKnownMarketplaces 读取已配置的 marketplace 来源。如果 enabledPlugins 包含来自 claude-plugins-official 的插件,但缺少 marketplace 来源,Codex 会推断其来源为 anthropics/claude-plugins-official

认证接口

JSON-RPC 的 auth/account 接口既包括请求 / 响应方法,也包括服务端主动发出的通知(不带 id)。你可以用这些接口来判断认证状态、启动或取消登录、执行登出、查看 ChatGPT 速率限制,并在 credits 耗尽或触达使用限制时通知 workspace owner。

认证模式

Codex 支持以下认证模式。当前激活的模式会出现在 account/updated.authMode 中;可用时,account/updated 也会包含当前 ChatGPT planTypeaccount/read 也会返回账户和计划细节。

  • API key(apikey:调用方用 type: "apiKey" 提供 OpenAI API key,Codex 保存它并用于发起 API 请求。
  • ChatGPT 托管(chatgpt:Codex 自行管理 ChatGPT OAuth 流程,持久化令牌并自动刷新。浏览器流程使用 type: "chatgpt",device-code 流程使用 type: "chatgptDeviceCode"
  • ChatGPT 外部令牌(chatgptAuthTokens:实验性模式,适用于已经自行管理用户 ChatGPT 认证生命周期的宿主应用。宿主应用直接提供 accessTokenchatgptAccountId 和可选的 chatgptPlanType,并在需要时刷新 token。

API 概览

  • account/read:读取当前账户信息;也可选择同时刷新令牌。
  • account/login/start:启动登录流程(apiKeychatgptchatgptDeviceCode 或实验性的 chatgptAuthTokens)。
  • account/login/completed(通知):一次登录尝试结束时发出,无论成功还是失败。
  • account/login/cancel:按 loginId 取消尚未完成的托管 ChatGPT 登录流程。
  • account/logout:执行登出;会触发 account/updated
  • account/updated(通知):认证模式发生变化时发出;authMode 可能是 apikeychatgptchatgptAuthTokensnull,可用时还会包含 planType
  • account/chatgptAuthTokens/refresh(服务端请求):在遇到授权错误后,请求宿主应用提供新的外部管理 ChatGPT 令牌。
  • account/rateLimits/read:读取 ChatGPT 速率限制。
  • account/rateLimits/updated(通知):用户的 ChatGPT 速率限制发生变化时发出。
  • account/sendAddCreditsNudgeEmail:请求 ChatGPT 在 credits 耗尽或触达使用限制时向 workspace owner 发送邮件。
  • mcpServer/oauthLogin/completed(通知):某次 mcpServer/oauth/login 流程完成后发出,负载中包含 { name, success, error? }
  • mcpServer/startupStatus/updated(通知):已配置 MCP server 在已加载线程中的启动状态变化时发出;负载包含 { name, status, error }

1)检查认证状态

请求:

{ "method": "account/read", "id": 1, "params": { "refreshToken": false } }

响应示例:

{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": false } }
{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": true } }
{
  "id": 1,
  "result": { "account": { "type": "apiKey" }, "requiresOpenaiAuth": true }
}
{
  "id": 1,
  "result": {
    "account": {
      "type": "chatgpt",
      "email": "user@example.com",
      "planType": "pro"
    },
    "requiresOpenaiAuth": true
  }
}

字段说明:

  • refreshToken(boolean):设为 true 时,会在 ChatGPT 托管模式下强制刷新一次令牌。在外部令牌模式(chatgptAuthTokens)下,app-server 会忽略这个字段。
  • requiresOpenaiAuth:反映当前激活的 provider 是否要求 OpenAI 凭据;如果为 false,说明 Codex 可以在没有 OpenAI 凭据的情况下运行。

2)使用 API key 登录

  1. 发送:

    {
      "method": "account/login/start",
      "id": 2,
      "params": { "type": "apiKey", "apiKey": "sk-..." }
    }
  2. 预期响应:

    { "id": 2, "result": { "type": "apiKey" } }
  3. 通知:

    {
      "method": "account/login/completed",
      "params": { "loginId": null, "success": true, "error": null }
    }
    {
      "method": "account/updated",
      "params": { "authMode": "apikey", "planType": null }
    }

3)使用 ChatGPT 登录(浏览器流程)

  1. 启动:

    { "method": "account/login/start", "id": 3, "params": { "type": "chatgpt" } }
    {
      "id": 3,
      "result": {
        "type": "chatgpt",
        "loginId": "<uuid>",
        "authUrl": "https://chatgpt.com/...&redirect_uri=http%3A%2F%2Flocalhost%3A<port>%2Fauth%2Fcallback"
      }
    }
  2. 在浏览器中打开 authUrl;app-server 会托管本地回调地址。

  3. 等待通知:

    {
      "method": "account/login/completed",
      "params": { "loginId": "<uuid>", "success": true, "error": null }
    }
    {
      "method": "account/updated",
      "params": { "authMode": "chatgpt", "planType": "plus" }
    }

3b)使用 ChatGPT 登录(device-code 流程)

当你的客户端自行负责登录仪式,或浏览器回调不稳定时,可以使用这个流程。

  1. 启动:

    {
      "method": "account/login/start",
      "id": 4,
      "params": { "type": "chatgptDeviceCode" }
    }
    {
      "id": 4,
      "result": {
        "type": "chatgptDeviceCode",
        "loginId": "<uuid>",
        "verificationUrl": "https://auth.openai.com/codex/device",
        "userCode": "ABCD-1234"
      }
    }
  2. 向用户展示 verificationUrluserCode;前端负责具体 UX。

  3. 等待通知:

    {
      "method": "account/login/completed",
      "params": { "loginId": "<uuid>", "success": true, "error": null }
    }
    {
      "method": "account/updated",
      "params": { "authMode": "chatgpt", "planType": "plus" }
    }

3c)使用外部管理的 ChatGPT 令牌登录(chatgptAuthTokens)

只有当宿主应用自行管理用户的 ChatGPT 认证生命周期并直接提供令牌时,才使用这一实验性模式。客户端必须先在 initialize 期间设置 capabilities.experimentalApi = true,然后才能使用这种登录类型。

  1. 发送:

    {
      "method": "account/login/start",
      "id": 7,
      "params": {
        "type": "chatgptAuthTokens",
        "accessToken": "<jwt>",
        "chatgptAccountId": "org-123",
        "chatgptPlanType": "business"
      }
    }
  2. 预期响应:

    { "id": 7, "result": { "type": "chatgptAuthTokens" } }
  3. 通知:

    {
      "method": "account/login/completed",
      "params": { "loginId": null, "success": true, "error": null }
    }
    {
      "method": "account/updated",
      "params": { "authMode": "chatgptAuthTokens", "planType": "business" }
    }

当服务端收到 401 Unauthorized 时,它可能向宿主应用请求刷新后的令牌:

{
  "method": "account/chatgptAuthTokens/refresh",
  "id": 8,
  "params": { "reason": "unauthorized", "previousAccountId": "org-123" }
}
{ "id": 8, "result": { "accessToken": "<jwt>", "chatgptAccountId": "org-123", "chatgptPlanType": "business" } }

在成功收到刷新响应后,服务端会重试原始请求。请求大约会在 10 秒后超时。

4)取消 ChatGPT 登录

{ "method": "account/login/cancel", "id": 4, "params": { "loginId": "<uuid>" } }
{ "method": "account/login/completed", "params": { "loginId": "<uuid>", "success": false, "error": "..." } }

5)退出登录

{ "method": "account/logout", "id": 5 }
{ "id": 5, "result": {} }
{ "method": "account/updated", "params": { "authMode": null, "planType": null } }

6)速率限制(ChatGPT)

{ "method": "account/rateLimits/read", "id": 6 }
{ "id": 6, "result": {
  "rateLimits": {
    "limitId": "codex",
    "limitName": null,
    "primary": { "usedPercent": 25, "windowDurationMins": 15, "resetsAt": 1730947200 },
    "secondary": null,
    "rateLimitReachedType": null
  },
  "rateLimitsByLimitId": {
    "codex": {
      "limitId": "codex",
      "limitName": null,
      "primary": { "usedPercent": 25, "windowDurationMins": 15, "resetsAt": 1730947200 },
      "secondary": null,
      "rateLimitReachedType": null
    },
    "codex_other": {
      "limitId": "codex_other",
      "limitName": "codex_other",
      "primary": { "usedPercent": 42, "windowDurationMins": 60, "resetsAt": 1730950800 },
      "secondary": null,
      "rateLimitReachedType": null
    }
  }
} }
{ "method": "account/rateLimits/updated", "params": {
  "rateLimits": {
    "limitId": "codex",
    "primary": { "usedPercent": 31, "windowDurationMins": 15, "resetsAt": 1730948100 }
  }
} }

字段说明:

  • rateLimits:向后兼容的单配额桶视图。
  • rateLimitsByLimitId:如果存在,则表示多配额桶视图;按计量使用的 limit_id 为键,例如 codex
  • limitId:计量配额桶的标识符。
  • limitName:面向用户的可选配额桶标签。
  • usedPercent:当前配额窗口内的使用比例。
  • windowDurationMins:配额窗口长度。
  • resetsAt:下一次重置的 Unix 时间戳(秒)。
  • planType:server 返回某个 bucket 所属 ChatGPT plan 时会包含。
  • credits:server 返回剩余 workspace credit 详情时会包含。
  • rateLimitReachedType:触达限制时,用于标识 server 分类出的限制状态。

7)通知 workspace owner 处理额度

使用 account/sendAddCreditsNudgeEmail 可以在 credits 耗尽或触达使用限制时,请求 ChatGPT 给 workspace owner 发送邮件。

{ "method": "account/sendAddCreditsNudgeEmail", "id": 7, "params": { "creditType": "credits" } }
{ "id": 7, "result": { "status": "sent" } }

当 workspace credits 耗尽时使用 creditType: "credits";当 workspace usage limit 已触达时使用 creditType: "usage_limit"。如果 owner 近期已经收到过提醒,响应状态会是 cooldown_active