@@ -31,10 +31,10 @@ import {
3131} from "@agentclientprotocol/sdk"
3232import { InstallationVersion } from "@opencode-ai/core/installation/version"
3333import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
34- import type { AssistantMessage , Message , OpencodeClient , SessionMessageResponse } from "@opencode-ai/sdk/v2"
34+ import type { AssistantMessage , Message , OpencodeClient , Session , SessionMessageResponse } from "@opencode-ai/sdk/v2"
3535import { Context , Effect , Layer , ManagedRuntime } from "effect"
3636import * as ACPError from "./error"
37- import { buildConfigOptions , parseModelSelection } from "./config-option"
37+ import { buildConfigOptions , DEFAULT_VARIANT_VALUE , parseModelSelection } from "./config-option"
3838import { promptContentToParts } from "./content"
3939import { Directory } from "./directory"
4040import { ACPEvent } from "./event"
@@ -210,23 +210,26 @@ export function make(input: {
210210
211211 const loadSession = Effect . fn ( "ACP.loadSession" ) ( function * ( params : LoadSessionRequest ) {
212212 const snapshot = yield * directorySnapshot ( params . cwd )
213- yield * request (
213+ const backing = yield * request (
214214 ( ) => input . sdk . session . get ( { directory : params . cwd , sessionID : params . sessionId } , { throwOnError : true } ) ,
215215 "session" ,
216216 )
217217 const messages = yield * request (
218218 ( ) => input . sdk . session . messages ( { directory : params . cwd , sessionID : params . sessionId } , { throwOnError : true } ) ,
219219 "session" ,
220220 )
221- const restored = restoreFromMessages ( messages . map ( ( item ) => item . info ) )
222- const model = restored . model ?? selectDefaultModel ( snapshot )
221+ const restored = restoreSession (
222+ snapshot ,
223+ backing ,
224+ messages . map ( ( item ) => item . info ) ,
225+ )
223226 const state = yield * session . load ( {
224227 id : params . sessionId ,
225228 cwd : params . cwd ,
226229 mcpServers : params . mcpServers ,
227- model,
228- variant : restored . variant ?? selectVariant ( snapshot , model ) ,
229- modeId : restored . modeId ?? ( snapshot . availableModes . length > 0 ? snapshot . defaultModeID : undefined ) ,
230+ model : restored . model ,
231+ variant : restored . variant ,
232+ modeId : restored . modeId ,
230233 } )
231234 sessionSnapshots . set ( state . id , snapshot )
232235
@@ -236,7 +239,7 @@ export function make(input: {
236239
237240 return {
238241 configOptions : configOptions ( snapshot , {
239- model : state . model ?? model ,
242+ model : state . model ?? restored . model ,
240243 variant : state . variant ,
241244 modeId : state . modeId ,
242245 } ) ,
@@ -291,7 +294,7 @@ export function make(input: {
291294
292295 const resumeSession = Effect . fn ( "ACP.resumeSession" ) ( function * ( params : ResumeSessionRequest ) {
293296 const snapshot = yield * directorySnapshot ( params . cwd )
294- yield * request (
297+ const backing = yield * request (
295298 ( ) => input . sdk . session . get ( { directory : params . cwd , sessionID : params . sessionId } , { throwOnError : true } ) ,
296299 "session" ,
297300 )
@@ -303,15 +306,18 @@ export function make(input: {
303306 ) ,
304307 "session" ,
305308 )
306- const restored = restoreFromMessages ( messages . map ( ( item ) => item . info ) )
307- const model = restored . model ?? selectDefaultModel ( snapshot )
309+ const restored = restoreSession (
310+ snapshot ,
311+ backing ,
312+ messages . map ( ( item ) => item . info ) ,
313+ )
308314 const state = yield * session . load ( {
309315 id : params . sessionId ,
310316 cwd : params . cwd ,
311317 mcpServers : params . mcpServers ?? [ ] ,
312- model,
313- variant : restored . variant ?? selectVariant ( snapshot , model ) ,
314- modeId : restored . modeId ?? ( snapshot . availableModes . length > 0 ? snapshot . defaultModeID : undefined ) ,
318+ model : restored . model ,
319+ variant : restored . variant ,
320+ modeId : restored . modeId ,
315321 } )
316322 sessionSnapshots . set ( state . id , snapshot )
317323
@@ -320,7 +326,7 @@ export function make(input: {
320326
321327 return {
322328 configOptions : configOptions ( snapshot , {
323- model : state . model ?? model ,
329+ model : state . model ?? restored . model ,
324330 variant : state . variant ,
325331 modeId : state . modeId ,
326332 } ) ,
@@ -371,15 +377,18 @@ export function make(input: {
371377 input . sdk . session . messages ( { directory : params . cwd , sessionID : forked . id , limit : 20 } , { throwOnError : true } ) ,
372378 "session" ,
373379 )
374- const restored = restoreFromMessages ( messages . map ( ( item ) => item . info ) )
375- const model = restored . model ?? selectDefaultModel ( snapshot )
380+ const restored = restoreSession (
381+ snapshot ,
382+ forked ,
383+ messages . map ( ( item ) => item . info ) ,
384+ )
376385 const state = yield * session . load ( {
377386 id : forked . id ,
378387 cwd : params . cwd ,
379388 mcpServers : params . mcpServers ?? [ ] ,
380- model,
381- variant : restored . variant ?? selectVariant ( snapshot , model ) ,
382- modeId : restored . modeId ?? ( snapshot . availableModes . length > 0 ? snapshot . defaultModeID : undefined ) ,
389+ model : restored . model ,
390+ variant : restored . variant ,
391+ modeId : restored . modeId ,
383392 } )
384393 sessionSnapshots . set ( state . id , snapshot )
385394
@@ -390,7 +399,7 @@ export function make(input: {
390399 return {
391400 sessionId : state . id ,
392401 configOptions : configOptions ( snapshot , {
393- model : state . model ?? model ,
402+ model : state . model ?? restored . model ,
394403 variant : state . variant ,
395404 modeId : state . modeId ,
396405 } ) ,
@@ -408,23 +417,25 @@ export function make(input: {
408417
409418 if ( params . configId === "model" ) {
410419 const selected = yield * parseSelectedModel ( snapshot , params . value )
411- const variant = selected . variant ?? selectVariant ( snapshot , selected . model )
420+ const variant = selectModelVariant ( snapshot , current , selected )
412421 const state = yield * session
413422 . setVariant ( params . sessionId , Directory . variants ( snapshot , selected . model ) ? variant : undefined )
414423 . pipe ( Effect . andThen ( session . setModel ( params . sessionId , selected . model ) ) )
424+ const options = configOptions ( snapshot , {
425+ model : state . model ?? selected . model ,
426+ variant : state . variant ,
427+ modeId : state . modeId ,
428+ } )
429+ yield * sendConfigOptionUpdate ( input . connection , params . sessionId , options )
415430 return {
416- configOptions : configOptions ( snapshot , {
417- model : state . model ?? selected . model ,
418- variant : state . variant ,
419- modeId : state . modeId ,
420- } ) ,
431+ configOptions : options ,
421432 }
422433 }
423434
424435 if ( params . configId === "effort" ) {
425436 const model = current . model ?? selectDefaultModel ( snapshot )
426437 const variants = Directory . variants ( snapshot , model )
427- if ( ! variants || ! Object . keys ( variants ) . includes ( params . value ) ) {
438+ if ( ! variants || ! hasVariant ( variants , params . value ) ) {
428439 return yield * new ACPError . InvalidEffortError ( { effort : params . value } )
429440 }
430441 const state = yield * session . setVariant ( params . sessionId , params . value )
@@ -468,14 +479,18 @@ export function make(input: {
468479 const current = yield * session . get ( params . sessionId )
469480 const snapshot = yield * configSnapshot ( current )
470481 const selected = yield * parseSelectedModel ( snapshot , params . modelId )
471- yield * session
472- . setVariant (
473- params . sessionId ,
474- Directory . variants ( snapshot , selected . model )
475- ? ( selected . variant ?? selectVariant ( snapshot , selected . model ) )
476- : undefined ,
477- )
482+ const state = yield * session
483+ . setVariant ( params . sessionId , selectModelVariant ( snapshot , current , selected ) )
478484 . pipe ( Effect . andThen ( session . setModel ( params . sessionId , selected . model ) ) )
485+ yield * sendConfigOptionUpdate (
486+ input . connection ,
487+ params . sessionId ,
488+ configOptions ( snapshot , {
489+ model : state . model ?? selected . model ,
490+ variant : state . variant ,
491+ modeId : state . modeId ,
492+ } ) ,
493+ )
479494 return { }
480495 } )
481496
@@ -899,6 +914,24 @@ function selectVariant(snapshot: Directory.Snapshot, model: Directory.DefaultMod
899914 return Object . keys ( variants ) [ 0 ]
900915}
901916
917+ function selectModelVariant (
918+ snapshot : Directory . Snapshot ,
919+ current : ACPSession . Info ,
920+ selected : { model : Directory . DefaultModel ; variant ?: string } ,
921+ ) {
922+ const variants = Directory . variants ( snapshot , selected . model )
923+ if ( ! variants ) return
924+ if ( selected . variant ) return selected . variant
925+ if ( sameModel ( selected . model , current . model ) && current . variant && hasVariant ( variants , current . variant ) )
926+ return current . variant
927+ return selectVariant ( snapshot , selected . model )
928+ }
929+
930+ function hasVariant ( variants : Directory . ModelVariants , variant : string ) {
931+ // "default" is also the persisted sentinel for no explicit variant override.
932+ return variant === DEFAULT_VARIANT_VALUE || Object . hasOwn ( variants , variant )
933+ }
934+
902935function configOptions ( snapshot : Directory . Snapshot , session : ConfigState ) {
903936 return buildConfigOptions ( {
904937 providers : Object . values ( snapshot . providers ) ,
@@ -909,6 +942,25 @@ function configOptions(snapshot: Directory.Snapshot, session: ConfigState) {
909942 } )
910943}
911944
945+ function sendConfigOptionUpdate (
946+ connection : ServiceConnection | undefined ,
947+ sessionId : string ,
948+ options : ReturnType < typeof configOptions > ,
949+ ) {
950+ if ( ! connection ) return Effect . void
951+ return Effect . tryPromise ( {
952+ try : ( ) =>
953+ connection . sessionUpdate ( {
954+ sessionId,
955+ update : {
956+ sessionUpdate : "config_option_update" ,
957+ configOptions : options ,
958+ } ,
959+ } ) ,
960+ catch : ( ) => undefined ,
961+ } ) . pipe ( Effect . ignore )
962+ }
963+
912964function parseSelectedModel ( snapshot : Directory . Snapshot , modelId : string ) {
913965 const selected = parseModelSelection ( modelId , Object . values ( snapshot . providers ) )
914966 const provider = snapshot . providers [ ProviderV2 . ID . make ( selected . model . providerID ) ]
@@ -1034,6 +1086,75 @@ function stableStringify(value: unknown): string {
10341086 . join ( "," ) } }`
10351087}
10361088
1089+ function restoreSession (
1090+ snapshot : Directory . Snapshot ,
1091+ backing : Pick < Session , "agent" | "model" > ,
1092+ messages : MessageInfo [ ] ,
1093+ ) {
1094+ const history = restoreFromMessages ( messages )
1095+ const durable = restoreDurableModel ( backing . model )
1096+ const model = restoreModel ( snapshot , durable . model , history . model )
1097+ return {
1098+ model,
1099+ variant : restoreVariant ( snapshot , model , durable , history ) ,
1100+ modeId : restoreMode ( snapshot , backing . agent , history . modeId ) ,
1101+ }
1102+ }
1103+
1104+ function restoreDurableModel ( model : Session [ "model" ] | undefined ) {
1105+ if ( ! model ) return { }
1106+ return {
1107+ model : {
1108+ providerID : ProviderV2 . ID . make ( model . providerID ) ,
1109+ modelID : ModelV2 . ID . make ( model . id ) ,
1110+ } ,
1111+ variant : model . variant ,
1112+ }
1113+ }
1114+
1115+ function restoreModel (
1116+ snapshot : Directory . Snapshot ,
1117+ durable : Directory . DefaultModel | undefined ,
1118+ history : Directory . DefaultModel | undefined ,
1119+ ) {
1120+ if ( durable && hasModel ( snapshot , durable ) ) return durable
1121+ if ( history && hasModel ( snapshot , history ) ) return history
1122+ return selectDefaultModel ( snapshot )
1123+ }
1124+
1125+ function restoreVariant (
1126+ snapshot : Directory . Snapshot ,
1127+ model : Directory . DefaultModel ,
1128+ durable : { model ?: Directory . DefaultModel ; variant ?: string } ,
1129+ history : { model ?: Directory . DefaultModel ; variant ?: string } ,
1130+ ) {
1131+ const variants = Directory . variants ( snapshot , model )
1132+ if ( ! variants ) return
1133+ if ( sameModel ( model , durable . model ) && durable . variant && hasVariant ( variants , durable . variant ) )
1134+ return durable . variant
1135+ if ( sameModel ( model , history . model ) && history . variant && hasVariant ( variants , history . variant ) )
1136+ return history . variant
1137+ return selectVariant ( snapshot , model )
1138+ }
1139+
1140+ function restoreMode ( snapshot : Directory . Snapshot , durable : string | undefined , history : string | undefined ) {
1141+ if ( hasMode ( snapshot , durable ) ) return durable
1142+ if ( hasMode ( snapshot , history ) ) return history
1143+ if ( snapshot . availableModes . length > 0 ) return snapshot . defaultModeID
1144+ }
1145+
1146+ function hasModel ( snapshot : Directory . Snapshot , model : Directory . DefaultModel ) {
1147+ return Boolean ( snapshot . providers [ model . providerID ] ?. models [ model . modelID ] )
1148+ }
1149+
1150+ function hasMode ( snapshot : Directory . Snapshot , modeId : string | undefined ) {
1151+ return Boolean ( modeId && snapshot . availableModes . some ( ( mode ) => mode . id === modeId ) )
1152+ }
1153+
1154+ function sameModel ( left : Directory . DefaultModel , right : Directory . DefaultModel | undefined ) {
1155+ return left . providerID === right ?. providerID && left . modelID === right . modelID
1156+ }
1157+
10371158function restoreFromMessages ( messages : readonly MessageInfo [ ] ) {
10381159 const user = messages . findLast (
10391160 ( message ) => message . role === "user" && message . model ?. providerID && message . model . modelID ,
0 commit comments