From 7c262eaae7f9bd1b7d3e449bf7df0ba4d39de504 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 16 Jul 2026 04:36:15 -0700 Subject: [PATCH] refactor(agents): simplify provider key normalization (#108950) Co-authored-by: Pick-cat --- src/agents/models-config.merge.test.ts | 13 +++++++++++ src/agents/models-config.providers.keys.ts | 26 ++++++++++++---------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/agents/models-config.merge.test.ts b/src/agents/models-config.merge.test.ts index 0c942e58a06d..ab41d1f45266 100644 --- a/src/agents/models-config.merge.test.ts +++ b/src/agents/models-config.merge.test.ts @@ -242,6 +242,19 @@ describe("models-config merge helpers", () => { expect(merged.openai?.baseUrl).toBe("https://canonical.example/v1"); }); + it("keeps canonical providers at the canonical key's position", () => { + const merged = mergeProviders({ + explicit: { + OpenAI: createConfigProvider({ baseUrl: "https://variant.example/v1" }), + anthropic: createConfigProvider({ baseUrl: "https://anthropic.example/v1" }), + openai: createConfigProvider({ baseUrl: "https://canonical.example/v1" }), + }, + }); + + expect(Object.keys(merged)).toEqual(["anthropic", "openai"]); + expect(merged.openai?.baseUrl).toBe("https://canonical.example/v1"); + }); + it("keeps the later provider when no collision key uses canonical spelling", () => { const merged = mergeProviders({ explicit: { diff --git a/src/agents/models-config.providers.keys.ts b/src/agents/models-config.providers.keys.ts index 2f7f1a3fff23..d345224fbdf1 100644 --- a/src/agents/models-config.providers.keys.ts +++ b/src/agents/models-config.providers.keys.ts @@ -4,24 +4,26 @@ import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; export function normalizeProviderMapKeys( providers: Record | null | undefined, ): Record { - const entries = Object.entries(providers ?? {}); - const canonicalKeys = new Set(); - for (const [key] of entries) { - const providerKey = normalizeProviderId(key); - if (providerKey && key === providerKey) { - canonicalKeys.add(providerKey); - } - } - const normalized: Record = {}; - for (const [key, value] of entries) { + const canonicalKeys = new Set(); + for (const [key, value] of Object.entries(providers ?? {})) { const providerKey = normalizeProviderId(key); - if (!providerKey || (canonicalKeys.has(providerKey) && key !== providerKey)) { + if (!providerKey) { + continue; + } + if (key === providerKey) { + canonicalKeys.add(providerKey); + // A prior alias inserted this key at the alias's position. Reinsert it so + // canonical spelling also controls deterministic provider order. + delete normalized[providerKey]; + normalized[providerKey] = value; continue; } // Exact canonical spelling wins over aliases regardless of object order. // Without one, the later variant wins, matching existing trim-collision behavior. - normalized[providerKey] = value; + if (!canonicalKeys.has(providerKey)) { + normalized[providerKey] = value; + } } return normalized; }