Skip to content

Commit 29c9ce4

Browse files
committed
fix(agent): use static catalog for embedded model fast path
1 parent 88203c9 commit 29c9ce4

4 files changed

Lines changed: 92 additions & 36 deletions

File tree

src/agents/embedded-agent-runner.e2e.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,12 @@ describe("runEmbeddedAgent", () => {
347347
expect(resolveModelCall?.[1]).toBe("openrouter/auto");
348348
expect(resolveModelCall?.[2]).toBe(agentDir);
349349
expect(resolveModelCall?.[3]).toBe(cfg);
350-
expect(
351-
(resolveModelCall?.[4] as { skipAgentDiscovery?: boolean } | undefined)?.skipAgentDiscovery,
352-
).toBe(true);
350+
expect(resolveModelCall?.[4]).toEqual(
351+
expect.objectContaining({
352+
allowBundledStaticCatalogFallback: true,
353+
skipAgentDiscovery: true,
354+
}),
355+
);
353356
expect(ensureOpenClawModelsJsonMock).not.toHaveBeenCalled();
354357
});
355358

src/agents/embedded-agent-runner/model.test.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,19 +525,46 @@ describe("resolveModel", () => {
525525
maxTokens: 8192,
526526
});
527527

528+
const runtimeHooks = createRuntimeHooks();
529+
const prepareProviderDynamicModel = vi.fn(async () => {});
530+
const reasoningEffortMap = {
531+
off: "none",
532+
minimal: "none",
533+
low: "high",
534+
medium: "high",
535+
high: "high",
536+
xhigh: "high",
537+
adaptive: "high",
538+
max: "high",
539+
};
540+
const normalizeProviderResolvedModelWithPlugin = vi.fn(({ context }) => ({
541+
...context.model,
542+
compat: {
543+
...context.model.compat,
544+
supportsStore: false,
545+
supportsReasoningEffort: true,
546+
maxTokensField: "max_tokens",
547+
reasoningEffortMap,
548+
},
549+
}));
528550
const result = await resolveModelAsync(
529551
"mistral",
530552
"mistral-medium-3-5",
531553
"/tmp/agent",
532554
undefined,
533555
{
534556
allowBundledStaticCatalogFallback: true,
535-
runtimeHooks: createRuntimeHooks(),
557+
runtimeHooks: {
558+
...runtimeHooks,
559+
normalizeProviderResolvedModelWithPlugin,
560+
prepareProviderDynamicModel,
561+
},
536562
skipAgentDiscovery: true,
537563
},
538564
);
539565

540-
expectRecordFields(expectResolvedModel(result), {
566+
const model = expectResolvedModel(result);
567+
expectRecordFields(model, {
541568
provider: "mistral",
542569
id: "mistral-medium-3-5",
543570
api: "openai-completions",
@@ -546,6 +573,22 @@ describe("resolveModel", () => {
546573
contextWindow: 262144,
547574
maxTokens: 8192,
548575
});
576+
expect(model.compat).toMatchObject({
577+
supportsStore: false,
578+
supportsReasoningEffort: true,
579+
maxTokensField: "max_tokens",
580+
reasoningEffortMap,
581+
});
582+
expect(normalizeProviderResolvedModelWithPlugin).toHaveBeenCalledWith(
583+
expect.objectContaining({
584+
provider: "mistral",
585+
context: expect.objectContaining({
586+
provider: "mistral",
587+
modelId: "mistral-medium-3-5",
588+
}),
589+
}),
590+
);
591+
expect(prepareProviderDynamicModel).not.toHaveBeenCalled();
549592
expect(resolveBundledStaticCatalogModelMock).toHaveBeenCalledWith({
550593
provider: "mistral",
551594
modelId: "mistral-medium-3-5",

src/agents/embedded-agent-runner/model.ts

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,38 @@ export async function resolveModelAsync(
14141414
};
14151415
}
14161416
const providerConfig = resolveConfiguredProviderConfig(cfg, normalizedRef.provider);
1417+
const resolveStaticCatalogAttempt = () => {
1418+
if (explicitModel || !options?.allowBundledStaticCatalogFallback) {
1419+
return undefined;
1420+
}
1421+
const staticCatalogModel = resolveBundledStaticCatalogModel({
1422+
provider: normalizedRef.provider,
1423+
modelId: normalizedRef.model,
1424+
cfg,
1425+
workspaceDir,
1426+
});
1427+
if (!staticCatalogModel) {
1428+
return undefined;
1429+
}
1430+
const overriddenStaticCatalogModel = applyConfiguredProviderOverrides({
1431+
provider: normalizedRef.provider,
1432+
discoveredModel: staticCatalogModel,
1433+
providerConfig,
1434+
modelId: normalizedRef.model,
1435+
cfg,
1436+
runtimeHooks,
1437+
workspaceDir,
1438+
preferDiscoveredModelMetadata: true,
1439+
});
1440+
return normalizeResolvedModel({
1441+
provider: normalizedRef.provider,
1442+
cfg,
1443+
agentDir: resolvedAgentDir,
1444+
workspaceDir,
1445+
model: overriddenStaticCatalogModel,
1446+
runtimeHooks,
1447+
});
1448+
};
14171449
const authProfile = resolveDynamicModelAuthProfile({
14181450
provider: normalizedRef.provider,
14191451
cfg,
@@ -1460,40 +1492,17 @@ export async function resolveModelAsync(
14601492
runtimeHooks,
14611493
})
14621494
? explicitModel.model
1463-
: await resolveDynamicAttempt();
1495+
: options?.skipAgentDiscovery
1496+
? (resolveStaticCatalogAttempt() ?? (await resolveDynamicAttempt()))
1497+
: await resolveDynamicAttempt();
14641498
if (!model && !explicitModel && options?.retryTransientProviderRuntimeMiss) {
14651499
// Startup can race the first provider-runtime snapshot load on a fresh
14661500
// gateway boot. Retry once before surfacing a user-visible "Unknown model"
14671501
// that disappears on the next message.
14681502
model = await resolveDynamicAttempt();
14691503
}
1470-
if (!model && !explicitModel && options?.allowBundledStaticCatalogFallback) {
1471-
const staticCatalogModel = resolveBundledStaticCatalogModel({
1472-
provider: normalizedRef.provider,
1473-
modelId: normalizedRef.model,
1474-
cfg,
1475-
workspaceDir,
1476-
});
1477-
if (staticCatalogModel) {
1478-
const overriddenStaticCatalogModel = applyConfiguredProviderOverrides({
1479-
provider: normalizedRef.provider,
1480-
discoveredModel: staticCatalogModel,
1481-
providerConfig,
1482-
modelId: normalizedRef.model,
1483-
cfg,
1484-
runtimeHooks,
1485-
workspaceDir,
1486-
preferDiscoveredModelMetadata: true,
1487-
});
1488-
model = normalizeResolvedModel({
1489-
provider: normalizedRef.provider,
1490-
cfg,
1491-
agentDir: resolvedAgentDir,
1492-
workspaceDir,
1493-
model: overriddenStaticCatalogModel,
1494-
runtimeHooks,
1495-
});
1496-
}
1504+
if (!model && !options?.skipAgentDiscovery) {
1505+
model = resolveStaticCatalogAttempt();
14971506
}
14981507
if (model && options?.allowBundledStaticCatalogFallback) {
14991508
const staticCatalogModel = resolveBundledStaticCatalogModel({

src/agents/embedded-agent-runner/run.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -700,9 +700,10 @@ export async function runEmbeddedAgent(
700700
agentDir,
701701
params.config,
702702
{
703-
// Plugin dynamic model hooks can resolve explicit model refs without
704-
// first generating OpenClaw models.json. This keeps one-shot model runs from
705-
// blocking on unrelated provider discovery.
703+
// Static catalogs and plugin dynamic model hooks can resolve explicit model refs
704+
// without first generating OpenClaw models.json. This keeps one-shot model runs
705+
// from blocking on unrelated provider discovery.
706+
allowBundledStaticCatalogFallback: true,
706707
skipAgentDiscovery: true,
707708
workspaceDir: resolvedWorkspace,
708709
authProfileId: params.authProfileId,

0 commit comments

Comments
 (0)