mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 13:17:39 +00:00
fix(tui): resolve wrong provider prefix when session has model without modelProvider (#25874)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: f0953a7284
Co-authored-by: lbo728 <72309817+lbo728@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
@@ -6,6 +6,7 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from
|
||||
import {
|
||||
clearSessionStoreCacheForTest,
|
||||
loadSessionStore,
|
||||
mergeSessionEntry,
|
||||
resolveAndPersistSessionFile,
|
||||
updateSessionStore,
|
||||
} from "../sessions.js";
|
||||
@@ -215,6 +216,42 @@ describe("session store lock (Promise chain mutex)", () => {
|
||||
const store = loadSessionStore(storePath);
|
||||
expect(store[key]?.modelOverride).toBe("recovered");
|
||||
});
|
||||
|
||||
it("clears stale runtime provider when model is patched without provider", () => {
|
||||
const merged = mergeSessionEntry(
|
||||
{
|
||||
sessionId: "sess-runtime",
|
||||
updatedAt: 100,
|
||||
modelProvider: "anthropic",
|
||||
model: "claude-opus-4-6",
|
||||
},
|
||||
{
|
||||
model: "gpt-5.2",
|
||||
},
|
||||
);
|
||||
expect(merged.model).toBe("gpt-5.2");
|
||||
expect(merged.modelProvider).toBeUndefined();
|
||||
});
|
||||
|
||||
it("normalizes orphan modelProvider fields at store write boundary", async () => {
|
||||
const key = "agent:main:orphan-provider";
|
||||
const { storePath } = await makeTmpStore({
|
||||
[key]: {
|
||||
sessionId: "sess-orphan",
|
||||
updatedAt: 100,
|
||||
modelProvider: "anthropic",
|
||||
},
|
||||
});
|
||||
|
||||
await updateSessionStore(storePath, async (store) => {
|
||||
const entry = store[key];
|
||||
entry.updatedAt = Date.now();
|
||||
});
|
||||
|
||||
const store = loadSessionStore(storePath);
|
||||
expect(store[key]?.modelProvider).toBeUndefined();
|
||||
expect(store[key]?.model).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("appendAssistantMessageToSessionTranscript", () => {
|
||||
|
||||
@@ -22,7 +22,11 @@ import { loadConfig } from "../config.js";
|
||||
import type { SessionMaintenanceConfig, SessionMaintenanceMode } from "../types.base.js";
|
||||
import { enforceSessionDiskBudget, type SessionDiskBudgetSweepResult } from "./disk-budget.js";
|
||||
import { deriveSessionMetaPatch } from "./metadata.js";
|
||||
import { mergeSessionEntry, type SessionEntry } from "./types.js";
|
||||
import {
|
||||
mergeSessionEntry,
|
||||
normalizeSessionRuntimeModelFields,
|
||||
type SessionEntry,
|
||||
} from "./types.js";
|
||||
|
||||
const log = createSubsystemLogger("sessions/store");
|
||||
|
||||
@@ -157,7 +161,7 @@ function normalizeSessionStore(store: Record<string, SessionEntry>): void {
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
const normalized = normalizeSessionEntryDelivery(entry);
|
||||
const normalized = normalizeSessionEntryDelivery(normalizeSessionRuntimeModelFields(entry));
|
||||
if (normalized !== entry) {
|
||||
store[key] = normalized;
|
||||
}
|
||||
|
||||
@@ -114,6 +114,65 @@ export type SessionEntry = {
|
||||
systemPromptReport?: SessionSystemPromptReport;
|
||||
};
|
||||
|
||||
function normalizeRuntimeField(value: string | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
export function normalizeSessionRuntimeModelFields(entry: SessionEntry): SessionEntry {
|
||||
const normalizedModel = normalizeRuntimeField(entry.model);
|
||||
const normalizedProvider = normalizeRuntimeField(entry.modelProvider);
|
||||
let next = entry;
|
||||
|
||||
if (!normalizedModel) {
|
||||
if (entry.model !== undefined || entry.modelProvider !== undefined) {
|
||||
next = { ...next };
|
||||
delete next.model;
|
||||
delete next.modelProvider;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
if (entry.model !== normalizedModel) {
|
||||
if (next === entry) {
|
||||
next = { ...next };
|
||||
}
|
||||
next.model = normalizedModel;
|
||||
}
|
||||
|
||||
if (!normalizedProvider) {
|
||||
if (entry.modelProvider !== undefined) {
|
||||
if (next === entry) {
|
||||
next = { ...next };
|
||||
}
|
||||
delete next.modelProvider;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
if (entry.modelProvider !== normalizedProvider) {
|
||||
if (next === entry) {
|
||||
next = { ...next };
|
||||
}
|
||||
next.modelProvider = normalizedProvider;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
export function setSessionRuntimeModel(
|
||||
entry: SessionEntry,
|
||||
runtime: { provider: string; model: string },
|
||||
): boolean {
|
||||
const provider = runtime.provider.trim();
|
||||
const model = runtime.model.trim();
|
||||
if (!provider || !model) {
|
||||
return false;
|
||||
}
|
||||
entry.modelProvider = provider;
|
||||
entry.model = model;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function mergeSessionEntry(
|
||||
existing: SessionEntry | undefined,
|
||||
patch: Partial<SessionEntry>,
|
||||
@@ -121,9 +180,20 @@ export function mergeSessionEntry(
|
||||
const sessionId = patch.sessionId ?? existing?.sessionId ?? crypto.randomUUID();
|
||||
const updatedAt = Math.max(existing?.updatedAt ?? 0, patch.updatedAt ?? 0, Date.now());
|
||||
if (!existing) {
|
||||
return { ...patch, sessionId, updatedAt };
|
||||
return normalizeSessionRuntimeModelFields({ ...patch, sessionId, updatedAt });
|
||||
}
|
||||
return { ...existing, ...patch, sessionId, updatedAt };
|
||||
const next = { ...existing, ...patch, sessionId, updatedAt };
|
||||
|
||||
// Guard against stale provider carry-over when callers patch runtime model
|
||||
// without also patching runtime provider.
|
||||
if (Object.hasOwn(patch, "model") && !Object.hasOwn(patch, "modelProvider")) {
|
||||
const patchedModel = normalizeRuntimeField(patch.model);
|
||||
const existingModel = normalizeRuntimeField(existing.model);
|
||||
if (patchedModel && patchedModel !== existingModel) {
|
||||
delete next.modelProvider;
|
||||
}
|
||||
}
|
||||
return normalizeSessionRuntimeModelFields(next);
|
||||
}
|
||||
|
||||
export function resolveFreshSessionTotalTokens(
|
||||
|
||||
Reference in New Issue
Block a user