mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-07 00:44:49 +00:00
fix(codex): start sessions on the project's configured model and effort
A session created without a model or reasoning effort read Codex defaults without its cwd, so a trusted project's .codex/config.toml was ignored and the global values were sent as explicit overrides. Read the effective config for the session cwd first and keep getUserSavedConfig as the fallback for older Codex builds. Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
This commit is contained in:
@@ -1117,6 +1117,42 @@ describe("Codex app-server provider", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("starts a session without a model on the project's Codex model and effort", async () => {
|
||||
const cwd = "/workspace/project";
|
||||
const appServer = createFakeCodexAppServer({
|
||||
"config/read": (params) => ({
|
||||
config:
|
||||
(params as { cwd?: unknown }).cwd === cwd
|
||||
? { model: "project-model", model_reasoning_effort: "high" }
|
||||
: { model: "global-model", model_reasoning_effort: "low" },
|
||||
}),
|
||||
getUserSavedConfig: () => ({
|
||||
config: { model: "global-model", modelReasoningEffort: "low" },
|
||||
}),
|
||||
});
|
||||
const session = new CodexAppServerAgentSession(
|
||||
createConfig({ cwd, model: undefined }),
|
||||
null,
|
||||
createTestLogger(),
|
||||
async () => appServer.child,
|
||||
);
|
||||
|
||||
try {
|
||||
await session.startTurn("use project defaults");
|
||||
|
||||
await expect(appServer.waitForRequest("thread/start")).resolves.toMatchObject({
|
||||
model: "project-model",
|
||||
});
|
||||
await expect(appServer.waitForTurnStart()).resolves.toMatchObject({
|
||||
model: "project-model",
|
||||
effort: "high",
|
||||
});
|
||||
appServer.assertNoErrors();
|
||||
} finally {
|
||||
await session.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("preserves cwd-resolved Codex writable roots under an explicit workflow mode", async () => {
|
||||
const appServer = createFakeCodexAppServer({
|
||||
"config/read": () => ({
|
||||
|
||||
@@ -3100,10 +3100,32 @@ const CodexNotificationSchema = z.union([
|
||||
),
|
||||
]);
|
||||
|
||||
// config/read resolves the effective layered config, including a trusted project's
|
||||
// .codex/config.toml when given its cwd; getUserSavedConfig predates it.
|
||||
async function readCodexConfiguredDefaults(
|
||||
client: CodexAppServerClient,
|
||||
logger: Logger,
|
||||
cwd: string | null,
|
||||
): Promise<CodexConfiguredDefaults> {
|
||||
let configReadDefaults: CodexConfiguredDefaults = {};
|
||||
try {
|
||||
const response = toObjectRecord(await client.request("config/read", { cwd }));
|
||||
const config = toObjectRecord(response?.config);
|
||||
const modelValue = typeof config?.model === "string" ? config.model : undefined;
|
||||
const thinkingOptionValue =
|
||||
typeof config?.model_reasoning_effort === "string" ? config.model_reasoning_effort : null;
|
||||
configReadDefaults = {
|
||||
model: normalizeCodexModelId(modelValue),
|
||||
thinkingOptionId: normalizeCodexThinkingOptionId(thinkingOptionValue),
|
||||
};
|
||||
} catch (error) {
|
||||
logger.debug({ error }, "Failed to read Codex config defaults");
|
||||
}
|
||||
|
||||
if (configReadDefaults.model && configReadDefaults.thinkingOptionId) {
|
||||
return configReadDefaults;
|
||||
}
|
||||
|
||||
let savedConfigDefaults: CodexConfiguredDefaults = {};
|
||||
try {
|
||||
const response = toObjectRecord(await client.request("getUserSavedConfig", {}));
|
||||
@@ -3119,26 +3141,7 @@ async function readCodexConfiguredDefaults(
|
||||
logger.debug({ error }, "Failed to read Codex saved config defaults");
|
||||
}
|
||||
|
||||
if (savedConfigDefaults.model && savedConfigDefaults.thinkingOptionId) {
|
||||
return savedConfigDefaults;
|
||||
}
|
||||
|
||||
let configReadDefaults: CodexConfiguredDefaults = {};
|
||||
try {
|
||||
const response = toObjectRecord(await client.request("config/read", {}));
|
||||
const config = toObjectRecord(response?.config);
|
||||
const modelValue = typeof config?.model === "string" ? config.model : undefined;
|
||||
const thinkingOptionValue =
|
||||
typeof config?.model_reasoning_effort === "string" ? config.model_reasoning_effort : null;
|
||||
configReadDefaults = {
|
||||
model: normalizeCodexModelId(modelValue),
|
||||
thinkingOptionId: normalizeCodexThinkingOptionId(thinkingOptionValue),
|
||||
};
|
||||
} catch (error) {
|
||||
logger.debug({ error }, "Failed to read Codex config defaults");
|
||||
}
|
||||
|
||||
return mergeCodexConfiguredDefaults(savedConfigDefaults, configReadDefaults);
|
||||
return mergeCodexConfiguredDefaults(configReadDefaults, savedConfigDefaults);
|
||||
}
|
||||
|
||||
interface CodexSkillPromptBlock {
|
||||
@@ -5119,7 +5122,11 @@ export class CodexAppServerAgentSession implements AgentSession {
|
||||
let model = this.config.model;
|
||||
let thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
|
||||
if (!model || !thinkingOptionId) {
|
||||
configuredDefaults = await readCodexConfiguredDefaults(this.client, this.logger);
|
||||
configuredDefaults = await readCodexConfiguredDefaults(
|
||||
this.client,
|
||||
this.logger,
|
||||
this.config.cwd ?? null,
|
||||
);
|
||||
}
|
||||
if (!model) {
|
||||
model = configuredDefaults.model;
|
||||
@@ -7323,7 +7330,7 @@ export class CodexAppServerAgentClient implements AgentClient {
|
||||
const parsedResponse = CodexModelListResponseSchema.safeParse(rawResponse);
|
||||
const models = parsedResponse.success ? (parsedResponse.data.data ?? []) : [];
|
||||
const configuredDefaults = await runProviderRefreshActivity(context, "config/read", () =>
|
||||
readCodexConfiguredDefaults(client!, this.logger),
|
||||
readCodexConfiguredDefaults(client!, this.logger, null),
|
||||
);
|
||||
const configuredDefaultModelId = configuredDefaults.model;
|
||||
const configuredDefaultThinkingOptionId = configuredDefaults.thinkingOptionId;
|
||||
|
||||
Reference in New Issue
Block a user