diff --git a/packages/server/src/server/agent/agent-manager.test.ts b/packages/server/src/server/agent/agent-manager.test.ts index 02e1798789..2fb6008916 100644 --- a/packages/server/src/server/agent/agent-manager.test.ts +++ b/packages/server/src/server/agent/agent-manager.test.ts @@ -5370,6 +5370,9 @@ test("model changes persist the resolved thinking and fresh provider handle befo thinkingOptionId: null, }); } + override async getRuntimeInfo() { + return { ...(await super.getRuntimeInfo()), ...this.selection }; + } describePersistence() { return { provider: "codex" as const, sessionId: this.id, metadata: { ...this.selection } }; } @@ -5452,6 +5455,62 @@ test("later explicit config mutations win over events emitted by earlier mutatio expect(manager.getAgent(snapshot.id)?.config.thinkingOptionId).toBe("high"); }); +test.each([ + { requested: "unavailable", effective: "source" }, + { requested: "alias", effective: "canonical" }, + { requested: null, effective: null }, +])( + "setAgentModel stores effective selections and preserves resets for $requested", + async ({ requested, effective }) => { + const workdir = mkdtempSync(join(tmpdir(), "agent-manager-effective-model-")); + class SelectingSession extends TestAgentSession { + private model: string | null = "source"; + + async setModel(model: string | null): Promise { + if (model && model !== "unavailable") this.model = model === "alias" ? "canonical" : model; + } + + override async getRuntimeInfo() { + return { ...(await super.getRuntimeInfo()), model: this.model }; + } + } + class SelectingClient extends TestAgentClient { + override async createSession(config: AgentSessionConfig): Promise { + return new SelectingSession(config); + } + } + const storage = new AgentStorage(join(workdir, "agents"), logger); + const manager = new AgentManager({ + clients: { codex: new SelectingClient() }, + registry: storage, + logger, + }); + try { + const snapshot = await manager.createAgent( + { provider: "codex", cwd: workdir, model: "source" }, + undefined, + { workspaceId: undefined }, + ); + await manager.setAgentModel(snapshot.id, requested); + await manager.flush(); + + expect(manager.getAgent(snapshot.id)?.config.model).toBe(effective ?? undefined); + expect(manager.getAgent(snapshot.id)?.runtimeInfo?.model).toBe(effective); + const persisted = await storage.get(snapshot.id); + expect(persisted?.config?.model).toBe(effective ?? undefined); + expect(persisted?.runtimeInfo?.model).toBe(effective); + + await manager.setAgentModel(snapshot.id, "target"); + expect(manager.getAgent(snapshot.id)?.config.model).toBe("target"); + expect(manager.getAgent(snapshot.id)?.runtimeInfo?.model).toBe("target"); + } finally { + for (const agent of manager.listAgents()) await manager.closeAgent(agent.id); + await manager.flush(); + rmSync(workdir, { recursive: true, force: true }); + } + }, +); + test("setAgentThinkingOption adopts the session's effective clamped level", async () => { const workdir = mkdtempSync(join(tmpdir(), "agent-manager-thinking-clamp-")); class ClampingSession extends TestAgentSession { diff --git a/packages/server/src/server/agent/agent-manager.ts b/packages/server/src/server/agent/agent-manager.ts index b47e12e558..1d9d19610d 100644 --- a/packages/server/src/server/agent/agent-manager.ts +++ b/packages/server/src/server/agent/agent-manager.ts @@ -2333,9 +2333,18 @@ export class AgentManager { } await this.drainSessionEvents(agentId); - agent.config.model = normalizedModelId ?? undefined; + let effectiveModelId = normalizedModelId; + // Clearing the preference still applies when the provider keeps its active model. + if (normalizedModelId !== null && agent.session.setModel) { + const runtimeInfo = await agent.session.getRuntimeInfo(); + if (runtimeInfo.model !== undefined) { + effectiveModelId = runtimeInfo.model; + } + } + + agent.config.model = effectiveModelId ?? undefined; if (agent.runtimeInfo) { - agent.runtimeInfo = { ...agent.runtimeInfo, model: normalizedModelId }; + agent.runtimeInfo = { ...agent.runtimeInfo, model: effectiveModelId }; } this.refreshSessionPersistence(agent); this.touchUpdatedAt(agent);