Skip to content

Commit 0221e35

Browse files
committed
fix(config-agent): 优化 Codex 配置写入与兼容性处理
- 调整 Codex 代理默认 wire_api 为 "responses",兼容新版 Codex - 增加对 legacy Codex <= 0.80.0 使用 wire_api "chat" 的警告提示 - 修正 agent flags 描述,更准确说明 wire_api 默认与兼容范围 - 优化代码格式,统一 import 语句风格 - 增加测试用例覆盖不同 wire_api 配置及环境变量警告 - 修复写入过程中文件备份及合并逻辑,保留用户已有配置 - 修复多个 provider 写入时键名与内容匹配,避免重复添加 - 改善测试代码格式,提高可读性与一致性
1 parent ff469ce commit 0221e35

5 files changed

Lines changed: 96 additions & 150 deletions

File tree

packages/commands/src/commands/config/agent/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const FLAGS = {
3838
type: "string",
3939
valueHint: "<api>",
4040
description:
41-
'Codex only: wire protocol "chat" works with every model; "responses" for models supporting the Responses API (default: chat)',
41+
'Codex only: wire protocol (default: responses). "chat" only works with legacy Codex <= 0.80.0',
4242
choices: ["chat", "responses"],
4343
},
4444
} satisfies FlagsDef;

packages/commands/src/commands/config/agent/writers/codex.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,23 @@ import { homedir } from "os";
22
import { join } from "path";
33
import { existsSync, readFileSync } from "fs";
44
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
5-
import {
6-
backup,
7-
readJson,
8-
writeJsonAtomic,
9-
writeTextAtomic,
10-
type AgentDef,
11-
} from "./utils.ts";
5+
import { backup, readJson, writeJsonAtomic, writeTextAtomic, type AgentDef } from "./utils.ts";
126

137
const PROVIDER_KEY = "bailian-cli";
148

159
export default {
1610
label: "Codex",
1711
write({ baseUrl, apiKey, model, wireApi: wireApiParam }) {
1812
const configPath = join(homedir(), ".codex", "config.toml");
13+
const warnings: string[] = [];
1914

2015
// config.toml — merge into existing config so unrelated settings
2116
// (mcp_servers, approval_policy, other providers, ...) are preserved.
2217
backup(configPath);
2318
let config: Record<string, unknown> = {};
2419
if (existsSync(configPath)) {
2520
try {
26-
config = parseToml(readFileSync(configPath, "utf-8")) as Record<
27-
string,
28-
unknown
29-
>;
21+
config = parseToml(readFileSync(configPath, "utf-8")) as Record<string, unknown>;
3022
} catch {
3123
config = {};
3224
}
@@ -35,9 +27,18 @@ export default {
3527
config.model_provider = PROVIDER_KEY;
3628
config.model = model;
3729

38-
// wire_api: "responses" for models supporting the Responses API (e.g.
39-
// qwen3.7/3.8 series); "chat" works with every model via Chat Completions.
40-
const wireApi = wireApiParam === "responses" ? "responses" : "chat";
30+
// wire_api — current Codex releases only load `wire_api = "responses"`
31+
// ("chat" is rejected at config load, see openai/codex discussion #7782).
32+
// "chat" remains an explicit opt-in for users pinned to legacy Codex
33+
// <= 0.80.0 (the Model Studio path for models without Responses support).
34+
const wireApi = wireApiParam === "chat" ? "chat" : "responses";
35+
if (wireApi === "chat") {
36+
warnings.push(
37+
'Current Codex releases refuse to load `wire_api = "chat"`; ' +
38+
"only use --wire-api chat with legacy Codex <= 0.80.0 " +
39+
"(e.g. `npm install -g @openai/codex@0.80.0`).",
40+
);
41+
}
4142

4243
const providers = (config.model_providers ?? {}) as Record<string, unknown>;
4344
const existing = (providers[PROVIDER_KEY] ?? {}) as Record<string, unknown>;
@@ -65,6 +66,7 @@ export default {
6566
return {
6667
paths: [configPath, authPath],
6768
nextStep: "Run `codex` to start using Codex with DashScope.",
69+
warnings: warnings.length > 0 ? warnings : undefined,
6870
};
6971
},
7072
} satisfies AgentDef;

packages/commands/tests/config-agent-writers.test.ts

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,11 @@ describe("config agent writers", () => {
214214
});
215215

216216
test("qwen-code anthropic 端点走 anthropic 协议", () => {
217-
qwenCode.write({ baseUrl: ANTHROPIC_URL, apiKey: "sk-q", model: "qwen3-max" });
217+
qwenCode.write({
218+
baseUrl: ANTHROPIC_URL,
219+
apiKey: "sk-q",
220+
model: "qwen3-max",
221+
});
218222
const settings = readJsonAt(".qwen", "settings.json");
219223
expect((settings.security as { auth: { selectedType: string } }).auth.selectedType).toBe(
220224
"anthropic",
@@ -225,8 +229,16 @@ describe("config agent writers", () => {
225229
});
226230

227231
test("qwen-code 对自有 provider 项按 id upsert 而非追加", () => {
228-
qwenCode.write({ baseUrl: OAI_URL, apiKey: "sk-1", model: "qwen3-coder-plus" });
229-
qwenCode.write({ baseUrl: OAI_URL, apiKey: "sk-2", model: "qwen3-coder-plus" });
232+
qwenCode.write({
233+
baseUrl: OAI_URL,
234+
apiKey: "sk-1",
235+
model: "qwen3-coder-plus",
236+
});
237+
qwenCode.write({
238+
baseUrl: OAI_URL,
239+
apiKey: "sk-2",
240+
model: "qwen3-coder-plus",
241+
});
230242
const settings = readJsonAt(".qwen", "settings.json");
231243
const openaiEntries = (settings.modelProviders as Record<string, unknown[]>).openai;
232244
expect(openaiEntries).toHaveLength(1);
@@ -327,7 +339,11 @@ describe("config agent writers", () => {
327339
JSON.stringify({ provider: { other: { name: "Other" } } }),
328340
);
329341

330-
opencode.write({ baseUrl: ANTHROPIC_URL, apiKey: "sk-o", model: "qwen3-max" });
342+
opencode.write({
343+
baseUrl: ANTHROPIC_URL,
344+
apiKey: "sk-o",
345+
model: "qwen3-max",
346+
});
331347
const config = readJsonAt(".config", "opencode", "opencode.json");
332348
const provider = config.provider as Record<string, Record<string, unknown>>;
333349
expect(provider.other).toBeDefined();
@@ -351,7 +367,11 @@ describe("config agent writers", () => {
351367
});
352368

353369
test("openclaw 写入 provider、api、primary,并登记 defaults.models", () => {
354-
openclaw.write({ baseUrl: OAI_URL, apiKey: "sk-c", model: "qwen3-coder-plus" });
370+
openclaw.write({
371+
baseUrl: OAI_URL,
372+
apiKey: "sk-c",
373+
model: "qwen3-coder-plus",
374+
});
355375
const config = readJsonAt(".openclaw", "openclaw.json");
356376
const models = config.models as Record<string, unknown>;
357377
expect(models.mode).toBe("merge");
@@ -496,19 +516,21 @@ describe("config agent writers", () => {
496516
// 预置 auth.json 无关键,验证合并保留
497517
writeFileSync(join(home, ".codex", "auth.json"), JSON.stringify({ EXISTING: "keep" }));
498518

499-
codex.write({
519+
const summary = codex.write({
500520
baseUrl: OAI_URL,
501521
apiKey: "sk-x",
502522
model: "qwen3-coder-plus",
503523
});
524+
// 默认路径无警告
525+
expect(summary.warnings).toBeUndefined();
504526
const toml = readFileSync(join(home, ".codex", "config.toml"), "utf8");
505527
expect(toml).toContain('model_provider = "bailian-cli"');
506528
expect(toml).toContain('model = "qwen3-coder-plus"');
507529
expect(toml).toContain("[model_providers.bailian-cli]");
508530
expect(toml).toContain(`base_url = "${OAI_URL}"`);
509531
expect(toml).toContain('env_key = "OPENAI_API_KEY"');
510-
// 未传 --wire-api 时默认 chat(所有模型可用
511-
expect(toml).toContain('wire_api = "chat"');
532+
// 未传 --wire-api 时默认 responses(新版 Codex 已不支持 chat
533+
expect(toml).toContain('wire_api = "responses"');
512534
expect(toml).toContain("requires_openai_auth = true");
513535
// 合并:保留用户已有的无关配置
514536
expect(toml).toContain('approval_policy = "on-request"');
@@ -518,16 +540,17 @@ describe("config agent writers", () => {
518540
expect(auth.OPENAI_API_KEY).toBe("sk-x");
519541
expect(auth.EXISTING).toBe("keep");
520542

521-
// --wire-api responses:支持 Responses API 的模型
522-
codex.write({
543+
// --wire-api chat:仅旧版 Codex <= 0.80.0 可用,附带警告
544+
const summary2 = codex.write({
523545
baseUrl: OAI_URL,
524546
apiKey: "sk-x",
525-
model: "qwen3.7-plus",
526-
wireApi: "responses",
547+
model: "glm-5",
548+
wireApi: "chat",
527549
});
550+
expect(summary2.warnings?.some((warning) => warning.includes("0.80.0"))).toBe(true);
528551
const toml2 = readFileSync(join(home, ".codex", "config.toml"), "utf8");
529-
expect(toml2).toContain('wire_api = "responses"');
530-
expect(toml2).toContain('model = "qwen3.7-plus"');
552+
expect(toml2).toContain('wire_api = "chat"');
553+
expect(toml2).toContain('model = "glm-5"');
531554
});
532555

533556
test("已存在的配置文件会被备份为 .bak.<epoch>", () => {

0 commit comments

Comments
 (0)