fix(agents): defer embedded alias normalization (#129700)

This commit is contained in:
Vincent Koc
2026-08-27 11:20:23 +08:00
committed by GitHub
parent 7e362eba35
commit f1a232c395
3 changed files with 88 additions and 1 deletions
@@ -4,6 +4,7 @@ import {
resolvePreparedModelThinkingCompat,
} from "../model-catalog-lookup.js";
import type { ModelCatalogEntry } from "../model-catalog.types.js";
import { resolveModelCandidateChain } from "../model-fallback-candidates.js";
import { resolveInitialEmbeddedRunModel } from "./run/runtime-resolution.js";
const STATIC_MODEL_ID = "claude-haiku-4-5";
@@ -14,6 +15,8 @@ const resolveHookModelSelectionMock = vi.hoisted(() =>
modelId,
})),
);
const loadManifestMetadataSnapshotMock = vi.hoisted(() => vi.fn());
const normalizeProviderModelIdWithRuntimeMock = vi.hoisted(() => vi.fn(() => undefined));
const emptyModelRegistry = {
find: vi.fn((_provider: string, _modelId: string) => null),
@@ -69,7 +72,12 @@ vi.mock("./model.js", () => ({
}));
vi.mock("../provider-model-normalization.runtime.js", () => ({
normalizeProviderModelIdWithRuntime: () => undefined,
normalizeProviderModelIdWithRuntime: normalizeProviderModelIdWithRuntimeMock,
}));
vi.mock("../../plugins/manifest-contract-eligibility.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../../plugins/manifest-contract-eligibility.js")>()),
loadManifestMetadataSnapshot: loadManifestMetadataSnapshotMock,
}));
vi.mock("../harness/runtime-plugin.js", () => ({
@@ -184,6 +192,8 @@ describe("embedded model resolution consistency", () => {
provider,
modelId,
}));
loadManifestMetadataSnapshotMock.mockReset();
normalizeProviderModelIdWithRuntimeMock.mockReset().mockReturnValue(undefined);
});
it("resolves an explicit alias configured only on the selected agent", () => {
@@ -208,6 +218,72 @@ describe("embedded model resolution consistency", () => {
model: "worker-haiku",
}),
).toEqual({ provider: "anthropic", modelId: "claude-haiku-4-5" });
expect(loadManifestMetadataSnapshotMock).not.toHaveBeenCalled();
expect(normalizeProviderModelIdWithRuntimeMock).not.toHaveBeenCalled();
});
it("defers custom-provider normalization until prepared manifest policy is available", () => {
const config = {
agents: {
entries: {
worker: {
model: { primary: "worker-custom" },
models: {
"custom-provider/legacy-model": { alias: "worker-custom" },
},
},
},
},
};
const initial = resolveInitialEmbeddedRunModel({
config,
agentId: "worker",
});
expect(initial).toEqual({
provider: "custom-provider",
modelId: "legacy-model",
});
expect(loadManifestMetadataSnapshotMock).not.toHaveBeenCalled();
expect(normalizeProviderModelIdWithRuntimeMock).not.toHaveBeenCalled();
const manifestPlugins = [
{
modelIdNormalization: {
providers: {
"custom-provider": {
aliases: { "legacy-model": "modern-model" },
},
},
},
},
];
expect(
resolveModelCandidateChain({
cfg: config,
agentId: "worker",
provider: initial.provider,
model: initial.modelId,
requestedRouteResolution: "resolved",
fallbacksOverride: [],
manifestPlugins,
}),
).toEqual([
{
provider: "custom-provider",
model: "modern-model",
routeOrigin: "requested",
routeResolution: "resolved",
},
]);
expect(normalizeProviderModelIdWithRuntimeMock).toHaveBeenCalledWith({
provider: "custom-provider",
plugins: manifestPlugins,
context: {
provider: "custom-provider",
modelId: "modern-model",
},
});
});
it("resolves the same undated configured model for chat and manual compaction", async () => {
@@ -90,9 +90,16 @@ export function resolveInitialEmbeddedRunModel(params: {
model?: string;
}): { provider: string; modelId: string } {
const cfg = params.config ?? {};
// Preliminary route identification stays static; prepared metadata owns
// plugin and workspace normalization once the runtime context exists.
const staticPreliminaryNormalization = {
allowManifestNormalization: false,
allowPluginNormalization: false,
} as const;
const configuredDefault = resolveDefaultModelForAgent({
cfg,
agentId: params.agentId,
...staticPreliminaryNormalization,
});
const explicitProvider = normalizeOptionalString(params.provider);
const explicitModel = normalizeOptionalString(params.model);
@@ -108,6 +115,7 @@ export function resolveInitialEmbeddedRunModel(params: {
cfg,
agentId: params.agentId,
defaultProvider: provider,
...staticPreliminaryNormalization,
});
const resolved = resolveModelRefFromString({
cfg,
@@ -115,6 +123,7 @@ export function resolveInitialEmbeddedRunModel(params: {
raw: explicitModel,
defaultProvider: provider,
aliasIndex,
...staticPreliminaryNormalization,
});
return {
provider: explicitProvider ?? resolved?.ref.provider ?? provider,
+2
View File
@@ -9,6 +9,7 @@ export function resolveDefaultModelForAgent(
params: {
cfg: OpenClawConfig;
agentId?: string;
allowManifestNormalization?: boolean;
allowPluginNormalization?: boolean;
} & ModelManifestNormalizationContext,
): ModelRef {
@@ -17,6 +18,7 @@ export function resolveDefaultModelForAgent(
agentId: params.agentId,
defaultProvider: DEFAULT_PROVIDER,
defaultModel: DEFAULT_MODEL,
allowManifestNormalization: params.allowManifestNormalization,
allowPluginNormalization: params.allowPluginNormalization,
manifestPlugins: params.manifestPlugins,
});