SetSessionAgentModel applies modelRef as the model override for the current agent of the session and persists it. Pass an empty modelRef to clear the override and revert to the agent's default model. On store-write failure the in-memory session state and the runtime override are rolled back so the
(ctx context.Context, sessionID, modelRef string)
| 1290 | // callers (notably the TUI's App) can switch models without going through |
| 1291 | // HTTP. |
| 1292 | func (sm *SessionManager) SetSessionAgentModel(ctx context.Context, sessionID, modelRef string) (string, string, error) { |
| 1293 | rs, ok := sm.runtimeSessions.Load(sessionID) |
| 1294 | if !ok { |
| 1295 | return "", "", ErrSessionNotRunning |
| 1296 | } |
| 1297 | |
| 1298 | if !rs.runtime.SupportsModelSwitching() { |
| 1299 | return "", "", ErrModelSwitchingNotSupported |
| 1300 | } |
| 1301 | |
| 1302 | agentName := rs.runtime.CurrentAgentName(ctx) |
| 1303 | sess := rs.session |
| 1304 | |
| 1305 | // Snapshot current state so we can roll back if persistence fails |
| 1306 | // after we've already mutated the runtime. |
| 1307 | var ( |
| 1308 | hadOverride bool |
| 1309 | prevOverride string |
| 1310 | hadOverridesMap bool |
| 1311 | ) |
| 1312 | if sess != nil { |
| 1313 | sm.mux.Lock() |
| 1314 | hadOverridesMap = sess.AgentModelOverrides != nil |
| 1315 | if hadOverridesMap { |
| 1316 | prevOverride, hadOverride = sess.AgentModelOverrides[agentName] |
| 1317 | } |
| 1318 | sm.mux.Unlock() |
| 1319 | } |
| 1320 | |
| 1321 | // Runtime mutation runs without sm.mux so it doesn't block other |
| 1322 | // session operations during slow provider creation. The per-session |
| 1323 | // modelSwitch lock above keeps SetAgentModel + UpdateSession + any |
| 1324 | // rollback atomic with respect to other model-switch calls on this |
| 1325 | // session. |
| 1326 | if err := rs.runtime.SetAgentModel(ctx, agentName, modelRef); err != nil { |
| 1327 | return "", "", err |
| 1328 | } |
| 1329 | |
| 1330 | if sess == nil { |
| 1331 | return agentName, modelRef, nil |
| 1332 | } |
| 1333 | |
| 1334 | // Clone the session for the store write. We'll apply mutations to the |
| 1335 | // clone, persist it, and only then update the live session. This ensures |
| 1336 | // concurrent readers never observe a not-yet-persisted state. |
| 1337 | updatedSess := &session.Session{ |
| 1338 | ID: sess.ID, |
| 1339 | Title: sess.Title, |
| 1340 | CreatedAt: sess.CreatedAt, |
| 1341 | WorkingDir: sess.WorkingDir, |
| 1342 | ToolsApproved: sess.ToolsApproved, |
| 1343 | Permissions: sess.Permissions, |
| 1344 | MaxIterations: sess.MaxIterations, |
| 1345 | MaxConsecutiveToolCalls: sess.MaxConsecutiveToolCalls, |
| 1346 | MaxOldToolCallTokens: sess.MaxOldToolCallTokens, |
| 1347 | InputTokens: sess.InputTokens, |
| 1348 | OutputTokens: sess.OutputTokens, |
| 1349 | Cost: sess.Cost, |