Skip to content

Commit ba1edea

Browse files
committed
fix(app): model sticks to session
1 parent 73c9b68 commit ba1edea

6 files changed

Lines changed: 176 additions & 8 deletions

File tree

packages/app/src/components/prompt-input/submit.test.ts

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,19 @@ let createPromptSubmit: typeof import("./submit").createPromptSubmit
66
const createdClients: string[] = []
77
const createdSessions: string[] = []
88
const enabledAutoAccept: Array<{ sessionID: string; directory: string }> = []
9+
const optimistic: Array<{
10+
message: {
11+
agent: string
12+
model: { providerID: string; modelID: string }
13+
variant?: string
14+
}
15+
}> = []
916
const sentShell: string[] = []
1017
const syncedDirectories: string[] = []
1118

19+
let params: { id?: string } = {}
1220
let selected = "/repo/worktree-a"
21+
let variant: string | undefined
1322

1423
const promptValue: Prompt = [{ type: "text", content: "ls", start: 0, end: 2 }]
1524

@@ -26,6 +35,7 @@ const clientFor = (directory: string) => {
2635
return { data: undefined }
2736
},
2837
prompt: async () => ({ data: undefined }),
38+
promptAsync: async () => ({ data: undefined }),
2939
command: async () => ({ data: undefined }),
3040
abort: async () => ({ data: undefined }),
3141
},
@@ -40,7 +50,7 @@ beforeAll(async () => {
4050

4151
mock.module("@solidjs/router", () => ({
4252
useNavigate: () => () => undefined,
43-
useParams: () => ({}),
53+
useParams: () => params,
4454
}))
4555

4656
mock.module("@opencode-ai/sdk/v2/client", () => ({
@@ -62,7 +72,7 @@ beforeAll(async () => {
6272
useLocal: () => ({
6373
model: {
6474
current: () => ({ id: "model", provider: { id: "provider" } }),
65-
variant: { current: () => undefined },
75+
variant: { current: () => variant },
6676
},
6777
agent: {
6878
current: () => ({ name: "agent" }),
@@ -118,7 +128,11 @@ beforeAll(async () => {
118128
data: { command: [] },
119129
session: {
120130
optimistic: {
121-
add: () => undefined,
131+
add: (value: {
132+
message: { agent: string; model: { providerID: string; modelID: string }; variant?: string }
133+
}) => {
134+
optimistic.push(value)
135+
},
122136
remove: () => undefined,
123137
},
124138
},
@@ -155,9 +169,12 @@ beforeEach(() => {
155169
createdClients.length = 0
156170
createdSessions.length = 0
157171
enabledAutoAccept.length = 0
172+
optimistic.length = 0
173+
params = {}
158174
sentShell.length = 0
159175
syncedDirectories.length = 0
160176
selected = "/repo/worktree-a"
177+
variant = undefined
161178
})
162179

163180
describe("prompt submit worktree selection", () => {
@@ -219,4 +236,39 @@ describe("prompt submit worktree selection", () => {
219236

220237
expect(enabledAutoAccept).toEqual([{ sessionID: "session-1", directory: "/repo/worktree-a" }])
221238
})
239+
240+
test("includes the selected variant on optimistic prompts", async () => {
241+
params = { id: "session-1" }
242+
variant = "high"
243+
244+
const submit = createPromptSubmit({
245+
info: () => ({ id: "session-1" }),
246+
imageAttachments: () => [],
247+
commentCount: () => 0,
248+
autoAccept: () => false,
249+
mode: () => "normal",
250+
working: () => false,
251+
editor: () => undefined,
252+
queueScroll: () => undefined,
253+
promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0),
254+
addToHistory: () => undefined,
255+
resetHistoryNavigation: () => undefined,
256+
setMode: () => undefined,
257+
setPopover: () => undefined,
258+
onSubmit: () => undefined,
259+
})
260+
261+
const event = { preventDefault: () => undefined } as unknown as Event
262+
263+
await submit.handleSubmit(event)
264+
265+
expect(optimistic).toHaveLength(1)
266+
expect(optimistic[0]).toMatchObject({
267+
message: {
268+
agent: "agent",
269+
model: { providerID: "provider", modelID: "model" },
270+
variant: "high",
271+
},
272+
})
273+
})
222274
})

packages/app/src/components/prompt-input/submit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
316316
time: { created: Date.now() },
317317
agent,
318318
model,
319+
variant,
319320
}
320321

321322
const addOptimisticMessage = () =>

packages/app/src/context/sync.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
199199
parts: Part[]
200200
agent: string
201201
model: { providerID: string; modelID: string }
202+
variant?: string
202203
}) {
203204
const message: Message = {
204205
id: input.messageID,
@@ -207,6 +208,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
207208
time: { created: Date.now() },
208209
agent: input.agent,
209210
model: input.model,
211+
variant: input.variant,
210212
}
211213
const [, setStore] = target()
212214
setOptimisticAdd(setStore as (...args: unknown[]) => void, {

packages/app/src/pages/session.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { createSessionComposerState, SessionComposerRegion } from "@/pages/sessi
3636
import { createOpenReviewFile, createSizing } from "@/pages/session/helpers"
3737
import { MessageTimeline } from "@/pages/session/message-timeline"
3838
import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab"
39+
import { syncSessionModel } from "@/pages/session/session-model-helpers"
3940
import { createScrollSpy } from "@/pages/session/scroll-spy"
4041
import { SessionMobileTabs } from "@/pages/session/session-mobile-tabs"
4142
import { SessionSidePanel } from "@/pages/session/session-side-panel"
@@ -418,11 +419,7 @@ export default function Page() {
418419
() => {
419420
const msg = lastUserMessage()
420421
if (!msg) return
421-
if (msg.agent) {
422-
local.agent.set(msg.agent)
423-
if (local.agent.current()?.model) return
424-
}
425-
if (msg.model) local.model.set(msg.model)
422+
syncSessionModel(local, msg)
426423
},
427424
),
428425
)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { describe, expect, test } from "bun:test"
2+
import type { UserMessage } from "@opencode-ai/sdk/v2"
3+
import { syncSessionModel } from "./session-model-helpers"
4+
5+
const message = (input?: Partial<Pick<UserMessage, "agent" | "model" | "variant">>) =>
6+
({
7+
id: "msg",
8+
sessionID: "session",
9+
role: "user",
10+
time: { created: 1 },
11+
agent: input?.agent ?? "build",
12+
model: input?.model ?? { providerID: "anthropic", modelID: "claude-sonnet-4" },
13+
variant: input?.variant,
14+
}) as UserMessage
15+
16+
describe("syncSessionModel", () => {
17+
test("restores the last message model and variant", () => {
18+
const calls: unknown[] = []
19+
20+
syncSessionModel(
21+
{
22+
agent: {
23+
set(value) {
24+
calls.push(["agent", value])
25+
},
26+
},
27+
model: {
28+
set(value) {
29+
calls.push(["model", value])
30+
},
31+
current() {
32+
return { id: "claude-sonnet-4", provider: { id: "anthropic" } }
33+
},
34+
variant: {
35+
set(value) {
36+
calls.push(["variant", value])
37+
},
38+
},
39+
},
40+
},
41+
message({ variant: "high" }),
42+
)
43+
44+
expect(calls).toEqual([
45+
["agent", "build"],
46+
["model", { providerID: "anthropic", modelID: "claude-sonnet-4" }],
47+
["variant", "high"],
48+
])
49+
})
50+
51+
test("skips variant when the model falls back", () => {
52+
const calls: unknown[] = []
53+
54+
syncSessionModel(
55+
{
56+
agent: {
57+
set(value) {
58+
calls.push(["agent", value])
59+
},
60+
},
61+
model: {
62+
set(value) {
63+
calls.push(["model", value])
64+
},
65+
current() {
66+
return { id: "gpt-5", provider: { id: "openai" } }
67+
},
68+
variant: {
69+
set(value) {
70+
calls.push(["variant", value])
71+
},
72+
},
73+
},
74+
},
75+
message({ variant: "high" }),
76+
)
77+
78+
expect(calls).toEqual([
79+
["agent", "build"],
80+
["model", { providerID: "anthropic", modelID: "claude-sonnet-4" }],
81+
])
82+
})
83+
})
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { UserMessage } from "@opencode-ai/sdk/v2"
2+
import { batch } from "solid-js"
3+
4+
type Local = {
5+
agent: {
6+
set(name: string | undefined): void
7+
}
8+
model: {
9+
set(model: UserMessage["model"] | undefined): void
10+
current():
11+
| {
12+
id: string
13+
provider: { id: string }
14+
}
15+
| undefined
16+
variant: {
17+
set(value: string | undefined): void
18+
}
19+
}
20+
}
21+
22+
export const syncSessionModel = (local: Local, msg: UserMessage) => {
23+
batch(() => {
24+
local.agent.set(msg.agent)
25+
local.model.set(msg.model)
26+
})
27+
28+
const model = local.model.current()
29+
if (!model) return
30+
if (model.provider.id !== msg.model.providerID) return
31+
if (model.id !== msg.model.modelID) return
32+
local.model.variant.set(msg.variant)
33+
}

0 commit comments

Comments
 (0)