Files
openclaw/src/sessions/model-overrides.ts
yuweuii 6c9b49a10b fix(sessions): clear stale contextTokens on model switch (#38044)
Merged via squash.

Prepared head SHA: bac2df4b7f
Co-authored-by: yuweuii <82372187+yuweuii@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-08 10:59:16 -07:00

113 lines
3.5 KiB
TypeScript

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 };
}