Merge pull request #22 from pikasTech/fix/v01-codex-config-model
fix: 避免 Codex 默认模型覆盖配置
This commit is contained in:
@@ -311,13 +311,13 @@ export async function runCodexStdioTurn(options: CodexStdioTurnOptions): Promise
|
|||||||
|
|
||||||
const threadMethod = options.threadId ? "thread/resume" : "thread/start";
|
const threadMethod = options.threadId ? "thread/resume" : "thread/start";
|
||||||
const threadParams: JsonRecord = options.threadId
|
const threadParams: JsonRecord = options.threadId
|
||||||
? { threadId: options.threadId, model: options.model ?? "default", cwd: options.cwd, approvalPolicy: options.approvalPolicy, sandbox: options.sandbox }
|
? withOptionalModel({ threadId: options.threadId, cwd: options.cwd, approvalPolicy: options.approvalPolicy, sandbox: options.sandbox }, options.model)
|
||||||
: { model: options.model ?? "default", cwd: options.cwd, approvalPolicy: options.approvalPolicy, sandbox: options.sandbox, serviceName: "agentrun" };
|
: withOptionalModel({ cwd: options.cwd, approvalPolicy: options.approvalPolicy, sandbox: options.sandbox, serviceName: "agentrun" }, options.model);
|
||||||
const threadResponse = requireResponseRecord(await client.request(threadMethod, threadParams, requestTimeoutMs), threadMethod);
|
const threadResponse = requireResponseRecord(await client.request(threadMethod, threadParams, requestTimeoutMs), threadMethod);
|
||||||
threadId = requireNestedId(threadResponse, threadMethod, "thread");
|
threadId = requireNestedId(threadResponse, threadMethod, "thread");
|
||||||
events.push({ type: "backend_status", payload: { phase: `${threadMethod}:completed`, threadId } });
|
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");
|
turnId = requireNestedId(turnResponse, "turn/start", "turn");
|
||||||
events.push({ type: "backend_status", payload: { phase: "turn/start:completed", turnId } });
|
events.push({ type: "backend_status", payload: { phase: "turn/start:completed", turnId } });
|
||||||
|
|
||||||
@@ -450,6 +450,12 @@ function terminalStatusFromValue(value: unknown): TerminalStatus {
|
|||||||
return "failed";
|
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 {
|
function childEnv(options: CodexStdioTurnOptions, codexHome: string): NodeJS.ProcessEnv {
|
||||||
return {
|
return {
|
||||||
...process.env,
|
...process.env,
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ const selfTest: SelfTestCase = async (context) => {
|
|||||||
await access(path.join(projectedHome, "auth.json"));
|
await access(path.join(projectedHome, "auth.json"));
|
||||||
await access(path.join(projectedHome, "config.toml"));
|
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: "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-rpc-error", expectedStatus: "failed", expectedFailureKind: "provider-unavailable" });
|
||||||
await runFailureCase({ client, managerUrl: server.baseUrl, context, mode: "provider-503-terminal", 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 runFailureCase({ client, managerUrl: server.baseUrl, context, mode: "missing-terminal", expectedStatus: "failed", expectedFailureKind: "backend-timeout", timeoutMs: 500 });
|
||||||
await runSpawnFailureCase({ client, managerUrl: server.baseUrl, context });
|
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 {
|
} finally {
|
||||||
await new Promise<void>((resolve) => server.server.close(() => resolve()));
|
await new Promise<void>((resolve) => server.server.close(() => resolve()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity
|
|||||||
const mode = process.env.AGENTRUN_FAKE_CODEX_MODE ?? "success";
|
const mode = process.env.AGENTRUN_FAKE_CODEX_MODE ?? "success";
|
||||||
let threadCounter = 0;
|
let threadCounter = 0;
|
||||||
let turnCounter = 0;
|
let turnCounter = 0;
|
||||||
|
let observedThreadModel = false;
|
||||||
|
|
||||||
for await (const line of rl) {
|
for await (const line of rl) {
|
||||||
const trimmed = String(line).trim();
|
const trimmed = String(line).trim();
|
||||||
@@ -18,6 +19,15 @@ for await (const line of rl) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (message.method === "thread/start") {
|
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;
|
threadCounter += 1;
|
||||||
const thread = { id: `thread_selftest_${threadCounter}` };
|
const thread = { id: `thread_selftest_${threadCounter}` };
|
||||||
notify("thread/started", { thread });
|
notify("thread/started", { thread });
|
||||||
@@ -25,12 +35,29 @@ for await (const line of rl) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (message.method === "thread/resume") {
|
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") };
|
const thread = { id: String(message.params?.threadId ?? "thread_selftest_resumed") };
|
||||||
notify("thread/started", { thread });
|
notify("thread/started", { thread });
|
||||||
respond(message.id, { thread });
|
respond(message.id, { thread });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (message.method === "turn/start") {
|
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") {
|
if (mode === "missing-turn-result") {
|
||||||
respond(message.id, {});
|
respond(message.id, {});
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user