import type { SessionEntry } from "../config/sessions.js"; export type ModelOverrideSelection = { provider: string; model: string; isDefault?: boolean; }; export function applyModelOverrideToSessionEntry(params: { entry: SessionEntry; selection: ModelOverrideSelection; profileOverride?: string; profileOverrideSource?: "auto" | "user"; }): { updated: boolean } { const { entry, selection, profileOverride } = params; const profileOverrideSource = params.profileOverrideSource ?? "user"; let updated = false; let selectionUpdated = false; if (selection.isDefault) { if (entry.providerOverride) { delete entry.providerOverride; updated = true; selectionUpdated = true; } if (entry.modelOverride) { delete entry.modelOverride; updated = true; selectionUpdated = true; } } else { if (entry.providerOverride !== selection.provider) { entry.providerOverride = selection.provider; updated = true; selectionUpdated = true; } if (entry.modelOverride !== selection.model) { entry.modelOverride = selection.model; updated = true; selectionUpdated = true; } } // Model overrides supersede previously recorded runtime model identity. // If runtime fields are stale (or the override changed), clear them so status // surfaces reflect the selected model immediately. const runtimeModel = typeof entry.model === "string" ? entry.model.trim() : ""; const runtimeProvider = typeof entry.modelProvider === "string" ? entry.modelProvider.trim() : ""; const runtimePresent = runtimeModel.length > 0 || runtimeProvider.length > 0; const runtimeAligned = runtimeModel === selection.model && (runtimeProvider.length === 0 || runtimeProvider === selection.provider); if (runtimePresent && (selectionUpdated || !runtimeAligned)) { if (entry.model !== undefined) { delete entry.model; updated = true; } if (entry.modelProvider !== undefined) { delete entry.modelProvider; updated = true; } } // contextTokens are derived from the active session model. When the selected // model changes (or runtime model is already stale), the cached window can // pin the session to an older/smaller limit until another run refreshes it. if ( entry.contextTokens !== undefined && (selectionUpdated || (runtimePresent && !runtimeAligned)) ) { delete entry.contextTokens; updated = true; } if (profileOverride) { if (entry.authProfileOverride !== profileOverride) { entry.authProfileOverride = profileOverride; updated = true; } if (entry.authProfileOverrideSource !== profileOverrideSource) { entry.authProfileOverrideSource = profileOverrideSource; updated = true; } if (entry.authProfileOverrideCompactionCount !== undefined) { delete entry.authProfileOverrideCompactionCount; updated = true; } } else { if (entry.authProfileOverride) { delete entry.authProfileOverride; updated = true; } if (entry.authProfileOverrideSource) { delete entry.authProfileOverrideSource; updated = true; } if (entry.authProfileOverrideCompactionCount !== undefined) { delete entry.authProfileOverrideCompactionCount; updated = true; } } // Clear stale fallback notice when the user explicitly switches models. if (updated) { delete entry.fallbackNoticeSelectedModel; delete entry.fallbackNoticeActiveModel; delete entry.fallbackNoticeReason; entry.updatedAt = Date.now(); } return { updated }; }