From 4ad8116ce37a0e77e7f3c0e9e4e1002bba05b15e Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:05:08 -0500 Subject: [PATCH] fix(app): model selection persist by session (#17348) --- packages/app/e2e/fixtures.ts | 3 + packages/app/e2e/selectors.ts | 3 + .../session/session-model-persistence.spec.ts | 351 +++++++++++ .../components/dialog-select-model-unpaid.tsx | 12 +- .../src/components/dialog-select-model.tsx | 19 +- packages/app/src/components/prompt-input.tsx | 155 ++--- .../components/prompt-input/submit.test.ts | 12 + .../app/src/components/prompt-input/submit.ts | 3 +- packages/app/src/context/local.tsx | 575 +++++++++++------- .../app/src/context/model-variant.test.ts | 20 + packages/app/src/context/model-variant.ts | 4 +- packages/app/src/pages/directory-layout.tsx | 6 +- packages/app/src/pages/session.tsx | 4 +- .../session/session-model-helpers.test.ts | 133 +--- .../pages/session/session-model-helpers.ts | 42 +- .../pages/session/use-session-commands.tsx | 2 +- packages/app/src/testing/model-selection.ts | 80 +++ packages/app/src/testing/terminal.ts | 6 + packages/ui/src/components/select.tsx | 3 + 19 files changed, 981 insertions(+), 452 deletions(-) create mode 100644 packages/app/e2e/session/session-model-persistence.spec.ts create mode 100644 packages/app/src/testing/model-selection.ts diff --git a/packages/app/e2e/fixtures.ts b/packages/app/e2e/fixtures.ts index cf59eeb47..efefd479e 100644 --- a/packages/app/e2e/fixtures.ts +++ b/packages/app/e2e/fixtures.ts @@ -95,6 +95,9 @@ async function seedStorage(page: Page, input: { directory: string; extra?: strin const win = window as E2EWindow win.__opencode_e2e = { ...win.__opencode_e2e, + model: { + enabled: true, + }, terminal: { enabled: true, terminals: {}, diff --git a/packages/app/e2e/selectors.ts b/packages/app/e2e/selectors.ts index 64b7bfe54..80b6c473d 100644 --- a/packages/app/e2e/selectors.ts +++ b/packages/app/e2e/selectors.ts @@ -13,6 +13,9 @@ export const sessionTodoToggleButtonSelector = '[data-action="session-todo-toggl export const sessionTodoListSelector = '[data-slot="session-todo-list"]' export const modelVariantCycleSelector = '[data-action="model-variant-cycle"]' +export const promptAgentSelector = '[data-component="prompt-agent-control"]' +export const promptModelSelector = '[data-component="prompt-model-control"]' +export const promptVariantSelector = '[data-component="prompt-variant-control"]' export const settingsLanguageSelectSelector = '[data-action="settings-language"]' export const settingsColorSchemeSelector = '[data-action="settings-color-scheme"]' export const settingsThemeSelector = '[data-action="settings-theme"]' diff --git a/packages/app/e2e/session/session-model-persistence.spec.ts b/packages/app/e2e/session/session-model-persistence.spec.ts new file mode 100644 index 000000000..4b09a5287 --- /dev/null +++ b/packages/app/e2e/session/session-model-persistence.spec.ts @@ -0,0 +1,351 @@ +import { base64Decode } from "@opencode-ai/util/encode" +import type { Locator, Page } from "@playwright/test" +import { test, expect } from "../fixtures" +import { openSidebar, sessionIDFromUrl, setWorkspacesEnabled, waitSessionIdle, waitSlug } from "../actions" +import { + promptAgentSelector, + promptModelSelector, + promptSelector, + promptVariantSelector, + workspaceItemSelector, + workspaceNewSessionSelector, +} from "../selectors" +import { createSdk, sessionPath } from "../utils" + +type Footer = { + agent: string + model: string + variant: string +} + +type Probe = { + dir?: string + sessionID?: string + model?: { providerID: string; modelID: string } +} + +const escape = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + +const text = async (locator: Locator) => ((await locator.textContent()) ?? "").trim() + +const modelKey = (state: Probe | null) => (state?.model ? `${state.model.providerID}:${state.model.modelID}` : null) + +const dirKey = (state: Probe | null) => state?.dir ?? "" + +async function probe(page: Page): Promise { + return page.evaluate(() => { + const win = window as Window & { + __opencode_e2e?: { + model?: { + current?: Probe + } + } + } + return win.__opencode_e2e?.model?.current ?? null + }) +} + +async function currentDir(page: Page) { + let hit = "" + await expect + .poll( + async () => { + const next = dirKey(await probe(page)) + if (next) hit = next + return next + }, + { timeout: 30_000 }, + ) + .not.toBe("") + return hit +} + +async function read(page: Page): Promise