Optimize session list model row resolution

This commit is contained in:
Forge
2026-05-05 22:35:42 -07:00
committed by Ayaan Zaidi
parent 8bfec5b9ac
commit 948375f494
2 changed files with 111 additions and 25 deletions

View File

@@ -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", () => {

View File

@@ -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,