diff --git a/src/gateway/session-utils.test.ts b/src/gateway/session-utils.test.ts index 87372fbb4c9..182250d8b90 100644 --- a/src/gateway/session-utils.test.ts +++ b/src/gateway/session-utils.test.ts @@ -259,7 +259,7 @@ describe("gateway session utils", () => { expect(row.thinkingDefault).toBe("medium"); }); - test("session list memoizes repeated thinking enrichment per provider model", async () => { + test("async session list skips per-row thinking enrichment for lightweight rows", async () => { const resolveThinkingProfile = vi.fn(() => ({ levels: [{ id: "off" as const }, { id: "medium" as const }], defaultLevel: "medium" as const, @@ -298,7 +298,10 @@ describe("gateway session utils", () => { }); expect(result.sessions).toHaveLength(5); - expect(resolveThinkingProfile).toHaveBeenCalledTimes(3); + expect(result.sessions.every((session) => session.thinkingLevels === undefined)).toBe(true); + expect(result.sessions.every((session) => session.thinkingOptions === undefined)).toBe(true); + expect(result.sessions.every((session) => session.thinkingDefault === undefined)).toBe(true); + expect(resolveThinkingProfile).toHaveBeenCalledTimes(2); }); test("session list thinking cache preserves case-distinct model catalog entries", async () => { @@ -319,7 +322,7 @@ describe("gateway session utils", () => { compat: { supportedReasoningEfforts: ["low", "medium", "high"] }, }, ]; - const result = await listSessionsFromStoreAsync({ + const result = listSessionsFromStore({ cfg, storePath: "", modelCatalog, @@ -1289,7 +1292,10 @@ describe("listSessionsFromStore selected model display", () => { }), ); expect(listed.sessions[0]?.agentRuntime).toEqual({ id: "pi", source: "implicit" }); - expect(listed.sessions[0]?.thinkingOptions?.length).toBeGreaterThan(0); + expect(listed.sessions[0]?.thinkingLevel).toBeUndefined(); + expect(listed.sessions[0]?.thinkingLevels).toBeUndefined(); + expect(listed.sessions[0]?.thinkingOptions).toBeUndefined(); + expect(listed.sessions[0]?.thinkingDefault).toBeUndefined(); } finally { fs.rmSync(tmpDir, { recursive: true, force: true }); } @@ -1478,6 +1484,58 @@ describe("listSessionsFromStore selected model display", () => { ["agent:review:review", "vercel-ai-gateway", "anthropic/claude-haiku-4-5"], ]); }); + + test("uses persisted runtime model metadata before selected defaults", () => { + const cfg = { + agents: { + defaults: { model: { primary: "openai/gpt-5.4" } }, + list: [{ id: "main", model: { primary: "anthropic/claude-sonnet-4-6" } }], + }, + } as OpenClawConfig; + + const result = listSessionsFromStore({ + cfg, + storePath: "/tmp/sessions.json", + store: { + "agent:main:main": { + sessionId: "sess-main", + updatedAt: Date.now(), + modelProvider: "openai-codex", + model: "gpt-5.5", + } as SessionEntry, + }, + opts: {}, + }); + + expect(result.sessions[0]?.modelProvider).toBe("openai-codex"); + expect(result.sessions[0]?.model).toBe("gpt-5.5"); + }); + + test("uses complete model overrides without default-model fallback", () => { + const cfg = { + agents: { + defaults: { model: { primary: "openai/gpt-5.4" } }, + list: [{ id: "main", model: { primary: "anthropic/claude-sonnet-4-6" } }], + }, + } as OpenClawConfig; + + const result = listSessionsFromStore({ + cfg, + storePath: "/tmp/sessions.json", + store: { + "agent:main:main": { + sessionId: "sess-main", + updatedAt: Date.now(), + providerOverride: "vercel-ai-gateway", + modelOverride: "anthropic/claude-haiku-4-5", + } as SessionEntry, + }, + opts: {}, + }); + + expect(result.sessions[0]?.modelProvider).toBe("vercel-ai-gateway"); + expect(result.sessions[0]?.model).toBe("anthropic/claude-haiku-4-5"); + }); }); describe("resolveSessionModelIdentityRef", () => { diff --git a/src/gateway/session-utils.ts b/src/gateway/session-utils.ts index 4a2b35b1b81..f18e5818677 100644 --- a/src/gateway/session-utils.ts +++ b/src/gateway/session-utils.ts @@ -578,6 +578,27 @@ function resolveSessionRowModelIdentityRef(params: { params.fallbackModelRef, ); } + const runtimeModel = normalizeOptionalString(params.entry?.model); + const runtimeProvider = normalizeOptionalString(params.entry?.modelProvider); + const fallbackModelRef = normalizeOptionalString(params.fallbackModelRef); + if (runtimeModel && runtimeProvider && !fallbackModelRef) { + return { provider: runtimeProvider, model: runtimeModel }; + } + const normalizedOverride = normalizeStoredOverrideModel({ + providerOverride: params.entry?.providerOverride, + modelOverride: params.entry?.modelOverride, + }); + if ( + !runtimeModel && + !fallbackModelRef && + normalizedOverride.providerOverride && + normalizedOverride.modelOverride + ) { + return { + provider: normalizedOverride.providerOverride, + model: normalizedOverride.modelOverride, + }; + } const key = createSessionEntryModelCacheKey({ cfg: params.cfg, agentId: params.agentId, @@ -589,14 +610,12 @@ function resolveSessionRowModelIdentityRef(params: { return cached; } - const runtimeModel = normalizeOptionalString(params.entry?.model); - const runtimeProvider = normalizeOptionalString(params.entry?.modelProvider); if ( !runtimeModel && !runtimeProvider && - !normalizeOptionalString(params.fallbackModelRef) && - !normalizeOptionalString(params.entry?.providerOverride) && - !normalizeOptionalString(params.entry?.modelOverride) + !fallbackModelRef && + !normalizedOverride.providerOverride && + !normalizedOverride.modelOverride ) { const resolved = resolveSessionDefaultModelRefForRow(params.cfg, params.agentId); params.rowContext.modelIdentityByEntryKey.set(key, resolved); @@ -623,6 +642,12 @@ function resolveSessionSelectedModelRef(params: { providerOverride: params.entry?.providerOverride, modelOverride: params.entry?.modelOverride, }); + if (override.providerOverride && override.modelOverride) { + return { + provider: override.providerOverride, + model: override.modelOverride, + }; + } if (!override.modelOverride) { return null; } @@ -1811,12 +1836,23 @@ export function buildGatewaySessionRow(params: { const thinkingProvider = rowModelProvider ?? DEFAULT_PROVIDER; const thinkingModel = rowModel ?? DEFAULT_MODEL; - const thinkingLevels = resolveSessionRowThinkingLevels({ - provider: thinkingProvider, - model: thinkingModel, - modelCatalog: params.modelCatalog, - rowContext, - }); + const thinkingLevels = lightweight + ? undefined + : resolveSessionRowThinkingLevels({ + provider: thinkingProvider, + model: thinkingModel, + modelCatalog: params.modelCatalog, + rowContext, + }); + const thinkingDefault = lightweight + ? undefined + : resolveGatewaySessionThinkingDefault({ + cfg, + provider: thinkingProvider, + model: thinkingModel, + agentId: sessionAgentId, + modelCatalog: params.modelCatalog, + }); const pluginExtensions = !lightweight && entry ? projectPluginSessionExtensionsSync({ sessionKey: key, entry }) : []; @@ -1845,16 +1881,8 @@ export function buildGatewaySessionRow(params: { abortedLastRun: entry?.abortedLastRun, thinkingLevel: entry?.thinkingLevel, thinkingLevels, - thinkingOptions: thinkingLevels.map((level) => level.label), - thinkingDefault: lightweight - ? entry?.thinkingLevel - : resolveGatewaySessionThinkingDefault({ - cfg, - provider: thinkingProvider, - model: thinkingModel, - agentId: sessionAgentId, - modelCatalog: params.modelCatalog, - }), + thinkingOptions: thinkingLevels?.map((level) => level.label), + thinkingDefault, fastMode: entry?.fastMode, verboseLevel: entry?.verboseLevel, traceLevel: entry?.traceLevel,