Skip to content

Commit 4ee13a1

Browse files
fix(agents): normalize prefixed Anthropic model ids
1 parent f7a1d3f commit 4ee13a1

5 files changed

Lines changed: 118 additions & 1 deletion

File tree

packages/model-catalog-core/src/provider-model-id-normalization.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,16 @@ describe("provider model id policy normalization", () => {
3434
),
3535
).toBe("openrouter/google/gemini-3.1-pro-preview");
3636
});
37+
38+
it("normalizes native Anthropic catalog refs without retaining the provider prefix", () => {
39+
expect(
40+
normalizeStaticProviderModelIdWithPolicies(
41+
"anthropic",
42+
"anthropic/claude-haiku-4-5",
43+
),
44+
).toBe("claude-haiku-4-5");
45+
expect(
46+
normalizeConfiguredProviderCatalogModelId("anthropic", "anthropic/claude-haiku-4-5"),
47+
).toBe("claude-haiku-4-5");
48+
});
3749
});

packages/model-catalog-core/src/provider-model-id-normalization.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,12 @@ export function normalizeBuiltInProviderModelId(provider: string, model: string)
119119
"opus-4.6": "claude-opus-4-6",
120120
"sonnet-4.6": "claude-sonnet-4-6",
121121
};
122-
return anthropicAliases[normalizeLowercaseStringOrEmpty(model)] ?? model;
122+
const anthropicPrefix = "anthropic/";
123+
const normalizedModel = normalizeLowercaseStringOrEmpty(model);
124+
const providerModel = normalizedModel.startsWith(anthropicPrefix)
125+
? model.trim().slice(anthropicPrefix.length)
126+
: model;
127+
return anthropicAliases[normalizeLowercaseStringOrEmpty(providerModel)] ?? providerModel;
123128
}
124129
if (normalizedProvider === "vercel-ai-gateway") {
125130
const vercelAliases: Record<string, string> = {

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,70 @@ describe("resolveModel", () => {
556556
expect(discoverModels).not.toHaveBeenCalled();
557557
});
558558

559+
it("looks up each static fallback candidate with its own normalized model id", async () => {
560+
resolveBundledStaticCatalogModelMock.mockImplementation(({ provider, modelId }) => ({
561+
provider,
562+
id: modelId,
563+
name: modelId,
564+
api: "openai-responses",
565+
input: ["text"],
566+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
567+
}));
568+
569+
const anthropicResult = await resolveModelAsync(
570+
"anthropic",
571+
"anthropic/claude-haiku-4-5",
572+
"/tmp/agent",
573+
undefined,
574+
{
575+
allowBundledStaticCatalogFallback: true,
576+
runtimeHooks: createRuntimeHooks(),
577+
skipAgentDiscovery: true,
578+
skipProviderRuntimeHooks: true,
579+
},
580+
);
581+
const openaiResult = await resolveModelAsync("openai", "gpt-4o", "/tmp/agent", undefined, {
582+
allowBundledStaticCatalogFallback: true,
583+
runtimeHooks: createRuntimeHooks(),
584+
skipAgentDiscovery: true,
585+
skipProviderRuntimeHooks: true,
586+
});
587+
588+
expectRecordFields(expectResolvedModel(anthropicResult), {
589+
provider: "anthropic",
590+
id: "claude-haiku-4-5",
591+
});
592+
expectRecordFields(expectResolvedModel(openaiResult), {
593+
provider: "openai",
594+
id: "gpt-4o",
595+
});
596+
expect(resolveBundledStaticCatalogModelMock).toHaveBeenCalledWith({
597+
provider: "anthropic",
598+
modelId: "claude-haiku-4-5",
599+
cfg: undefined,
600+
workspaceDir: undefined,
601+
});
602+
expect(resolveBundledStaticCatalogModelMock).toHaveBeenCalledWith({
603+
provider: "openai",
604+
modelId: "gpt-4o",
605+
cfg: undefined,
606+
workspaceDir: undefined,
607+
});
608+
expect(resolveBundledStaticCatalogModelMock).not.toHaveBeenCalledWith(
609+
expect.objectContaining({
610+
provider: "openai",
611+
modelId: "claude-haiku-4-5",
612+
}),
613+
);
614+
expect(resolveBundledStaticCatalogModelMock).not.toHaveBeenCalledWith(
615+
expect.objectContaining({
616+
modelId: "anthropic/claude-haiku-4-5",
617+
}),
618+
);
619+
expect(discoverAuthStorage).not.toHaveBeenCalled();
620+
expect(discoverModels).not.toHaveBeenCalled();
621+
});
622+
559623
it("applies provider overrides to bundled static catalog rows while skipping agent discovery", async () => {
560624
resolveBundledStaticCatalogModelMock.mockReturnValueOnce({
561625
provider: "mistral",

src/agents/model-fallback.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,6 +1716,36 @@ describe("runWithModelFallback", () => {
17161716
]);
17171717
});
17181718

1719+
it("normalizes prefixed Anthropic fallback candidates independently", () => {
1720+
const cfg = makeCfg({
1721+
agents: {
1722+
defaults: {
1723+
model: {
1724+
primary: "anthropic/claude-sonnet-4-6",
1725+
fallbacks: ["anthropic/claude-haiku-4-5", "anthropic/claude-opus-4-7"],
1726+
},
1727+
models: {
1728+
"anthropic/claude-sonnet-4-6": {},
1729+
"anthropic/claude-haiku-4-5": {},
1730+
"anthropic/claude-opus-4-7": {},
1731+
},
1732+
},
1733+
},
1734+
});
1735+
1736+
const candidates = testing.resolveFallbackCandidates({
1737+
cfg,
1738+
provider: "anthropic",
1739+
model: "anthropic/claude-sonnet-4-6",
1740+
});
1741+
1742+
expect(candidates).toEqual([
1743+
{ provider: "anthropic", model: "claude-sonnet-4-6" },
1744+
{ provider: "anthropic", model: "claude-haiku-4-5" },
1745+
{ provider: "anthropic", model: "claude-opus-4-7" },
1746+
]);
1747+
});
1748+
17191749
it("tries configured fallbacks before primary for override credential validation errors", async () => {
17201750
const cfg = makeCfg();
17211751
const run = createOverrideFailureRun({

src/agents/model-ref-shared.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ describe("normalizeStaticProviderModelId", () => {
3939
);
4040
});
4141

42+
it("strips native Anthropic provider prefixes from static catalog ids", () => {
43+
expect(normalizeStaticProviderModelId("anthropic", "anthropic/claude-haiku-4-5")).toBe(
44+
"claude-haiku-4-5",
45+
);
46+
});
47+
4248
it("uses supplied manifest normalization policies when provided", () => {
4349
const manifestPlugins = [
4450
{

0 commit comments

Comments
 (0)