fix: 避免 Codex 默认模型覆盖配置

This commit is contained in:
Codex
2026-05-29 16:27:26 +08:00
parent 0b388a2c5e
commit cfaebb4e8b
3 changed files with 46 additions and 4 deletions
+9 -3
View File
@@ -311,13 +311,13 @@ export async function runCodexStdioTurn(options: CodexStdioTurnOptions): Promise
const threadMethod = options.threadId ? "thread/resume" : "thread/start";
const threadParams: JsonRecord = options.threadId
? { threadId: options.threadId, model: options.model ?? "default", cwd: options.cwd, approvalPolicy: options.approvalPolicy, sandbox: options.sandbox }
: { model: options.model ?? "default", cwd: options.cwd, approvalPolicy: options.approvalPolicy, sandbox: options.sandbox, serviceName: "agentrun" };
? withOptionalModel({ threadId: options.threadId, cwd: options.cwd, approvalPolicy: options.approvalPolicy, sandbox: options.sandbox }, options.model)
: withOptionalModel({ cwd: options.cwd, approvalPolicy: options.approvalPolicy, sandbox: options.sandbox, serviceName: "agentrun" }, options.model);
const threadResponse = requireResponseRecord(await client.request(threadMethod, threadParams, requestTimeoutMs), threadMethod);
threadId = requireNestedId(threadResponse, threadMethod, "thread");
events.push({ type: "backend_status", payload: { phase: `${threadMethod}:completed`, threadId } });
const turnResponse = requireResponseRecord(await client.request("turn/start", { threadId, input: textInput(options.prompt), cwd: options.cwd, approvalPolicy: options.approvalPolicy, model: options.model ?? "default" }, requestTimeoutMs), "turn/start");
const turnResponse = requireResponseRecord(await client.request("turn/start", withOptionalModel({ threadId, input: textInput(options.prompt), cwd: options.cwd, approvalPolicy: options.approvalPolicy }, options.model), requestTimeoutMs), "turn/start");
turnId = requireNestedId(turnResponse, "turn/start", "turn");
events.push({ type: "backend_status", payload: { phase: "turn/start:completed", turnId } });
@@ -450,6 +450,12 @@ function terminalStatusFromValue(value: unknown): TerminalStatus {
return "failed";
}
function withOptionalModel(params: JsonRecord, model: string | undefined): JsonRecord {
const value = typeof model === "string" ? model.trim() : "";
if (!value) return params;
return { ...params, model: value };
}
function childEnv(options: CodexStdioTurnOptions, codexHome: string): NodeJS.ProcessEnv {
return {
...process.env,
+10 -1
View File
@@ -33,6 +33,15 @@ const selfTest: SelfTestCase = async (context) => {
await access(path.join(projectedHome, "auth.json"));
await access(path.join(projectedHome, "config.toml"));
const configModel = await createRunWithCommand(client, context, "hello config model", "selftest-config-model", 15_000);
const configModelResult = await runOnce({ managerUrl: server.baseUrl, runId: configModel.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_FAKE_CODEX_MODE: "reject-unexpected-model" } });
assert.equal(configModelResult.terminalStatus, "completed", "unspecified model should be omitted so Codex config.toml remains authoritative");
const explicitModel = await createRunWithCommand(client, context, "hello explicit model placeholder", "selftest-explicit-model-placeholder", 15_000);
const explicitCommand = await client.post(`/api/v1/runs/${explicitModel.runId}/commands`, { type: "turn", payload: { prompt: "hello explicit model", model: "gpt-5.5" }, idempotencyKey: "selftest-explicit-model-command" }) as { id: string };
const explicitModelResult = await runOnce({ managerUrl: server.baseUrl, runId: explicitModel.runId, commandId: explicitCommand.id, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_FAKE_CODEX_MODE: "require-explicit-model" } });
assert.equal(explicitModelResult.terminalStatus, "completed", "explicit command payload model should still be forwarded");
await runFailureCase({ client, managerUrl: server.baseUrl, context, mode: "missing-turn-result", expectedStatus: "failed", expectedFailureKind: "backend-response-invalid" });
await runFailureCase({ client, managerUrl: server.baseUrl, context, mode: "provider-503-rpc-error", expectedStatus: "failed", expectedFailureKind: "provider-unavailable" });
await runFailureCase({ client, managerUrl: server.baseUrl, context, mode: "provider-503-terminal", expectedStatus: "failed", expectedFailureKind: "provider-unavailable" });
@@ -41,7 +50,7 @@ const selfTest: SelfTestCase = async (context) => {
await runFailureCase({ client, managerUrl: server.baseUrl, context, mode: "missing-terminal", expectedStatus: "failed", expectedFailureKind: "backend-timeout", timeoutMs: 500 });
await runSpawnFailureCase({ client, managerUrl: server.baseUrl, context });
return { name: "codex-stdio", tests: ["runner-lease-heartbeat", "codex-stdio-fake-turn", "codex-stdio-projected-writable-home", "codex-stdio-missing-turn-result", "codex-stdio-provider-503-rpc-error", "codex-stdio-provider-503-terminal", "codex-stdio-provider-503-retry-event", "codex-stdio-invalid-json", "codex-stdio-timeout", "codex-stdio-spawn-failure"] };
return { name: "codex-stdio", tests: ["runner-lease-heartbeat", "codex-stdio-fake-turn", "codex-stdio-projected-writable-home", "codex-stdio-config-model-authoritative", "codex-stdio-explicit-model-forwarded", "codex-stdio-missing-turn-result", "codex-stdio-provider-503-rpc-error", "codex-stdio-provider-503-terminal", "codex-stdio-provider-503-retry-event", "codex-stdio-invalid-json", "codex-stdio-timeout", "codex-stdio-spawn-failure"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}
+27
View File
@@ -4,6 +4,7 @@ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity
const mode = process.env.AGENTRUN_FAKE_CODEX_MODE ?? "success";
let threadCounter = 0;
let turnCounter = 0;
let observedThreadModel = false;
for await (const line of rl) {
const trimmed = String(line).trim();
@@ -18,6 +19,15 @@ for await (const line of rl) {
continue;
}
if (message.method === "thread/start") {
observedThreadModel = Object.hasOwn(message.params ?? {}, "model");
if (mode === "reject-unexpected-model" && observedThreadModel) {
respond(message.id, null, { code: -32000, message: "thread/start unexpectedly included model" });
continue;
}
if (mode === "require-explicit-model" && message.params?.model !== "gpt-5.5") {
respond(message.id, null, { code: -32000, message: "thread/start did not include expected model" });
continue;
}
threadCounter += 1;
const thread = { id: `thread_selftest_${threadCounter}` };
notify("thread/started", { thread });
@@ -25,12 +35,29 @@ for await (const line of rl) {
continue;
}
if (message.method === "thread/resume") {
observedThreadModel = Object.hasOwn(message.params ?? {}, "model");
if (mode === "reject-unexpected-model" && observedThreadModel) {
respond(message.id, null, { code: -32000, message: "thread/resume unexpectedly included model" });
continue;
}
if (mode === "require-explicit-model" && message.params?.model !== "gpt-5.5") {
respond(message.id, null, { code: -32000, message: "thread/resume did not include expected model" });
continue;
}
const thread = { id: String(message.params?.threadId ?? "thread_selftest_resumed") };
notify("thread/started", { thread });
respond(message.id, { thread });
continue;
}
if (message.method === "turn/start") {
if (mode === "reject-unexpected-model" && (observedThreadModel || Object.hasOwn(message.params ?? {}, "model"))) {
respond(message.id, null, { code: -32000, message: "turn/start unexpectedly included model" });
continue;
}
if (mode === "require-explicit-model" && message.params?.model !== "gpt-5.5") {
respond(message.id, null, { code: -32000, message: "turn/start did not include expected model" });
continue;
}
if (mode === "missing-turn-result") {
respond(message.id, {});
continue;