TUI: sync /model status immediately

This commit is contained in:
Vignesh Natarajan
2026-02-28 14:02:56 -08:00
parent a623c9c8d2
commit 0929c233d8
5 changed files with 144 additions and 5 deletions

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

View File

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