From ca9b5739419a366bc4fc7420a39a57d513fa8a64 Mon Sep 17 00:00:00 2001 From: Zero Liu Date: Tue, 7 Jul 2026 15:46:00 -0700 Subject: [PATCH] test(agent-mode): cover effort landing on a cross-backend pick + guard-less backend Adds regression coverage for the audited transitions: a cross-backend Claude seed lands both the picked model and its effort, and a guard-less setModel backend (codex-style) still issues the switch after the seed-drop change. Co-Authored-By: Claude Opus 4.8 --- src/agentMode/session/AgentSession.test.ts | 81 ++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/agentMode/session/AgentSession.test.ts b/src/agentMode/session/AgentSession.test.ts index 7be314fd..747c7d23 100644 --- a/src/agentMode/session/AgentSession.test.ts +++ b/src/agentMode/session/AgentSession.test.ts @@ -1798,6 +1798,87 @@ describe("AgentSession.create (via start)", () => { expect(session.getState()?.model?.current.baseModelId).toBe("sonnet"); }); + it("cross-backend seed to Claude lands both the picked model and its effort", async () => { + const mock = makeMockBackend(); + const reported: BackendState = { + model: { + current: { baseModelId: "opus", effort: null }, + apply: { kind: "setModel" }, + availableModels: [ + { baseModelId: "opus", name: "Opus", provider: "anthropic", effortOptions: [] }, + { + baseModelId: "sonnet", + name: "Sonnet", + provider: "anthropic", + effortOptions: [{ value: "high", label: "High" }], + }, + ], + }, + mode: null, + }; + mock.newSession.mockResolvedValueOnce({ sessionId: "acp-1", state: reported }); + mock.setSessionModel.mockResolvedValueOnce({ + model: { ...reported.model!, current: { baseModelId: "sonnet", effort: null } }, + mode: null, + }); + mock.setSessionConfigOption.mockResolvedValueOnce({ + model: { ...reported.model!, current: { baseModelId: "sonnet", effort: "high" } }, + mode: null, + }); + const session = AgentSession.start({ + backend: mock.asBackend, + cwd: "/vault", + internalId: "internal-1", + backendId: "claude", + defaultModelSelection: { baseModelId: "sonnet", effort: "high" }, + getDescriptor: () => makeDescriptorWireWithoutEffort(), + }); + await session.ready; + expect(mock.setSessionModel).toHaveBeenCalledWith({ sessionId: "acp-1", modelId: "sonnet" }); + expect(mock.setSessionConfigOption).toHaveBeenCalledWith({ + sessionId: "acp-1", + configId: "effort", + value: "high", + }); + expect(session.getState()?.model?.current).toEqual({ baseModelId: "sonnet", effort: "high" }); + }); + + it("cross-backend seed to a guard-less setModel backend issues the switch", async () => { + const mock = makeMockBackend(); + const reported: BackendState = { + model: { + current: { baseModelId: "gpt-5-codex", effort: null }, + apply: { kind: "setModel" }, + availableModels: [ + { + baseModelId: "gpt-5-codex", + name: "GPT-5 Codex", + provider: "openai", + effortOptions: [], + }, + { baseModelId: "o3", name: "o3", provider: "openai", effortOptions: [] }, + ], + }, + mode: null, + }; + mock.newSession.mockResolvedValueOnce({ sessionId: "acp-1", state: reported }); + mock.setSessionModel.mockResolvedValueOnce({ + model: { ...reported.model!, current: { baseModelId: "o3", effort: null } }, + mode: null, + }); + const session = AgentSession.start({ + backend: mock.asBackend, + cwd: "/vault", + internalId: "internal-1", + backendId: "codex", + defaultModelSelection: { baseModelId: "o3", effort: null }, + getDescriptor: () => makeWireOnlyDescriptor(), + }); + await session.ready; + expect(mock.setSessionModel).toHaveBeenCalledWith({ sessionId: "acp-1", modelId: "o3" }); + expect(session.getState()?.model?.current.baseModelId).toBe("o3"); + }); + it("keeps the user-applied model when a late state_changed would revert it", async () => { // Regression: opencode's config_option_update broadcasts (synthesized into // state_changed) can race a switch and carry the pre-switch default; the old