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

Prepared head SHA: bac2df4b7f920ce271f0a15f1db9ed99b35300f3
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

121 lines
3.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { SessionEntry } from "../config/sessions.js";
import { applyModelOverrideToSessionEntry } from "./model-overrides.js";
function applyOpenAiSelection(entry: SessionEntry) {
return applyModelOverrideToSessionEntry({
entry,
selection: {
provider: "openai",
model: "gpt-5.2",
},
});
}
function expectRuntimeModelFieldsCleared(entry: SessionEntry, before: number) {
expect(entry.providerOverride).toBe("openai");
expect(entry.modelOverride).toBe("gpt-5.2");
expect(entry.modelProvider).toBeUndefined();
expect(entry.model).toBeUndefined();
expect((entry.updatedAt ?? 0) > before).toBe(true);
}
describe("applyModelOverrideToSessionEntry", () => {
it("clears stale runtime model fields when switching overrides", () => {
const before = Date.now() - 5_000;
const entry: SessionEntry = {
sessionId: "sess-1",
updatedAt: before,
modelProvider: "anthropic",
model: "claude-sonnet-4-6",
providerOverride: "anthropic",
modelOverride: "claude-sonnet-4-6",
contextTokens: 160_000,
fallbackNoticeSelectedModel: "anthropic/claude-sonnet-4-6",
fallbackNoticeActiveModel: "anthropic/claude-sonnet-4-6",
fallbackNoticeReason: "provider temporary failure",
};
const result = applyOpenAiSelection(entry);
expect(result.updated).toBe(true);
expectRuntimeModelFieldsCleared(entry, before);
expect(entry.contextTokens).toBeUndefined();
expect(entry.fallbackNoticeSelectedModel).toBeUndefined();
expect(entry.fallbackNoticeActiveModel).toBeUndefined();
expect(entry.fallbackNoticeReason).toBeUndefined();
});
it("clears stale runtime model fields even when override selection is unchanged", () => {
const before = Date.now() - 5_000;
const entry: SessionEntry = {
sessionId: "sess-2",
updatedAt: before,
modelProvider: "anthropic",
model: "claude-sonnet-4-6",
providerOverride: "openai",
modelOverride: "gpt-5.2",
contextTokens: 160_000,
};
const result = applyOpenAiSelection(entry);
expect(result.updated).toBe(true);
expectRuntimeModelFieldsCleared(entry, before);
expect(entry.contextTokens).toBeUndefined();
});
it("retains aligned runtime model fields when selection and runtime already match", () => {
const before = Date.now() - 5_000;
const entry: SessionEntry = {
sessionId: "sess-3",
updatedAt: before,
modelProvider: "openai",
model: "gpt-5.2",
providerOverride: "openai",
modelOverride: "gpt-5.2",
contextTokens: 200_000,
};
const result = applyModelOverrideToSessionEntry({
entry,
selection: {
provider: "openai",
model: "gpt-5.2",
},
});
expect(result.updated).toBe(false);
expect(entry.modelProvider).toBe("openai");
expect(entry.model).toBe("gpt-5.2");
expect(entry.contextTokens).toBe(200_000);
expect(entry.updatedAt).toBe(before);
});
it("clears stale contextTokens when switching back to the default model", () => {
const before = Date.now() - 5_000;
const entry: SessionEntry = {
sessionId: "sess-4",
updatedAt: before,
providerOverride: "local",
modelOverride: "sunapi386/llama-3-lexi-uncensored:8b",
contextTokens: 4_096,
};
const result = applyModelOverrideToSessionEntry({
entry,
selection: {
provider: "local",
model: "llama3.1:8b",
isDefault: true,
},
});
expect(result.updated).toBe(true);
expect(entry.providerOverride).toBeUndefined();
expect(entry.modelOverride).toBeUndefined();
expect(entry.contextTokens).toBeUndefined();
expect((entry.updatedAt ?? 0) > before).toBe(true);
});
});