openclaw/src/sessions/model-overrides.ts

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