@@ -341,4 +341,141 @@ describe("KiroAdapterLive integration", () => {
341341 yield * adapter . stopSession ( threadId ) ;
342342 } ) . pipe ( Effect . scoped , Effect . provide ( adapterLayer ) ) ,
343343 ) ;
344+
345+ it . effect (
346+ "switches model in-session without restarting the process" ,
347+ ( ) =>
348+ Effect . gen ( function * ( ) {
349+ const adapter = yield * KiroAdapter ;
350+ const threadId = ThreadId . make ( "kiro-int-model-switch-1" ) ;
351+
352+ yield * adapter . startSession ( {
353+ threadId,
354+ provider : "kiro" ,
355+ cwd : process . cwd ( ) ,
356+ runtimeMode : "full-access" ,
357+ modelSelection : { provider : "kiro" , model : "auto" } ,
358+ } ) ;
359+
360+ const spawnCountAfterStart = capturedArgs . length ;
361+
362+ // First turn with default model
363+ yield * adapter . sendTurn ( {
364+ threadId,
365+ input : "first turn" ,
366+ attachments : [ ] ,
367+ } ) ;
368+
369+ // Second turn with different model — should NOT respawn
370+ yield * adapter . sendTurn ( {
371+ threadId,
372+ input : "second turn after model switch" ,
373+ attachments : [ ] ,
374+ modelSelection : {
375+ provider : "kiro" ,
376+ model : "claude-sonnet-4-20250514" ,
377+ } ,
378+ } ) ;
379+
380+ // Session should NOT have been restarted — only one spawn
381+ expect ( capturedArgs . length ) . toBe ( spawnCountAfterStart ) ;
382+
383+ yield * adapter . stopSession ( threadId ) ;
384+ } ) . pipe ( Effect . scoped , Effect . provide ( adapterLayer ) ) ,
385+ ) ;
386+
387+ it . effect (
388+ "updates session.model immediately after in-session model switch" ,
389+ ( ) =>
390+ Effect . gen ( function * ( ) {
391+ const adapter = yield * KiroAdapter ;
392+ const threadId = ThreadId . make ( "kiro-int-model-state-1" ) ;
393+
394+ const session = yield * adapter . startSession ( {
395+ threadId,
396+ provider : "kiro" ,
397+ cwd : process . cwd ( ) ,
398+ runtimeMode : "full-access" ,
399+ modelSelection : { provider : "kiro" , model : "auto" } ,
400+ } ) ;
401+
402+ expect ( session . model ) . toBe ( "auto" ) ;
403+
404+ // Collect turn.started events to verify model is correct
405+ const eventsFiber = yield * adapter . streamEvents . pipe (
406+ Stream . filter (
407+ ( event ) => event . type === "turn.started" || event . type === "turn.completed" ,
408+ ) ,
409+ Stream . take ( 2 ) , // turn.started + turn.completed for the model-switch turn
410+ Stream . runCollect ,
411+ Effect . forkChild ,
412+ ) ;
413+
414+ // Send turn with new model
415+ const turn = yield * adapter . sendTurn ( {
416+ threadId,
417+ input : "switch model turn" ,
418+ attachments : [ ] ,
419+ modelSelection : {
420+ provider : "kiro" ,
421+ model : "claude-sonnet-4-20250514" ,
422+ } ,
423+ } ) ;
424+
425+ expect ( turn . threadId ) . toBe ( threadId ) ;
426+
427+ const events = yield * Fiber . join ( eventsFiber ) ;
428+ const turnStarted = events . find ( ( e ) => e . type === "turn.started" ) ;
429+ expect ( turnStarted ) . toBeDefined ( ) ;
430+ if ( turnStarted ?. type === "turn.started" ) {
431+ expect ( turnStarted . payload . model ) . toBe ( "claude-sonnet-4-20250514" ) ;
432+ }
433+
434+ // Verify session state reflects the new model
435+ const sessions = yield * adapter . listSessions ( ) ;
436+ const currentSession = sessions . find ( ( s ) => s . threadId === threadId ) ;
437+ expect ( currentSession ?. model ) . toBe ( "claude-sonnet-4-20250514" ) ;
438+
439+ yield * adapter . stopSession ( threadId ) ;
440+ } ) . pipe ( Effect . scoped , Effect . provide ( adapterLayer ) ) ,
441+ ) ;
442+
443+ it . effect (
444+ "does not call setModel when model is unchanged between turns" ,
445+ ( ) =>
446+ Effect . gen ( function * ( ) {
447+ const adapter = yield * KiroAdapter ;
448+ const threadId = ThreadId . make ( "kiro-int-model-unchanged-1" ) ;
449+
450+ yield * adapter . startSession ( {
451+ threadId,
452+ provider : "kiro" ,
453+ cwd : process . cwd ( ) ,
454+ runtimeMode : "full-access" ,
455+ modelSelection : { provider : "kiro" , model : "auto" } ,
456+ } ) ;
457+
458+ // Two turns with the same model — should not trigger setModel
459+ yield * adapter . sendTurn ( {
460+ threadId,
461+ input : "first turn" ,
462+ attachments : [ ] ,
463+ modelSelection : { provider : "kiro" , model : "auto" } ,
464+ } ) ;
465+
466+ yield * adapter . sendTurn ( {
467+ threadId,
468+ input : "second turn same model" ,
469+ attachments : [ ] ,
470+ modelSelection : { provider : "kiro" , model : "auto" } ,
471+ } ) ;
472+
473+ // Both turns should succeed without issues (no setModel called)
474+ const sessions = yield * adapter . listSessions ( ) ;
475+ const currentSession = sessions . find ( ( s ) => s . threadId === threadId ) ;
476+ expect ( currentSession ?. model ) . toBe ( "auto" ) ;
477+
478+ yield * adapter . stopSession ( threadId ) ;
479+ } ) . pipe ( Effect . scoped , Effect . provide ( adapterLayer ) ) ,
480+ ) ;
344481} ) ;
0 commit comments