@@ -6,6 +6,7 @@ const state = vi.hoisted(() => ({
66 requestEmbeddedRunModelSwitchMock : vi . fn ( ) ,
77 consumeEmbeddedRunModelSwitchMock : vi . fn ( ) ,
88 resolveDefaultModelForAgentMock : vi . fn ( ) ,
9+ resolvePersistedModelRefMock : vi . fn ( ) ,
910 loadSessionStoreMock : vi . fn ( ) ,
1011 resolveStorePathMock : vi . fn ( ) ,
1112} ) ) ;
@@ -24,6 +25,7 @@ vi.mock("./pi-embedded-runner/runs.js", () => ({
2425vi . mock ( "./model-selection.js" , ( ) => ( {
2526 resolveDefaultModelForAgent : ( ...args : unknown [ ] ) =>
2627 state . resolveDefaultModelForAgentMock ( ...args ) ,
28+ resolvePersistedModelRef : ( ...args : unknown [ ] ) => state . resolvePersistedModelRefMock ( ...args ) ,
2729} ) ) ;
2830
2931vi . mock ( "../config/sessions.js" , ( ) => ( {
@@ -46,6 +48,50 @@ describe("live model switch", () => {
4648 state . resolveDefaultModelForAgentMock
4749 . mockReset ( )
4850 . mockReturnValue ( { provider : "anthropic" , model : "claude-opus-4-6" } ) ;
51+ state . resolvePersistedModelRefMock
52+ . mockReset ( )
53+ . mockImplementation (
54+ ( params : {
55+ defaultProvider : string ;
56+ runtimeProvider ?: string ;
57+ runtimeModel ?: string ;
58+ overrideProvider ?: string ;
59+ overrideModel ?: string ;
60+ } ) => {
61+ const defaultProvider = params . defaultProvider . trim ( ) ;
62+ const runtimeProvider = params . runtimeProvider ?. trim ( ) ;
63+ const runtimeModel = params . runtimeModel ?. trim ( ) ;
64+ if ( runtimeModel ) {
65+ if ( runtimeProvider ) {
66+ return { provider : runtimeProvider , model : runtimeModel } ;
67+ }
68+ const slash = runtimeModel . indexOf ( "/" ) ;
69+ if ( slash <= 0 || slash === runtimeModel . length - 1 ) {
70+ return { provider : defaultProvider , model : runtimeModel } ;
71+ }
72+ return {
73+ provider : runtimeModel . slice ( 0 , slash ) ,
74+ model : runtimeModel . slice ( slash + 1 ) ,
75+ } ;
76+ }
77+ const overrideProvider = params . overrideProvider ?. trim ( ) ;
78+ const overrideModel = params . overrideModel ?. trim ( ) ;
79+ if ( ! overrideModel ) {
80+ return null ;
81+ }
82+ if ( overrideProvider ) {
83+ return { provider : overrideProvider , model : overrideModel } ;
84+ }
85+ const slash = overrideModel . indexOf ( "/" ) ;
86+ if ( slash <= 0 || slash === overrideModel . length - 1 ) {
87+ return { provider : defaultProvider , model : overrideModel } ;
88+ }
89+ return {
90+ provider : overrideModel . slice ( 0 , slash ) ,
91+ model : overrideModel . slice ( slash + 1 ) ,
92+ } ;
93+ } ,
94+ ) ;
4995 state . loadSessionStoreMock . mockReset ( ) . mockReturnValue ( { } ) ;
5096 state . resolveStorePathMock . mockReset ( ) . mockReturnValue ( "/tmp/session-store.json" ) ;
5197 } ) ;
@@ -112,6 +158,57 @@ describe("live model switch", () => {
112158 } ) ;
113159 } ) ;
114160
161+ it ( "splits legacy combined session overrides when providerOverride is missing" , async ( ) => {
162+ state . loadSessionStoreMock . mockReturnValue ( {
163+ main : {
164+ modelOverride : "ollama-beelink2/qwen2.5-coder:7b" ,
165+ } ,
166+ } ) ;
167+
168+ const { resolveLiveSessionModelSelection } = await loadModule ( ) ;
169+
170+ expect (
171+ resolveLiveSessionModelSelection ( {
172+ cfg : { session : { store : "/tmp/custom-store.json" } } ,
173+ sessionKey : "main" ,
174+ agentId : "reply" ,
175+ defaultProvider : "anthropic" ,
176+ defaultModel : "claude-opus-4-6" ,
177+ } ) ,
178+ ) . toEqual ( {
179+ provider : "ollama-beelink2" ,
180+ model : "qwen2.5-coder:7b" ,
181+ authProfileId : undefined ,
182+ authProfileIdSource : undefined ,
183+ } ) ;
184+ } ) ;
185+
186+ it ( "preserves provider when runtime model is a vendor-prefixed OpenRouter id" , async ( ) => {
187+ state . loadSessionStoreMock . mockReturnValue ( {
188+ main : {
189+ modelProvider : "openrouter" ,
190+ model : "anthropic/claude-haiku-4.5" ,
191+ } ,
192+ } ) ;
193+
194+ const { resolveLiveSessionModelSelection } = await loadModule ( ) ;
195+
196+ expect (
197+ resolveLiveSessionModelSelection ( {
198+ cfg : { session : { store : "/tmp/custom-store.json" } } ,
199+ sessionKey : "main" ,
200+ agentId : "reply" ,
201+ defaultProvider : "anthropic" ,
202+ defaultModel : "claude-opus-4-6" ,
203+ } ) ,
204+ ) . toEqual ( {
205+ provider : "openrouter" ,
206+ model : "anthropic/claude-haiku-4.5" ,
207+ authProfileId : undefined ,
208+ authProfileIdSource : undefined ,
209+ } ) ;
210+ } ) ;
211+
115212 it ( "queues a live switch only when an active run was aborted" , async ( ) => {
116213 state . abortEmbeddedPiRunMock . mockReturnValue ( true ) ;
117214
0 commit comments