diff --git a/ui/src/ui/chat-model-ref.test.ts b/ui/src/ui/chat-model-ref.test.ts index 86b46f3fe7f..894ca276bbd 100644 --- a/ui/src/ui/chat-model-ref.test.ts +++ b/ui/src/ui/chat-model-ref.test.ts @@ -21,6 +21,19 @@ describe("chat-model-ref helpers", () => { }); }); + it("preserves nested OpenRouter model ids in option values", () => { + expect( + buildChatModelOption({ + id: "anthropic/claude-opus-4.6", + name: "Claude Opus 4.6", + provider: "openrouter", + }), + ).toEqual({ + value: "openrouter/anthropic/claude-opus-4.6", + label: "anthropic/claude-opus-4.6 ยท openrouter", + }); + }); + it("normalizes raw overrides when the catalog match is unique", () => { expect(normalizeChatModelOverrideValue(createChatModelOverride("gpt-5-mini"), catalog)).toBe( "openai/gpt-5-mini", @@ -45,6 +58,9 @@ describe("chat-model-ref helpers", () => { it("resolves server session data to qualified option values", () => { expect(resolveServerChatModelValue("gpt-5-mini", "openai")).toBe("openai/gpt-5-mini"); + expect(resolveServerChatModelValue("anthropic/claude-opus-4.6", "openrouter")).toBe( + "openrouter/anthropic/claude-opus-4.6", + ); expect(resolveServerChatModelValue("alias-only", null)).toBe("alias-only"); }); }); diff --git a/ui/src/ui/views/chat.test.ts b/ui/src/ui/views/chat.test.ts index 8e0e18dcba9..c01ced8dde5 100644 --- a/ui/src/ui/views/chat.test.ts +++ b/ui/src/ui/views/chat.test.ts @@ -706,6 +706,46 @@ describe("chat view", () => { vi.unstubAllGlobals(); }); + it("preserves OpenRouter-prefixed model refs from the chat header picker", async () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ + ok: false, + } satisfies Partial), + ); + const { state, request } = createChatHeaderState({ + models: [ + { + id: "anthropic/claude-opus-4.6", + name: "Claude Opus 4.6", + provider: "openrouter", + }, + ], + }); + const container = document.createElement("div"); + render(renderChatSessionSelect(state), container); + + const modelSelect = container.querySelector( + 'select[data-chat-model-select="true"]', + ); + expect(modelSelect).not.toBeNull(); + expect( + Array.from(modelSelect?.querySelectorAll("option") ?? []).map((option) => option.value), + ).toContain("openrouter/anthropic/claude-opus-4.6"); + + modelSelect!.value = "openrouter/anthropic/claude-opus-4.6"; + modelSelect!.dispatchEvent(new Event("change", { bubbles: true })); + await flushTasks(); + + expect(request).toHaveBeenCalledWith("sessions.patch", { + key: "main", + model: "openrouter/anthropic/claude-opus-4.6", + }); + expect(state.sessionsResult?.sessions[0]?.model).toBe("anthropic/claude-opus-4.6"); + expect(state.sessionsResult?.sessions[0]?.modelProvider).toBe("openrouter"); + vi.unstubAllGlobals(); + }); + it("clears the session model override back to the default model", async () => { vi.stubGlobal( "fetch",