mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
perf(sessions): reuse plugin metadata during listing (#117707)
This commit is contained in:
committed by
GitHub
parent
ab8090e0d3
commit
6c63c57fe2
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
@@ -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 ??
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)`
|
||||
|
||||
Reference in New Issue
Block a user