perf(agents): avoid full setup registry for runtime aliases

This commit is contained in:
Vincent Koc
2026-05-31 17:02:57 +02:00
parent 4ef141d525
commit a52c4d101a
5 changed files with 136 additions and 15 deletions

View File

@@ -232,6 +232,33 @@ export function listCliRuntimeProviderIds(
].toSorted();
}
export function resolveCliRuntimeCanonicalProvider(params: {
runtime: string | undefined;
config?: OpenClawConfig;
env?: NodeJS.ProcessEnv;
includeSetupRegistry?: boolean;
}): string | undefined {
const runtime = normalizeBackendKey(params.runtime ?? "");
if (!runtime) {
return undefined;
}
const runtimeBinding = listCliRuntimeModelBackendBindings().find(
(binding) => binding.runtime === runtime,
);
if (runtimeBinding) {
return runtimeBinding.provider;
}
if (params.includeSetupRegistry !== true) {
return undefined;
}
const setupBackend = cliBackendsDeps.resolvePluginSetupCliBackend({
backend: runtime,
config: params.config,
env: params.env,
});
return setupBackend ? resolveCliBackendModelProvider(setupBackend.backend) : undefined;
}
export function resolveCliRuntimeModelBackendBinding(params: {
provider: string | undefined;
runtime: string | undefined;
@@ -253,11 +280,22 @@ export function resolveCliRuntimeModelBackendBinding(params: {
if (!includeSetupRegistry) {
return undefined;
}
return listCliRuntimeModelBackendBindings({
const setupBackend = cliBackendsDeps.resolvePluginSetupCliBackend({
backend: runtime,
config: params.config,
env: params.env,
includeSetupRegistry: true,
}).find((binding) => binding.provider === provider && binding.runtime === runtime);
});
if (!setupBackend) {
return undefined;
}
const setupProvider = resolveCliBackendModelProvider(setupBackend.backend);
return setupProvider === provider
? {
provider,
runtime,
...(setupBackend.pluginId ? { pluginId: setupBackend.pluginId } : {}),
}
: undefined;
}
export function isCliRuntimeModelBackendForProvider(params: {

View File

@@ -185,4 +185,39 @@ describe("areRuntimeModelRefsEquivalent", () => {
}),
).toBe(true);
});
it("resolves one setup runtime alias without loading the full setup registry", () => {
cliBackendsTesting.setDepsForTest({
resolvePluginSetupCliBackend: ({ backend }) =>
backend === "claude-cli"
? {
pluginId: "anthropic",
backend: {
id: "claude-cli",
modelProvider: "anthropic",
config: { command: "claude" },
bundleMcp: false,
},
}
: undefined,
resolvePluginSetupRegistry: () => {
throw new Error("setup registry should not load for a single runtime alias");
},
resolveRuntimeCliBackends: () => [],
});
expect(
areRuntimeModelRefsEquivalent("anthropic/claude-opus-4-7", "claude-cli/claude-opus-4-7", {
config: {
agents: {
defaults: {
cliBackends: {
"claude-cli": { command: "claude" },
},
},
},
},
}),
).toBe(true);
});
});

View File

@@ -5,6 +5,7 @@ import {
isCliRuntimeModelBackendForProvider,
listCliRuntimeModelBackendBindings,
listCliRuntimeProviderIds,
resolveCliRuntimeCanonicalProvider,
resolveCliRuntimeModelBackendBinding,
} from "./cli-backends.js";
import { resolveModelRuntimePolicy } from "./model-runtime-policy.js";
@@ -53,14 +54,14 @@ function canonicalizeRuntimeAliasProvider(
provider: string,
options: RuntimeAliasComparisonOptions = {},
): string {
const normalized = normalizeProviderId(provider);
return (
listCliRuntimeModelBackendBindings({
resolveCliRuntimeCanonicalProvider({
runtime: provider,
config: options.config,
env: options.env,
includeSetupRegistry:
options.includeSetupRegistry ?? (options.config !== undefined || options.env !== undefined),
}).find((binding) => binding.runtime === normalized)?.provider ?? provider
}) ?? provider
);
}

View File

@@ -155,6 +155,7 @@ describe("fallback-state", () => {
fallbackNoticeActiveModel: "claude-cli/claude-opus-4-7",
fallbackNoticeReason: "selected model unavailable",
},
cfg: {},
});
expect(resolved.fallbackActive).toBe(false);
@@ -164,6 +165,47 @@ describe("fallback-state", () => {
expect(resolved.nextState.activeModel).toBeUndefined();
});
it("does not repeat runtime alias comparison when persisted fallback refs match", () => {
let setupBackendLookups = 0;
cliBackendsTesting.setDepsForTest({
resolvePluginSetupCliBackend: ({ backend }) => {
setupBackendLookups += 1;
return backend === "claude-cli"
? {
pluginId: "anthropic",
backend: {
id: "claude-cli",
modelProvider: "anthropic",
config: { command: "claude" },
bundleMcp: false,
},
}
: undefined;
},
resolvePluginSetupRegistry: () => {
throw new Error("full setup registry should not load for a single runtime alias");
},
resolveRuntimeCliBackends: () => [],
});
const resolved = resolveFallbackTransition({
selectedProvider: "anthropic",
selectedModel: "claude-opus-4-7",
activeProvider: "claude-cli",
activeModel: "claude-opus-4-7",
attempts: [],
state: {
fallbackNoticeSelectedModel: "anthropic/claude-opus-4-7",
fallbackNoticeActiveModel: "claude-cli/claude-opus-4-7",
fallbackNoticeReason: "selected model unavailable",
},
cfg: {},
});
expect(resolved.fallbackActive).toBe(false);
expect(setupBackendLookups).toBe(2);
});
it("does not build a fallback notice for equivalent CLI runtime aliases", () => {
registerAnthropicCliBackendForTest();

View File

@@ -166,15 +166,20 @@ export function resolveFallbackTransition(params: {
fallbackActive &&
(previousState.selectedModel !== selectedModelRef ||
previousState.activeModel !== activeModelRef);
const previousStateWasRealFallback = Boolean(
previousState.selectedModel &&
previousState.activeModel &&
!areRuntimeModelRefsEquivalent(
previousState.selectedModel,
previousState.activeModel,
comparisonOptions,
),
);
const previousStateMatchesCurrent =
previousState.selectedModel === selectedModelRef &&
previousState.activeModel === activeModelRef;
const previousStateWasRealFallback = previousStateMatchesCurrent
? fallbackActive
: Boolean(
previousState.selectedModel &&
previousState.activeModel &&
!areRuntimeModelRefsEquivalent(
previousState.selectedModel,
previousState.activeModel,
comparisonOptions,
),
);
const fallbackCleared = !fallbackActive && previousStateWasRealFallback;
const reasonSummary = buildFallbackReasonSummary(params.attempts);
const attemptSummaries = buildFallbackAttemptSummaries(params.attempts);