perf(sessions): reuse plugin metadata during listing (#117707)

This commit is contained in:
Peter Steinberger
2026-08-01 17:22:49 -07:00
committed by GitHub
parent ab8090e0d3
commit 6c63c57fe2
7 changed files with 151 additions and 36 deletions
+17 -1
View File
@@ -3,9 +3,25 @@
*/
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveRuntimeCliBackends } from "../plugins/cli-backends.runtime.js";
import { resolvePluginSetupCliBackendDescriptor } from "../plugins/setup-registry.runtime.js";
import {
resolvePluginSetupCliBackendDescriptor,
resolvePluginSetupCliBackendIds,
} from "../plugins/setup-registry.runtime.js";
import { normalizeProviderId } from "./model-ref-shared.js";
export type CliProviderClassifier = (provider: string) => boolean;
/** Prepare one CLI-provider lookup for request paths that classify multiple models. */
export function prepareCliProviderClassifier(cfg?: OpenClawConfig): CliProviderClassifier {
const providers = new Set(
[
...resolveRuntimeCliBackends().map((backend) => backend.id),
...resolvePluginSetupCliBackendIds({ config: cfg }),
].map(normalizeProviderId),
);
return (provider) => providers.has(normalizeProviderId(provider));
}
/** Return true when a provider id resolves to a configured or plugin CLI backend. */
export function isCliProvider(provider: string, cfg?: OpenClawConfig): boolean {
const normalized = normalizeProviderId(provider);
+5 -1
View File
@@ -79,7 +79,11 @@ export {
resolveModelAliasFromPair,
resolveModelRefFromString,
};
export { isCliProvider } from "./model-selection-cli.js";
export {
isCliProvider,
prepareCliProviderClassifier,
type CliProviderClassifier,
} from "./model-selection-cli.js";
// Cron imports this narrow owner directly; the public facade must not fork its policy.
export { getModelRefStatus } from "./model-selection-resolve.js";
+10 -5
View File
@@ -9,6 +9,7 @@ import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
import {
inferUniqueProviderFromConfiguredModels,
isCliProvider,
type CliProviderClassifier,
} from "../agents/model-selection.js";
import { resolveAgentModelPrimaryValue } from "../config/model-input.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
@@ -95,15 +96,16 @@ function normalizeCliRuntimeDisplayRef(
cfg: OpenClawConfig,
ref: SessionDisplayModelRef,
defaultRef: SessionDisplayModelRef,
classifyCliProvider: CliProviderClassifier,
): SessionDisplayModelRef {
if (!isCliProvider(ref.provider, cfg)) {
if (!classifyCliProvider(ref.provider)) {
return ref;
}
if (ref.model.includes("/")) {
// CLI runtimes can store the real provider/model inside the model field;
// prefer that embedded provider when it is not another CLI runtime alias.
const parsed = parseModelRef(ref.model, defaultRef.provider);
if (!isCliProvider(parsed.provider, cfg)) {
if (!classifyCliProvider(parsed.provider)) {
return parsed;
}
}
@@ -111,13 +113,13 @@ function normalizeCliRuntimeDisplayRef(
cfg,
model: ref.model,
});
if (inferredProvider && !isCliProvider(inferredProvider, cfg)) {
if (inferredProvider && !classifyCliProvider(inferredProvider)) {
return { provider: inferredProvider, model: ref.model };
}
// If the CLI runtime model cannot be mapped to a concrete provider, fall
// back to the configured default provider so rows stay comparable.
const parsed = parseModelRef(ref.model, defaultRef.provider);
if (!isCliProvider(parsed.provider, cfg)) {
if (!classifyCliProvider(parsed.provider)) {
return parsed;
}
return {
@@ -130,14 +132,16 @@ function normalizeCliRuntimeDisplayRef(
export function resolveSessionDisplayModel(
cfg: OpenClawConfig,
row: SessionDisplayModelRow,
classifyCliProvider?: CliProviderClassifier,
): string {
return resolveSessionDisplayModelRef(cfg, row).model;
return resolveSessionDisplayModelRef(cfg, row, classifyCliProvider).model;
}
/** Resolves provider/model display metadata for a session row. */
export function resolveSessionDisplayModelRef(
cfg: OpenClawConfig,
row: SessionDisplayModelRow,
classifyCliProvider: CliProviderClassifier = (provider) => isCliProvider(provider, cfg),
): SessionDisplayModelRef {
const agentId = row.key.startsWith("agent:") ? row.key.split(":")[1] : undefined;
const defaultRef = resolveDefaultModelRef(cfg, agentId);
@@ -157,6 +161,7 @@ export function resolveSessionDisplayModelRef(
cfg,
parseModelRef(row.model, row.modelProvider ?? defaultRef.provider),
defaultRef,
classifyCliProvider,
);
}
return defaultRef;
@@ -0,0 +1,91 @@
// Session listing prepares plugin-backed CLI provider metadata once for every row.
import { afterEach, describe, expect, it, vi } from "vitest";
import type { SessionEntry } from "../config/sessions/types.js";
import {
mockSessionsConfig,
resetMockSessionsConfig,
runSessionsJson,
setMockSessionsConfig,
writeStore,
} from "./sessions.test-helpers.js";
const resolvePluginMetadataSnapshotMock = vi.hoisted(() => vi.fn());
vi.mock("../plugins/plugin-metadata-snapshot.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../plugins/plugin-metadata-snapshot.js")>()),
resolvePluginMetadataSnapshot: resolvePluginMetadataSnapshotMock,
}));
mockSessionsConfig();
const { sessionsCommand } = await import("./sessions.js");
afterEach(() => {
resetMockSessionsConfig();
resolvePluginMetadataSnapshotMock.mockReset();
});
describe("sessions plugin metadata preparation", () => {
it("loads one plugin metadata snapshot before enriching every stored row", async () => {
const config = {
agents: {
defaults: {
model: { primary: "openai/gpt-5.5" },
models: { "openai/gpt-5.5": {} },
contextTokens: 128_000,
},
},
};
setMockSessionsConfig(() => config);
resolvePluginMetadataSnapshotMock.mockReturnValue({
configFingerprint: "sessions-plugin-metadata-test",
index: {
plugins: [
{
pluginId: "fixture-plugin",
origin: "global",
enabled: true,
},
],
},
plugins: [
{
id: "fixture-plugin",
origin: "global",
cliBackends: ["fixture-cli"],
},
],
});
const entries = Object.fromEntries(
Array.from({ length: 12 }, (_, index) => [
`agent:main:fixture-${index}`,
{
sessionId: `fixture-session-${index}`,
updatedAt: Date.now() - index * 1_000,
modelProvider: "fixture-cli",
model: "openai/gpt-5.5",
} satisfies SessionEntry,
]),
);
const store = await writeStore(entries, "sessions-plugin-metadata");
const payload = await runSessionsJson<{
count: number;
totalCount: number;
sessions: Array<{ model: string; modelProvider: string }>;
}>(sessionsCommand, store, { limit: 1 });
expect(payload).toMatchObject({
count: 1,
totalCount: 12,
sessions: [{ model: "gpt-5.5", modelProvider: "openai" }],
});
expect(payload.sessions[0]).not.toHaveProperty("displayModelRef");
expect(resolvePluginMetadataSnapshotMock).toHaveBeenCalledTimes(1);
expect(resolvePluginMetadataSnapshotMock).toHaveBeenCalledWith({
config,
env: process.env,
});
});
});
+18 -27
View File
@@ -12,6 +12,10 @@ import { isRich, theme } from "../../packages/terminal-core/src/theme.js";
import { readAcpSessionMetaForEntry } from "../acp/runtime/session-meta.js";
import { resolveModelAgentRuntimeMetadata } from "../agents/agent-runtime-metadata.js";
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
import {
prepareCliProviderClassifier,
type CliProviderClassifier,
} from "../agents/model-selection.js";
import { resolveRuntimePolicySessionKey } from "../auto-reply/reply/runtime-policy-session-key.js";
import { normalizeChatType } from "../channels/chat-type.js";
import { getRuntimeConfig } from "../config/config.js";
@@ -55,6 +59,8 @@ type SessionRow = SessionDisplayRow & {
kind: SessionKind;
agentRuntime: ReturnType<typeof resolveModelAgentRuntimeMetadata>;
runtimeLabel: string;
/** Carry the prepared identity into JSON/table emission without re-resolving plugin metadata. */
displayModelRef: { provider: string; model: string };
/**
* True only when the session has persisted ACP runtime metadata. Key-shape
* alone is not sufficient because ACP bridge sessions (translator.ts) may
@@ -215,9 +221,7 @@ function resolveSessionRuntimeLabel(params: {
entry: SessionEntry;
agentRuntime: ReturnType<typeof resolveModelAgentRuntimeMetadata>;
modelProvider: string;
model: string;
agentId: string;
sessionKey: string;
classifyCliProvider: CliProviderClassifier;
}): string {
const id = normalizeOptionalLowercaseString(params.agentRuntime.id);
const resolvedHarness = id && id !== "openclaw" && id !== "auto" ? id : undefined;
@@ -226,6 +230,7 @@ function resolveSessionRuntimeLabel(params: {
sessionEntry: params.entry,
resolvedHarness,
fallbackProvider: params.modelProvider,
classifyCliProvider: params.classifyCliProvider,
});
}
@@ -240,8 +245,9 @@ function resolveSessionStoreDisplayPath(target: { agentId: string; storePath: st
}).path;
}
function toJsonSessionRow(row: SessionRow): Omit<SessionRow, "runtimeLabel"> {
const { runtimeLabel, ...jsonRow } = row;
function toJsonSessionRow(row: SessionRow): Omit<SessionRow, "displayModelRef" | "runtimeLabel"> {
const { displayModelRef, runtimeLabel, ...jsonRow } = row;
void displayModelRef;
void runtimeLabel;
return jsonRow;
}
@@ -365,6 +371,8 @@ export async function sessionsCommand(
return;
}
const classifyCliProvider = prepareCliProviderClassifier(cfg);
const allRows = targets.flatMap((target) => {
return listSessionEntriesReadOnly({ agentId: target.agentId, storePath: target.storePath })
.filter(({ entry }) => {
@@ -390,7 +398,7 @@ export async function sessionsCommand(
// ACP rows need stored-key metadata before model/runtime resolution so
// bridge sessions and true ACP runtime sessions display differently.
const modelRef = applyAcpModelOverlayIfNeeded(
resolveSessionDisplayModelRef(cfg, row),
resolveSessionDisplayModelRef(cfg, row, classifyCliProvider),
acpSessionKey,
acpRuntime,
);
@@ -408,6 +416,7 @@ export async function sessionsCommand(
agentId,
acpRuntime,
agentRuntime,
displayModelRef: modelRef,
kind: classifySessionKind(row.key, entry),
runtimePolicySessionKey: resolveDisplayRuntimePolicySessionKey({
cfg,
@@ -419,9 +428,7 @@ export async function sessionsCommand(
entry,
agentRuntime,
modelProvider: modelRef.provider,
model: modelRef.model,
agentId,
sessionKey: row.key,
classifyCliProvider,
}),
});
});
@@ -450,15 +457,7 @@ export async function sessionsCommand(
sessions: await Promise.all(
rows.map(async (row) => {
const r = toJsonSessionRow(row);
const modelRef = applyAcpModelOverlayIfNeeded(
resolveSessionDisplayModelRef(cfg, r),
resolveStoredSessionKeyForAgentStore({
cfg,
agentId: row.agentId,
sessionKey: r.key,
}),
row.acpRuntime,
);
const modelRef = row.displayModelRef;
return {
...r,
totalTokens: resolveSessionTotalTokens(r) ?? null,
@@ -520,15 +519,7 @@ export async function sessionsCommand(
runtime.log(rich ? theme.heading(header) : header);
for (const row of rows) {
const model = applyAcpModelOverlayIfNeeded(
resolveSessionDisplayModelRef(cfg, row),
resolveStoredSessionKeyForAgentStore({
cfg,
agentId: row.agentId,
sessionKey: row.key,
}),
row.acpRuntime,
).model;
const model = row.displayModelRef.model;
const contextTokens =
row.contextTokens ??
configuredContextTokens ??
+7
View File
@@ -90,3 +90,10 @@ export function resolvePluginSetupCliBackendDescriptor(
(entry) => normalizeProviderId(entry.backend.id) === normalized,
);
}
/** Resolve enabled setup CLI backend ids from one metadata snapshot. */
export function resolvePluginSetupCliBackendIds(
params: Omit<SetupCliBackendDescriptorLookupParams, "backend"> = {},
): string[] {
return resolveSetupCliBackendDescriptors(params).map((entry) => entry.backend.id);
}
+3 -2
View File
@@ -5,7 +5,7 @@ import {
normalizeOptionalString,
} from "@openclaw/normalization-core/string-coerce";
import { sanitizeTerminalText } from "../../packages/terminal-core/src/safe-text.js";
import { isCliProvider } from "../agents/model-selection.js";
import { isCliProvider, type CliProviderClassifier } from "../agents/model-selection.js";
import type { SessionEntry } from "../config/sessions/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
@@ -27,6 +27,7 @@ export function resolveAgentRuntimeLabel(args: {
>;
resolvedHarness?: string;
fallbackProvider?: string;
classifyCliProvider?: CliProviderClassifier;
}): string {
const acpAgentRaw = normalizeOptionalString(args.sessionEntry?.acp?.agent);
const acpAgent = acpAgentRaw ? sanitizeTerminalText(acpAgentRaw) : undefined;
@@ -49,7 +50,7 @@ export function resolveAgentRuntimeLabel(args: {
normalizeOptionalString(args.sessionEntry?.providerOverride) ??
normalizeOptionalString(args.fallbackProvider);
const provider = providerRaw ? sanitizeTerminalText(providerRaw) : undefined;
if (provider && isCliProvider(provider, args.config)) {
if (provider && (args.classifyCliProvider?.(provider) ?? isCliProvider(provider, args.config))) {
return (
AGENT_RUNTIME_LABELS[normalizeOptionalLowercaseString(providerRaw) ?? ""] ??
`${provider} (cli)`