一、config.toml

更新时间:2026-07-26。基于 OpenAI Codex Manual、OpenAI Codex GitHub 源码、Codex CLI 0.146.0-alpha.3.1,以及本地 ~/.codex/config.toml 实践整理。文中的“当前配置”是这个日期的本机快照;本文只讲本地 Codex App / CLI / IDE Extension 会读取的配置,不讲云端 Codex 任务的后台环境配置。其中 tui.* 只作用于 CLI 的终端界面,不代表 App 或 IDE 的通知行为。

config.tomlCodex 本地客户端的持久默认配置。是告诉 Codex:默认用什么模型、请求发给哪个 provider、命令执行权限多大、启用哪些 MCP / plugin / 本地偏好。

可以先把它理解成 Codex 启动时会读取的一组默认值:

  • 模型请求modelmodel_providermodel_providers.*model_reasoning_effort
  • 执行权限sandbox_modeapproval_policy,以及新版 default_permissions / [permissions.*]
  • 外部工具mcp_servers.*plugins.*features.*
  • 自动动作notifyhooks.*
  • 本地体验tui.*desktop.*、项目 trust 状态。

它不负责三件事:

  • 登录态本身:ChatGPT 登录、API key 存储、OAuth token 通常在 auth.json、系统钥匙串或环境变量里。
  • 项目写作规则:仓库里的 AGENTS.md 是给 Agent 的行为指导,不是 config.toml
  • 第三方中转服务本身config.toml 只告诉 Codex 怎么连中转;中转平台如何鉴权、如何把请求转给后端模型,是中转服务自己的事。

用户级配置文件更准确的定位方式是:

$CODEX_HOME/config.toml

CODEX_HOME 未显式设置时通常是 ~/.codex,所以常见路径是 /Users/markz/.codex/config.toml。启动器或发行版也可以设置不同的 CODEX_HOME;例如当前 ducx 环境使用 /Users/markz/.baidu-cx/config.toml。可以先看当前 shell 有没有显式设置它:

printenv CODEX_HOME

没有输出通常表示回退到 ~/.codex,但这只能证明当前 shell 的环境。如果是通过启动器、别名或 App 进入 Codex,应在相同的启动环境中执行:

codex doctor --json

报告里的 checks.config.load.details.CODEX_HOMEconfig.toml 会给出该次运行实际解析的路径;即使认证或网络检查导致总体状态为 fail,仍可以单独看 config.load 结果。启动器如果只给子进程注入环境变量,就要通过同一启动器调用 doctor,或用完全相同的 argv 和环境重现;单独在另一个 shell 里运行 printenv 不足以确认。

下面的顶层字段专指 /Users/markz/.codex/config.toml 这份本机快照,不是 ducx.baidu-cx 配置:

sandbox_mode = "workspace-write"
notify = ["/Users/markz/.codex/computer-use/Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient", "turn-ended"]
service_tier = "priority"
model = "gpt-5.6-sol"
model_provider = "oneapi"
model_catalog_json = "/Users/markz/.codex/models-with-grok.json"
model_reasoning_effort = "xhigh"

含义是:用户级默认模型是 gpt-5.6-sol,模型请求默认交给 oneapi provider,使用 xhigh 推理强度并请求 priority 服务通道;本地命令默认处在 workspace 可写、非全盘可写 的沙箱里。notify 是回合完成后的外部程序回调,和模型请求路径无关。
它不等于每次运行最终一定这样,因为 CLI 参数、profile、项目配置可能覆盖它。

字段当前值作用边界
sandbox_modeworkspace-write本地命令的沙箱模式。Codex 可以在工作区范围内写文件,例如当前项目目录、允许的 workspace roots。它不等于“全盘可写”。写工作区外的文件、联网、提权命令,仍可能被拒绝或需要审批。
service_tierpriority模型服务档位 / 服务通道请求。源码注释里常见值包括 defaultpriorityflex,旧版 fast 也兼容。它影响 Codex 请求模型服务时偏向哪个服务通道,是否生效取决于账号、模型和 provider;它不是模型名,也不是权限开关。
model_reasoning_effortxhigh推理强度。值越高,通常表示希望模型在复杂推理、代码理解、排查问题时投入更多 reasoning。它只在模型 / provider 支持时才有意义;可能带来更慢响应或更高消耗,不改变请求发往哪个 provider。
modelgpt-5.6-sol默认模型名。Codex 默认会向当前 provider 请求这个模型。它只回答“用哪个模型”,不回答“请求发到哪里”。请求出口要继续看 model_provider[model_providers.*]
model_provideroneapi默认模型请求出口。选择对应的 [model_providers.oneapi]profile、CLI 参数或会话设置仍可能覆盖它。
model_catalog_json本地 JSON 路径模型目录来源。决定 /model 列表及能力声明。不负责请求出口和鉴权。
notifySkyComputerUseClient turn-ended外部完成回调。Codex 回合完成后执行该 argv,并追加事件 JSON。它不是 TUI 桌面通知开关,也不能替代完整 Hook 生命周期。

二、最终配置从哪些层叠出来?

Codex 不是只读一个文件,而是把多层配置合并成最终配置。同一个 key 如果在多层都出现,优先级更高的层覆盖更低的层。

常见个人使用场景里,优先级可以这样记:

优先级配置层适合放什么
最高CLI flags / --config / -c本次临时覆盖,例如临时改模型、权限、MCP 开关
项目级 .codex/config.toml只对当前 trusted 项目生效的偏好
--profile 指定的 $CODEX_HOME/<profile>.config.toml一组可切换场景,例如深度审查、中转模型、只读分析
中低用户级 $CODEX_HOME/config.toml个人长期默认值;默认展开为 ~/.codex/config.toml
系统级 /etc/codex/config.toml机器级或团队默认值
最低Codex 内置默认值没有配置时才使用

⚠️ 企业 managed config / requirements 可能额外约束某些值,例如不允许 approval_policy = "never" 或不允许 full access。这类约束不是个人 config 能强行覆盖的。本文主要按个人本地配置讲。

显式值、内置默认值与最终有效值

config.toml 只记录显式配置,不会把 Codex 的全部默认值展开写回文件。某个字段没有出现,可能表示“功能未配置”,也可能表示“使用内置默认值”;必须结合字段定义判断,不能统一理解成 false

以 TUI 通知为例,Codex CLI 0.146.0-alpha.3.1 的源码把三个字段的默认值定义为:

[tui]
notifications = true
notification_method = "auto"
notification_condition = "unfocused"

这段配置即使没有出现在文件里,默认行为仍然成立:TUI 通知开启,发送方式由终端自动选择,并且只在终端失去焦点时投递。这里的代码块是在说明等价的默认行为,不是说 Codex 会自动把这三行写进 config.toml

因此排查配置时要区分三个问题:

  • 显式值:某一配置层实际写了什么;
  • 内置默认值:所有配置层都没写时,客户端采用什么;
  • 最终有效值:把配置层按优先级合并后,仅为已定义默认值的缺失字段补值;其余字段仍保持未配置。

CLI 的 -c / --config 直接覆盖本次进程,适合验证最终行为:

codex -c 'tui.notifications=false'

-c 后面的 key=value 按 TOML 解析。上面的 false 是布尔值;字符串值要保留 TOML 引号,例如 -c 'tui.notification_method="osc9"'。这种覆盖不会修改磁盘上的配置文件,进程结束后也不会保留。

三、每一层应该怎么用?

$CODEX_HOME/config.toml 适合放个人稳定默认值;在默认 home 下就是 ~/.codex/config.toml。比如常用模型、默认沙箱、MCP server、插件启用状态、通知与 Hook、TUI 状态栏、桌面端打开文件偏好、项目 trust 状态。

.codex/config.toml 适合放“这个项目里才需要”的配置。官方文档和源码都明确限制了项目配置的能力:项目配置只在 trusted 项目中加载,而且不能覆盖会重定向凭据、改变 provider 鉴权或执行本机通知/遥测的 key。

项目配置里会被忽略的典型 key 包括:

openai_base_url
chatgpt_base_url
model_provider
model_providers
notify
profile
profiles
otel

这条限制很重要:不要把模型中转 provider 写进项目 .codex/config.toml。provider 和认证相关配置应该放在用户级配置或 profile 文件里。
--profile 是“场景覆盖层”。Codex 0.134.0 以后,官方写法是单独建文件:

$CODEX_HOME/deep-review.config.toml
$CODEX_HOME/relay.config.toml

然后这样启动:

codex --profile relay

profile 文件里写顶层 key,不要写成 [profiles.relay]

# $CODEX_HOME/relay.config.toml
model = "DeepSeek-V4-Pro"
model_provider = "aimami_relay_4b291ef3da"
model_reasoning_effort = "high"

当前 /Users/markz/.codex/config.toml 里 AiMaMi 托管块还包含 [profiles.aimami_relay]。这块标了 DO NOT EDIT MANUALLY,应按 AiMaMi 管理块理解,不要当成新版 Codex 的手写 profile 模板。按当前官方文档和源码,CLI --profile xxx 应该对应 $CODEX_HOME/xxx.config.toml

四、模型配置先看三个字段

模型请求路径只需要先看三个字段:modelmodel_providermodel_providers.<id>

三者关系是:

  • model:模型名,例如 gpt-5.6-solDeepSeek-V4-Pro。它只回答“想用哪个模型”。
  • model_provider:provider id,例如 openaiaimami_relay_4b291ef3da。它回答“把模型请求交给谁发”。
  • [model_providers.<id>]:provider 连接方式。这里才定义 base_urlwire_api、认证方式、headers、重试和超时。
    如果顶层没有写 model_provider,通常会走默认 provider。当前用户级配置已经显式选择:
model = "gpt-5.6-sol"
model_provider = "oneapi"

所以不带其他覆盖启动时,请求会交给 [model_providers.oneapi]。排查“现在到底走哪个 provider”时仍不能只看这个文件的顶层,还要看本次启动有没有 --profile-c model_provider=...,以及 App 内是否有会话级选择覆盖。

五、中转配置就是一个 provider 例子

你写这篇文章的真实背景是:看不懂中转配置。中转不要先当成一个新概念,它本质上就是自定义 provider。
一个最小 provider 大概长这样:

model = "gpt-5.6-sol"
model_provider = "proxy"
 
[model_providers.proxy]
name = "OpenAI-compatible proxy"
base_url = "https://proxy.example.com/v1"
wire_api = "responses"
env_key = "PROXY_API_KEY"

这段配置的意思是:

  • 顶层 model_provider = "proxy" 选中下面的 [model_providers.proxy]
  • base_url 指向 OpenAI-compatible API 根地址,通常以 /v1 结尾。
  • wire_api = "responses" 表示 Codex 按 Responses API 协议组织请求。
  • env_key = "PROXY_API_KEY" 表示真正的 token 从环境变量 PROXY_API_KEY 取,不直接写死在 TOML 里。
    你的 AiMaMi 当前生成了两个 provider,其中 DeepSeek-V4-Pro 这段可以这样读:
[model_providers.aimami_relay_4b291ef3da]
name = "oneapi [ds4p]"
base_url = "http://127.0.0.1:25817/codex/by-provider/aimami_relay_4b291ef3da/v1"
wire_api = "responses"
supports_websockets = false
requires_openai_auth = false
 
[profiles.aimami_relay]
model_provider = "aimami_relay_4b291ef3da"
model = "DeepSeek-V4-Pro"

这里最关键的是:请求先发到本机 127.0.0.1:25817,再由 AiMaMi relay 转发到它管理的后端模型。requires_openai_auth = false 表示这个 provider 不要求 Codex 用 OpenAI 登录态去鉴权。
这段里还有 api_key = "aimami-relay"。在本文验证的 Codex CLI 0.146.0-alpha.3.1 源码中,ModelProviderInfo 没有把 api_key 定义为通用官方字段;官方字段里更常见的是 env_keyexperimental_bearer_token[model_providers.<id>.auth]。所以 api_key 这一行更应该按 AiMaMi 托管字段或兼容占位理解,不要把它当成手写官方 provider 的推荐写法。

六、provider 常用字段怎么读?

常用 provider 字段可以按用途分成四类:

字段作用常见写法
name显示名,便于 UI 或日志识别name = "OpenAI-compatible proxy"
base_url模型 API 根地址base_url = "https://proxy.example.com/v1"
wire_apiCodex 和 provider 之间的协议当前按新版本理解为 responses
env_key从环境变量读取 API keyenv_key = "PROXY_API_KEY"
experimental_bearer_token直接写 bearer token不推荐,除非临时或程序化场景
[model_providers.<id>.auth]通过命令动态获取 bearer token企业凭据助手、临时 token
http_headers静态附加 header{ "X-Client" = "codex" }
env_http_headers从环境变量读取 header 值{ "X-Token" = "TOKEN_ENV" }
query_params给请求 URL 附加 query特定网关要求时使用
request_max_retries普通 HTTP 请求重试次数网络不稳或网关偶发失败时调
stream_max_retries流式响应断开后的重连次数长输出、流式代理不稳时调
stream_idle_timeout_ms流式响应空闲多久算断开慢模型或中转慢时调大
requires_openai_auth是否需要 OpenAI 登录/API key 鉴权自定义中转通常是 false
supports_websockets是否支持 Responses WebSocket 传输普通 HTTP 中转通常是 false

如果 provider 需要命令动态取 token,官方写法是:

[model_providers.proxy]
name = "OpenAI-compatible proxy"
base_url = "https://proxy.example.com/v1"
wire_api = "responses"
 
[model_providers.proxy.auth]
command = "/usr/local/bin/fetch-codex-token"
args = ["--audience", "codex"]
timeout_ms = 5000
refresh_interval_ms = 300000

这里不要再同时写 env_keyrequires_openai_auth。官方文档明确说 command-backed auth 不应该和这些认证方式混用。

七、wire_api/v1/chat/completions 怎么理解?

你截图里那句 wire_api = "chat" 的意思是:让 Codex 用 Chat Completions 协议去请求 provider,也就是请求形态接近 /v1/chat/completions
但这个点必须按版本谨慎看:

  • 官方 Codex Manual 的模型页写过:Codex 可以指向支持 Chat Completions 或 Responses API 的 provider,但 Chat Completions 支持已经 deprecated。
  • 本文验证的 Codex CLI 0.146.0-alpha.3.1 源码里,WireApi 枚举只有 Responses;如果配置 wire_api = "chat",反序列化会报错,提示 wire_api = "chat" 不再支持。
    所以这篇笔记按当前新版本给结论:不要把 wire_api = "chat" 当成现在的推荐配置。如果某个中转平台后端只支持 /v1/chat/completions,更稳的做法是让中转平台自己把 Codex 的 Responses 请求转换成 Chat Completions 请求,而不是在 Codex 里强行写 wire_api = "chat"
    你的当前 AiMaMi 配置正好是:
wire_api = "responses"

所以它不是截图里说的 Chat Completions 模式。它更像是:Codex 按 Responses 协议请求本机 AiMaMi relay,relay 再决定怎么转给后端。

八、推理、服务档位和模型能力

模型字段之外,还有几个常见顶层字段:

字段作用你当前的值
model_reasoning_effort推理强度,常见值包括 lowmediumhighxhighxhigh
model_verbosity回答详略,官方说明只对 Responses API provider 生效未显式配置
service_tier服务档位,例如 fast / flex / priority 这类产品侧档位priority
model_context_window手动声明上下文窗口未显式配置

这里最容易误解的是:这些字段不是所有模型和所有 provider 都保证支持。特别是 model_verbosity,官方文档明确说 Chat Completions provider 会忽略它。也就是说,越偏“Codex 原生 Responses 能力”的 provider,越可能完整吃到这些控制项;越偏普通 OpenAI-compatible Chat 网关,越可能只能兼容一部分。

九、权限配置和模型配置分开看

权限配置回答的是“Codex 能不能执行命令、能写哪里、什么时候问你”,不是“模型请求发到哪里”。
常见旧式写法是:

sandbox_mode = "workspace-write"
approval_policy = "on-request"

两者分工不同:

  • sandbox_mode 控制文件系统和执行环境边界,例如 read-onlyworkspace-writedanger-full-access
  • approval_policy 控制什么时候停下来问你,例如 untrustedon-requestnever
    你当前写了:
sandbox_mode = "workspace-write"

这表示 Codex 默认可以在 workspace 范围内写文件,写 workspace 外、联网或提升权限时仍可能需要审批。你没有在顶层显式写 approval_policy,所以会走默认值或本次启动覆盖。
新版官方文档还提到 default_permissions[permissions.*]。它们是把文件系统、网络、workspace roots 等权限组合成可复用 profile 的新方式。官方建议:如果你开始用 permission profiles,就不要在同一个会话里再混用旧的 sandbox_mode / sandbox_workspace_write 思路。

十、MCP、Plugin、Project trust 在 config 里各自是什么?

mcp_servers.* 是工具入口配置。它告诉 Codex 怎么启动或连接一个 MCP server。
本地 stdio MCP 常见写法:

[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
env_vars = ["LOCAL_TOKEN"]
startup_timeout_sec = 20

远程 HTTP MCP 常见写法:

[mcp_servers.datapilot]
url = "https://datapilot.baidu-int.com/mcp"

你当前就有两类 MCP:

  • mcp_servers.node_repl:本地命令启动,带一批 env
  • mcp_servers.datapilot:远程 HTTP 地址。
    plugins.* 是插件启用状态。你的配置里启用了 figmachromecomputer-usebrowserdocumentsspreadsheetsiplugin 等插件。但插件启用不等于里面所有能力都一定可用,有些还要看产品授权、系统权限、MCP server 是否启动、外部服务是否登录。
    projects."<path>".trust_level 是项目 trust 状态。你的 /Users/markz/Desktop/sql 当前是:
[projects."/Users/markz/Desktop/sql"]
trust_level = "trusted"

所以这个目录下的项目级 .codex/config.toml、项目 hooks、项目 rules 才会被 Codex 加载。未 trusted 的项目会跳过这些 project-local 层。

十一、本地交互配置:TUI 通知、notify 与 Hook

模型、权限和外部工具决定 Codex“怎么工作”,本地交互配置决定“什么时候提醒用户、什么时候调用外部程序、什么时候介入生命周期”。tui.notifications、顶层 notify 和 Hook 都可能在回合结束附近产生动作,但它们不是同一个机制。本节的 TUI 默认值特指 CLI 终端界面;App 和 IDE 如何呈现通知,由各自的 UI 实现决定。

TUI 通知

TUI 通知由 Codex 在终端界面中直接发出,不需要额外脚本。前文列出的三个默认值分别控制是否发送、用什么终端协议发送,以及终端处于什么焦点状态时发送:

字段0.146.0-alpha.3.1 默认值作用
tui.notificationstrue开启全部 TUI 通知;也可以改成事件名数组
tui.notification_method"auto"自动选择 OSC 9 或 BEL
tui.notification_condition"unfocused"只在终端失去焦点时投递

当前版本的通知事件分为三类:

事件名触发场景
agent-turn-completeAgent 完成一个回合
approval-requested命令、文件修改或 MCP 请求等待审批
plan-mode-promptPlan 模式等待用户选择

如果只关心其中一部分,可以把布尔值改成事件名数组:

[tui]
notifications = ["agent-turn-complete", "approval-requested"]
notification_method = "auto"
notification_condition = "unfocused"

auto 会把 Ghostty、iTerm2、Kitty、Warp 和 WezTerm 识别为支持 OSC 9 的终端,其他终端回退到 BEL。OSC 9 通常表现为桌面通知;BEL 的实际效果取决于终端设置,可能是声音、标签闪烁或 Dock 提示。

这里还要区分“终端接收应用通知”和“终端监控任意命令完成”。Ghostty 默认允许终端内应用通过 OSC 9 / OSC 777 发桌面通知,但它自己的 notify-on-command-finish 默认是 never。因此 Codex 完成后出现 Ghostty 通知,通常是 Codex 主动发送 OSC 9,不代表 Ghostty 已经开启了通用命令完成提醒。

临时关闭 TUI 通知仍使用前文的 argv 覆盖:

codex -c 'tui.notifications=false'

外部 notify 回调

顶层 notify 是一条外部程序 argv。数组第一个元素是 executable,后续元素是固定参数;Codex 不把它当成一段 shell 字符串解析。当前配置:

notify = ["/Users/markz/.codex/computer-use/Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient", "turn-ended"]

Agent 回合完成后,Codex 会在这组 argv 末尾追加一个 JSON 参数。实际调用可以概括为:

SkyComputerUseClient turn-ended '{"type":"agent-turn-complete", ...}'

事件 JSON 包含 thread、turn、cwd、输入消息和最后一条 Assistant 消息等信息。因此 notify 只应指向自己信任的本地程序;如果 wrapper 还会转发或记录 JSON,需要同时检查日志和外发边界。当前这个回调用于让 Computer Use 客户端感知回合结束;如果顶层没有配置 notify,该功能默认不执行。它和 TUI 通知彼此独立,所以关闭 tui.notifications 不会阻止 SkyComputerUseClient 运行。

排查通知来源时可以在本次进程里同时关闭外部回调:

codex -c 'notify=[]' -c 'tui.notifications=false'

空 argv 不会启动外部程序;这仍然只是本次进程覆盖,不会删除原配置。需要把一个回合事件转发到多个目标时,应让 notify 指向一个稳定 wrapper,再由 wrapper 分发,不要把管道或多个命令直接塞进 argv 数组。

与 Hook 的职责边界

Codex 当前源码把顶层 notify 作为兼容回调接入 Hook 引擎,但它公开给用户的能力仍然很窄:只在 Agent 回合完成后被动启动一个外部程序。完整 Hook 则覆盖用户输入、工具执行、审批、上下文压缩、子 Agent 和结束等生命周期节点,并且部分事件能够阻断或改变后续流程。

机制未显式配置时触发范围能否干预流程
TUI 通知默认开启回合完成、审批、Plan 输入不能,只负责提醒
顶层 notify默认不执行Agent 回合完成不能,只启动外部程序
Hook没有 handler 就不执行多个生命周期事件取决于事件,可观察、阻断或补充上下文

因此,“一个 Agent 回合完成时提醒我”优先使用 TUI 通知;“回合完成时通知另一个本地程序”使用 notify;“工具执行前拦截危险命令、结束前检查测试结果”才使用 Hook。这里的“回合完成”不等于整个 CLI 进程退出。Hook 的事件、阻断协议和安全边界详见 Agent Hook 完全指南

十二、读你当前 config 的顺序

以后排查自己的配置,可以按这个顺序读,不要从头到尾逐行陷进去。
第一,看顶层默认值:

sandbox_mode = "workspace-write"
notify = [".../SkyComputerUseClient", "turn-ended"]
service_tier = "priority"
model = "gpt-5.6-sol"
model_provider = "oneapi"
model_reasoning_effort = "xhigh"

这决定默认模型、请求出口、推理强度、服务档位、沙箱和外部完成回调。没有写出的字段还要继续查内置默认值。
第二,看本次运行有没有覆盖层:

codex --profile ...
codex -c model=...
codex -c model_provider=...

这些比 $CODEX_HOME/config.toml 更优先。
第三,看模型请求是否走中转:

model_provider = "oneapi"

当前默认继续看 [model_providers.oneapi]base_urlwire_api 和认证字段;如果 profile 把 provider 切到 AiMaMi,再改看对应的 [model_providers.aimami_relay_*]
第四,看 MCP:

[mcp_servers.node_repl]
[mcp_servers.datapilot]

模型中转只影响模型请求,不会自动让 MCP、plugin、connector 获得授权。
第五,看 plugins、features、desktop、tui、notify 和 hooks。这里既有显式配置,也有 TUI 通知这类默认开启的行为;排查时要同时看配置文件、内置默认值和 argv 覆盖。

十三、常见配置模板

日常开发:workspace 可写,必要时问我

model = "gpt-5.6-sol"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
approval_policy = "on-request"
web_search = "cached"

只读分析:尽量不改文件

model = "gpt-5.6-sol"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
approval_policy = "untrusted"

中转模型:Responses 优先

model = "DeepSeek-V4-Pro"
model_provider = "proxy"
 
[model_providers.proxy]
name = "OpenAI-compatible proxy"
base_url = "https://proxy.example.com/v1"
wire_api = "responses"
env_key = "PROXY_API_KEY"
requires_openai_auth = false
supports_websockets = false

Profile 文件:把中转做成可切换场景

# $CODEX_HOME/relay.config.toml
model = "DeepSeek-V4-Pro"
model_provider = "proxy"
 
[model_providers.proxy]
name = "OpenAI-compatible proxy"
base_url = "https://proxy.example.com/v1"
wire_api = "responses"
env_key = "PROXY_API_KEY"

启动时:

codex --profile relay

十四、常见误区

误区一:改了 model 就等于改了请求出口。
不是。model 只是模型名,请求出口由 model_provider[model_providers.<id>] 决定。
误区二:项目 .codex/config.toml 可以配置中转。
不应该。项目级配置不能覆盖 model_providermodel_providersopenai_base_url 这类会重定向凭据或 provider 的 key。
误区三:wire_api = "chat" 是现在的通用解法。
不是。旧资料里它表示 Chat Completions 协议,但当前 Codex 源码已经只接受 responses。如果中转平台只支持 Chat,应该让中转层做协议转换,或者确认你使用的是仍支持 chat 的旧 Codex 版本。
误区四:插件启用了,工具就一定能用。
不一定。plugin 只是分发和启用入口;MCP 是否启动、App/Connector 是否授权、系统权限是否打开、workspace policy 是否允许,都会影响最终可用性。
误区五:web_search = "live" 等于 shell 命令可以联网。
不是。web_search 控制 Codex 的第一方 web search 工具;shell 里的 curlnpm installgit fetch 是否能联网,仍受 sandbox、network permission 和审批策略影响。
误区六:改了 model_catalog_json 指向的模型目录,就等于已经切到了某个自定义模型。
不是。模型目录只决定菜单和能力声明;当前默认模型看 model,请求出口看 model_provider
误区七:config.toml 没写某个布尔字段,就等于它是关闭的。
不一定。字段缺失后可能使用内置默认值;例如 tui.notifications 默认就是 true。要判断最终行为,必须同时检查配置层、字段默认值和本次 argv 覆盖。

十五、专题案例:model_catalog_json 与模型目录

到这里,常用配置的读法和本地通知边界已经完整。下面保留一个更深的模型目录排查案例;没有配置 model_catalog_json 时可以直接跳到参考链接。

前面讲的 model / model_provider 只回答“默认用哪个模型、请求发给谁”。model_catalog_json 还会决定 /model 菜单里会出现哪些模型、每个模型支持哪些 reasoning 档位、缺字段时会不会直接启动失败

1. 它是不是 model 读取的?

是,但只负责“模型元数据目录”,不负责真正发请求。

先看这张图,再看下面的表:

可以把它拆成三层看:

配置回答的问题不回答的问题
模型目录model_catalog_json = "/path/to/models.json"/model 列表有哪些项、显示名、默认 reasoning、是否显示请求发到哪里、怎么鉴权
当前选中模型model = "my-model"这次会话默认选哪个 slug这个 slug 有没有出现在目录里、能不能解析目录
请求出口model_provider + [model_providers.*]请求发到哪个 base_url、用什么凭据菜单里显示哪些模型

一句话:

  • 模型目录决定 菜单和模型能力声明
  • model 决定 当前默认选中哪个
  • model_provider 决定 真正请求打到哪里

最小接线只需要在 config.toml 里加一行:

# $CODEX_HOME/config.toml
model_catalog_json = "/absolute/path/to/models-catalog.json"

多个客户端如果都指向同一份目录,菜单项会一致;各自的默认 model 仍可不同。

2. 启动时怎么读?

启动时可以先按下面这条链路理解:

文字版链路:

读取 config.toml
  -> 看到 model_catalog_json
  -> 解析 JSON
  -> 校验每个 model 是否包含必填字段
  -> 过滤 visibility == "list" 的项给 /model 菜单
  -> 用 model / profile / CLI -m 决定当前选中项
  -> 真正发请求时再走 model_provider

如果 JSON 解析或 schema 校验失败,Codex 会在启动阶段直接报错,连 TUI 都进不去。典型错误类似:

Error loading configuration: failed to parse model_catalog_json path
`/absolute/path/to/models-catalog.json` as JSON:
missing field `supports_reasoning_summaries` at line N column M

注意:这里写 “as JSON”,但文件本身可以是合法 JSON;失败点常常是 字段 schema 不完整,不是花括号写坏了。

3. supports_reasoning_summaries 是什么?

它是模型目录里的一个 布尔能力字段,表示客户端是否按“支持 reasoning summary”处理这个模型。

和它相邻、容易一起混的字段有:

字段类型作用
supports_reasoning_summariesbool客户端是否按“支持 reasoning summary”处理这个模型
default_reasoning_summarystring默认 summary 策略,常见如 "none"
supported_reasoning_levelsarray这个模型允许切换哪些 reasoning effort
default_reasoning_levelstring切到这个模型时默认 effort
model_reasoning_effort(在 config.toml)string用户/会话层当前选中的 effort,覆盖默认值

关键边界:

  • 不是 模型名,也 不是 provider。
  • 不是 “开了就一定有 reasoning 文本给你看”;还受 default_reasoning_summary、客户端 UI、provider 实际能力影响。
  • 在当前 CLI 里它更像 schema 必填能力声明。缺了就解析失败;补上合法布尔值后,客户端才能完成目录加载。

一个常见修复就是给目录里每个模型补上:

"supports_reasoning_summaries": true

这只影响模型目录能否被解析,不自动改默认模型,也不改 provider。

4. 模型目录怎么读?

文件顶层通常很简单,只有一个数组:

{
  "models": [
    { "...一个模型..." },
    { "...另一个模型..." }
  ]
}

visibility 决定是否进菜单:

  • list:出现在 /model
  • hide:目录里保留,但不默认展示

下面用一个自定义模型条目举例。注意:真实 JSON 文件不能写注释;这里用 JSONC 只为讲解字段。

// 真实 models-catalog.json 不能写注释。
// 真正发请求不看这份目录,而看 config.toml 里的 model_provider。
{
  // 模型 ID。必须和 config.toml / profile 里的 model = "..." 完全一致
  "slug": "my-model",
 
  // /model 菜单上显示的名字
  "display_name": "My Model",
 
  // 菜单项说明文案
  "description": "A custom model exposed through the configured relay.",
 
  // list = 出现在 /model;hide = 目录里有但不展示
  "visibility": "list",
 
  // 菜单排序权重,数字越小通常越靠前
  "priority": 4,
 
  // 切到这个模型时的默认 reasoning 档位
  "default_reasoning_level": "high",
 
  // 这个模型允许切换的 reasoning 档位列表
  "supported_reasoning_levels": [
    { "effort": "low", "description": "Fast responses with lighter reasoning" },
    { "effort": "medium", "description": "Balances speed and reasoning depth for everyday tasks" },
    { "effort": "high", "description": "Greater reasoning depth for complex problems" },
    { "effort": "xhigh", "description": "Extra high reasoning depth for complex problems" }
  ],
 
  // 是否按“支持 reasoning summary”处理;当前 CLI 里接近 schema 必填字段
  // 缺了可能启动失败:missing field `supports_reasoning_summaries`
  "supports_reasoning_summaries": true,
 
  // 默认 summary 策略。none = 默认不强调输出 reasoning summary
  "default_reasoning_summary": "none",
 
  // 是否支持 verbosity(回答详略)相关控制
  "support_verbosity": true,
 
  // 默认回答详略
  "default_verbosity": "low",
 
  // 本地 shell 工具形态。shell_command = 走命令执行工具
  "shell_type": "shell_command",
 
  // 声明的上下文窗口大小(token)
  "context_window": 272000,
 
  // 允许的最大上下文窗口
  "max_context_window": 272000,
 
  // 是否倾向走 responses lite 路径;false 表示按完整 responses 能力处理
  "use_responses_lite": false
}

字段可以按用途分组读:

分组字段你怎么理解
菜单展示slugdisplay_namedescriptionvisibilitypriority/model 显示什么、排第几、是否隐藏
Reasoning 能力supported_reasoning_levelsdefault_reasoning_levelsupports_reasoning_summariesdefault_reasoning_summary能切哪些 effort、默认多少、summary 怎么处理
输出与工具形态support_verbositydefault_verbosityshell_typeapply_patch_tool_typeweb_search_tool_type回答详略、命令壳类型、补丁/搜索工具形态
上下文context_windowmax_context_windoweffective_context_window_percent上下文窗口声明
系统提示base_instructionsmodel_messages这个模型在客户端侧的基础指令模板;通常很长,不建议手改
协议偏好use_responses_litesupports_search_tool客户端倾向怎么调用模型能力

slug 必须和你在 config.toml / profile 里写的 model 对得上。例如:

model = "my-model"

这里的 my-model 就是目录里的 slug,不是 display_name

5. 目录有了,不等于请求一定能通

这是最容易混的一点。

目录只保证:

  1. Codex 能启动并解析模型列表。
  2. /model 里能看到对应菜单项。
  3. 客户端知道它支持哪些 reasoning 档位和部分能力开关。

真正请求还要继续看 provider。很多自定义模型并不是 Codex 直连上游,而是先打到本地/自建 relay,再由 relay 转发。一个通用例子:

model_provider = "relay"
 
[model_providers.relay]
name = "Local relay"
base_url = "http://127.0.0.1:3000/v1"
env_key = "RELAY_API_KEY"
wire_api = "responses"
requires_openai_auth = false

如果只是偶尔切到某个自定义模型,也可以做成 profile,而不是改默认:

# $CODEX_HOME/my-model.config.toml
model_provider = "relay"
model = "my-model"

启动:

codex --profile my-model

于是:

  • 菜单项来自模型目录
  • 当前模型变成 my-model
  • 请求出口变成本地 relay

如果只改目录、不配 provider,结果通常是“菜单里有这项,但一发请求就找不到正确出口或鉴权失败”。

6. 只加选项、不改默认,应该怎么配

目标如果是“默认模型不变,只多一个可选项”,正确组合是:

# $CODEX_HOME/config.toml
model = "gpt-5.6-sol"   # 保持你原来的默认
model_catalog_json = "/absolute/path/to/models-catalog.json"
 
[model_providers.relay]
name = "Local relay"
base_url = "http://127.0.0.1:3000/v1"
env_key = "RELAY_API_KEY"
wire_api = "responses"
requires_openai_auth = false

需要切到自定义模型时:

  1. TUI 里 /model 选择对应项
  2. codex --profile my-model

不要把“加一个选项”理解成必须执行“把默认 model 永久改成自定义模型”。那会改默认,不是“只加选项”。

token / API key 应放在环境变量或系统钥匙串里,不要写进模型目录,也不要直接写进可分享的笔记正文。

7. 排查顺序

以后再遇到“模型菜单/自定义模型”问题,按这个顺序看:

  1. config.toml 有没有 model_catalog_json
  2. 路径文件在不在、是不是合法 JSON
  3. 每个 model 是否缺必填字段,尤其是新版本新增字段如 supports_reasoning_summaries
  4. 目标模型 visibility 是不是 list
  5. model / profile 里的 slug 是否等于目录里的 slug
  6. 当前 model_provider[model_providers.*] 是否指向可用 provider
  7. env_key 对应环境变量有没有值

前 4 步挂了,常见表现是启动失败或菜单没有该项;后 3 步挂了,常见表现是菜单有项但请求失败。

十六、参考链接


相关笔记