mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-14 07:18:35 +00:00
TUI: sync /model status immediately
This commit is contained in:
90
src/sessions/model-overrides.test.ts
Normal file
90
src/sessions/model-overrides.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { SessionEntry } from "../config/sessions.js";
|
||||
import { applyModelOverrideToSessionEntry } from "./model-overrides.js";
|
||||
|
||||
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",
|
||||
fallbackNoticeSelectedModel: "anthropic/claude-sonnet-4-6",
|
||||
fallbackNoticeActiveModel: "anthropic/claude-sonnet-4-6",
|
||||
fallbackNoticeReason: "provider temporary failure",
|
||||
};
|
||||
|
||||
const result = applyModelOverrideToSessionEntry({
|
||||
entry,
|
||||
selection: {
|
||||
provider: "openai",
|
||||
model: "gpt-5.2",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.updated).toBe(true);
|
||||
expect(entry.providerOverride).toBe("openai");
|
||||
expect(entry.modelOverride).toBe("gpt-5.2");
|
||||
expect(entry.modelProvider).toBeUndefined();
|
||||
expect(entry.model).toBeUndefined();
|
||||
expect(entry.fallbackNoticeSelectedModel).toBeUndefined();
|
||||
expect(entry.fallbackNoticeActiveModel).toBeUndefined();
|
||||
expect(entry.fallbackNoticeReason).toBeUndefined();
|
||||
expect((entry.updatedAt ?? 0) > before).toBe(true);
|
||||
});
|
||||
|
||||
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",
|
||||
};
|
||||
|
||||
const result = applyModelOverrideToSessionEntry({
|
||||
entry,
|
||||
selection: {
|
||||
provider: "openai",
|
||||
model: "gpt-5.2",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.updated).toBe(true);
|
||||
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);
|
||||
});
|
||||
|
||||
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",
|
||||
};
|
||||
|
||||
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.updatedAt).toBe(before);
|
||||
});
|
||||
});
|
||||
@@ -15,24 +15,49 @@ export function applyModelOverrideToSessionEntry(params: {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user