diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx index 09c2d64b00dc..1d453c6ee869 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx @@ -9,6 +9,21 @@ import { DialogVariant } from "./dialog-variant" import * as fuzzysort from "fuzzysort" import { useConnected } from "./use-connected" +type ModelSelection = { providerID: string; modelID: string } + +export function shouldOpenVariantDialog( + model: ModelSelection, + variant: { + list(model?: ModelSelection): string[] + selected(model?: ModelSelection): string | undefined + }, +) { + const list = variant.list(model) + const cur = variant.selected(model) + if (cur === "default" || (cur && list.includes(cur))) return false + return list.length > 0 +} + export function DialogModel(props: { providerID?: string }) { const local = useLocal() const sync = useSync() @@ -131,14 +146,9 @@ export function DialogModel(props: { providerID?: string }) { }) function onSelect(providerID: string, modelID: string) { - local.model.set({ providerID, modelID }, { recent: true }) - const list = local.model.variant.list() - const cur = local.model.variant.selected() - if (cur === "default" || (cur && list.includes(cur))) { - dialog.clear() - return - } - if (list.length > 0) { + const model = { providerID, modelID } + local.model.set(model, { recent: true }) + if (shouldOpenVariantDialog(model, local.model.variant)) { dialog.replace(() => ) return } diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index c4bab95842ec..86e6dbc590d8 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -23,6 +23,11 @@ export function parseModel(model: string) { } } +type SelectedModel = { + providerID: string + modelID: string +} + export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ name: "Local", init: () => { @@ -336,20 +341,20 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ }) }, variant: { - selected() { - const m = currentModel() + selected(model?: SelectedModel) { + const m = model ?? currentModel() if (!m) return undefined const key = `${m.providerID}/${m.modelID}` return modelStore.variant[key] }, - current() { - const v = this.selected() + current(model?: SelectedModel) { + const v = this.selected(model) if (!v) return undefined - if (!this.list().includes(v)) return undefined + if (!this.list(model).includes(v)) return undefined return v }, - list() { - const m = currentModel() + list(model?: SelectedModel) { + const m = model ?? currentModel() if (!m) return [] const provider = sync.data.provider.find((x) => x.id === m.providerID) const info = provider?.models[m.modelID] diff --git a/packages/opencode/test/cli/cmd/tui/dialog-model.test.ts b/packages/opencode/test/cli/cmd/tui/dialog-model.test.ts new file mode 100644 index 000000000000..deb4d12f15cc --- /dev/null +++ b/packages/opencode/test/cli/cmd/tui/dialog-model.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, test } from "bun:test" +import { shouldOpenVariantDialog } from "../../../../src/cli/cmd/tui/component/dialog-model" + +describe("DialogModel variant routing", () => { + test("checks variants for the selected model instead of the previous current model", () => { + const calls: Array<{ providerID: string; modelID: string } | undefined> = [] + const selected = { providerID: "openai", modelID: "gpt-5.4" } + + const shouldOpen = shouldOpenVariantDialog(selected, { + list(model) { + calls.push(model) + return model?.modelID === selected.modelID ? ["low", "medium", "high"] : [] + }, + selected(model) { + calls.push(model) + return model?.modelID === selected.modelID ? undefined : "default" + }, + }) + + expect(shouldOpen).toBe(true) + expect(calls).toEqual([selected, selected]) + }) + + test("keeps the model dialog closed when a variant choice is already saved", () => { + const selected = { providerID: "openai", modelID: "gpt-5.4" } + + expect( + shouldOpenVariantDialog(selected, { + list: () => ["low", "medium", "high"], + selected: () => "medium", + }), + ).toBe(false) + + expect( + shouldOpenVariantDialog(selected, { + list: () => ["low", "medium", "high"], + selected: () => "default", + }), + ).toBe(false) + }) +})