Skip to content

Commit a52c4d1

Browse files
committed
perf(agents): avoid full setup registry for runtime aliases
1 parent 4ef141d commit a52c4d1

5 files changed

Lines changed: 136 additions & 15 deletions

File tree

src/agents/cli-backends.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,33 @@ export function listCliRuntimeProviderIds(
232232
].toSorted();
233233
}
234234

235+
export function resolveCliRuntimeCanonicalProvider(params: {
236+
runtime: string | undefined;
237+
config?: OpenClawConfig;
238+
env?: NodeJS.ProcessEnv;
239+
includeSetupRegistry?: boolean;
240+
}): string | undefined {
241+
const runtime = normalizeBackendKey(params.runtime ?? "");
242+
if (!runtime) {
243+
return undefined;
244+
}
245+
const runtimeBinding = listCliRuntimeModelBackendBindings().find(
246+
(binding) => binding.runtime === runtime,
247+
);
248+
if (runtimeBinding) {
249+
return runtimeBinding.provider;
250+
}
251+
if (params.includeSetupRegistry !== true) {
252+
return undefined;
253+
}
254+
const setupBackend = cliBackendsDeps.resolvePluginSetupCliBackend({
255+
backend: runtime,
256+
config: params.config,
257+
env: params.env,
258+
});
259+
return setupBackend ? resolveCliBackendModelProvider(setupBackend.backend) : undefined;
260+
}
261+
235262
export function resolveCliRuntimeModelBackendBinding(params: {
236263
provider: string | undefined;
237264
runtime: string | undefined;
@@ -253,11 +280,22 @@ export function resolveCliRuntimeModelBackendBinding(params: {
253280
if (!includeSetupRegistry) {
254281
return undefined;
255282
}
256-
return listCliRuntimeModelBackendBindings({
283+
const setupBackend = cliBackendsDeps.resolvePluginSetupCliBackend({
284+
backend: runtime,
257285
config: params.config,
258286
env: params.env,
259-
includeSetupRegistry: true,
260-
}).find((binding) => binding.provider === provider && binding.runtime === runtime);
287+
});
288+
if (!setupBackend) {
289+
return undefined;
290+
}
291+
const setupProvider = resolveCliBackendModelProvider(setupBackend.backend);
292+
return setupProvider === provider
293+
? {
294+
provider,
295+
runtime,
296+
...(setupBackend.pluginId ? { pluginId: setupBackend.pluginId } : {}),
297+
}
298+
: undefined;
261299
}
262300

263301
export function isCliRuntimeModelBackendForProvider(params: {

src/agents/model-runtime-aliases.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,39 @@ describe("areRuntimeModelRefsEquivalent", () => {
185185
}),
186186
).toBe(true);
187187
});
188+
189+
it("resolves one setup runtime alias without loading the full setup registry", () => {
190+
cliBackendsTesting.setDepsForTest({
191+
resolvePluginSetupCliBackend: ({ backend }) =>
192+
backend === "claude-cli"
193+
? {
194+
pluginId: "anthropic",
195+
backend: {
196+
id: "claude-cli",
197+
modelProvider: "anthropic",
198+
config: { command: "claude" },
199+
bundleMcp: false,
200+
},
201+
}
202+
: undefined,
203+
resolvePluginSetupRegistry: () => {
204+
throw new Error("setup registry should not load for a single runtime alias");
205+
},
206+
resolveRuntimeCliBackends: () => [],
207+
});
208+
209+
expect(
210+
areRuntimeModelRefsEquivalent("anthropic/claude-opus-4-7", "claude-cli/claude-opus-4-7", {
211+
config: {
212+
agents: {
213+
defaults: {
214+
cliBackends: {
215+
"claude-cli": { command: "claude" },
216+
},
217+
},
218+
},
219+
},
220+
}),
221+
).toBe(true);
222+
});
188223
});

src/agents/model-runtime-aliases.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
isCliRuntimeModelBackendForProvider,
66
listCliRuntimeModelBackendBindings,
77
listCliRuntimeProviderIds,
8+
resolveCliRuntimeCanonicalProvider,
89
resolveCliRuntimeModelBackendBinding,
910
} from "./cli-backends.js";
1011
import { resolveModelRuntimePolicy } from "./model-runtime-policy.js";
@@ -53,14 +54,14 @@ function canonicalizeRuntimeAliasProvider(
5354
provider: string,
5455
options: RuntimeAliasComparisonOptions = {},
5556
): string {
56-
const normalized = normalizeProviderId(provider);
5757
return (
58-
listCliRuntimeModelBackendBindings({
58+
resolveCliRuntimeCanonicalProvider({
59+
runtime: provider,
5960
config: options.config,
6061
env: options.env,
6162
includeSetupRegistry:
6263
options.includeSetupRegistry ?? (options.config !== undefined || options.env !== undefined),
63-
}).find((binding) => binding.runtime === normalized)?.provider ?? provider
64+
}) ?? provider
6465
);
6566
}
6667

src/auto-reply/fallback-state.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ describe("fallback-state", () => {
155155
fallbackNoticeActiveModel: "claude-cli/claude-opus-4-7",
156156
fallbackNoticeReason: "selected model unavailable",
157157
},
158+
cfg: {},
158159
});
159160

160161
expect(resolved.fallbackActive).toBe(false);
@@ -164,6 +165,47 @@ describe("fallback-state", () => {
164165
expect(resolved.nextState.activeModel).toBeUndefined();
165166
});
166167

168+
it("does not repeat runtime alias comparison when persisted fallback refs match", () => {
169+
let setupBackendLookups = 0;
170+
cliBackendsTesting.setDepsForTest({
171+
resolvePluginSetupCliBackend: ({ backend }) => {
172+
setupBackendLookups += 1;
173+
return backend === "claude-cli"
174+
? {
175+
pluginId: "anthropic",
176+
backend: {
177+
id: "claude-cli",
178+
modelProvider: "anthropic",
179+
config: { command: "claude" },
180+
bundleMcp: false,
181+
},
182+
}
183+
: undefined;
184+
},
185+
resolvePluginSetupRegistry: () => {
186+
throw new Error("full setup registry should not load for a single runtime alias");
187+
},
188+
resolveRuntimeCliBackends: () => [],
189+
});
190+
191+
const resolved = resolveFallbackTransition({
192+
selectedProvider: "anthropic",
193+
selectedModel: "claude-opus-4-7",
194+
activeProvider: "claude-cli",
195+
activeModel: "claude-opus-4-7",
196+
attempts: [],
197+
state: {
198+
fallbackNoticeSelectedModel: "anthropic/claude-opus-4-7",
199+
fallbackNoticeActiveModel: "claude-cli/claude-opus-4-7",
200+
fallbackNoticeReason: "selected model unavailable",
201+
},
202+
cfg: {},
203+
});
204+
205+
expect(resolved.fallbackActive).toBe(false);
206+
expect(setupBackendLookups).toBe(2);
207+
});
208+
167209
it("does not build a fallback notice for equivalent CLI runtime aliases", () => {
168210
registerAnthropicCliBackendForTest();
169211

src/auto-reply/fallback-state.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,20 @@ export function resolveFallbackTransition(params: {
166166
fallbackActive &&
167167
(previousState.selectedModel !== selectedModelRef ||
168168
previousState.activeModel !== activeModelRef);
169-
const previousStateWasRealFallback = Boolean(
170-
previousState.selectedModel &&
171-
previousState.activeModel &&
172-
!areRuntimeModelRefsEquivalent(
173-
previousState.selectedModel,
174-
previousState.activeModel,
175-
comparisonOptions,
176-
),
177-
);
169+
const previousStateMatchesCurrent =
170+
previousState.selectedModel === selectedModelRef &&
171+
previousState.activeModel === activeModelRef;
172+
const previousStateWasRealFallback = previousStateMatchesCurrent
173+
? fallbackActive
174+
: Boolean(
175+
previousState.selectedModel &&
176+
previousState.activeModel &&
177+
!areRuntimeModelRefsEquivalent(
178+
previousState.selectedModel,
179+
previousState.activeModel,
180+
comparisonOptions,
181+
),
182+
);
178183
const fallbackCleared = !fallbackActive && previousStateWasRealFallback;
179184
const reasonSummary = buildFallbackReasonSummary(params.attempts);
180185
const attemptSummaries = buildFallbackAttemptSummaries(params.attempts);

0 commit comments

Comments
 (0)