From b7841d8d161e9ad71bea1c88f794fbbd31cd82f1 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Thu, 26 Feb 2026 03:30:12 -0500 Subject: [PATCH] test(models): cover merge auth/baseUrl precedence --- changelog/fragments/pr-27293.md | 1 + ...ssing-provider-apikey-from-env-var.test.ts | 90 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 changelog/fragments/pr-27293.md diff --git a/changelog/fragments/pr-27293.md b/changelog/fragments/pr-27293.md new file mode 100644 index 00000000000..b18f69fc3e1 --- /dev/null +++ b/changelog/fragments/pr-27293.md @@ -0,0 +1 @@ +- Fix models merge to preserve agent-level provider `apiKey` and `baseUrl` when present (#27293) (thanks @Sid-Qin) diff --git a/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts b/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts index c26142158e8..1d4148b496b 100644 --- a/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts +++ b/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts @@ -134,6 +134,96 @@ describe("models-config", () => { }); }); + it("preserves non-empty agent apiKey/baseUrl for matching providers in merge mode", async () => { + await withTempHome(async () => { + const agentDir = resolveOpenClawAgentDir(); + await fs.mkdir(agentDir, { recursive: true }); + await fs.writeFile( + path.join(agentDir, "models.json"), + JSON.stringify( + { + providers: { + custom: { + baseUrl: "https://agent.example/v1", + apiKey: "AGENT_KEY", + api: "openai-responses", + models: [{ id: "agent-model", name: "Agent model", input: ["text"] }], + }, + }, + }, + null, + 2, + ), + "utf8", + ); + + await ensureOpenClawModelsJson({ + models: { + mode: "merge", + providers: { + custom: { + baseUrl: "https://config.example/v1", + apiKey: "CONFIG_KEY", + api: "openai-responses", + models: [{ id: "config-model", name: "Config model", input: ["text"] }], + }, + }, + }, + }); + + const parsed = await readGeneratedModelsJson<{ + providers: Record; + }>(); + expect(parsed.providers.custom?.apiKey).toBe("AGENT_KEY"); + expect(parsed.providers.custom?.baseUrl).toBe("https://agent.example/v1"); + }); + }); + + it("uses config apiKey/baseUrl when existing agent values are empty", async () => { + await withTempHome(async () => { + const agentDir = resolveOpenClawAgentDir(); + await fs.mkdir(agentDir, { recursive: true }); + await fs.writeFile( + path.join(agentDir, "models.json"), + JSON.stringify( + { + providers: { + custom: { + baseUrl: "", + apiKey: "", + api: "openai-responses", + models: [{ id: "agent-model", name: "Agent model", input: ["text"] }], + }, + }, + }, + null, + 2, + ), + "utf8", + ); + + await ensureOpenClawModelsJson({ + models: { + mode: "merge", + providers: { + custom: { + baseUrl: "https://config.example/v1", + apiKey: "CONFIG_KEY", + api: "openai-responses", + models: [{ id: "config-model", name: "Config model", input: ["text"] }], + }, + }, + }, + }); + + const parsed = await readGeneratedModelsJson<{ + providers: Record; + }>(); + expect(parsed.providers.custom?.apiKey).toBe("CONFIG_KEY"); + expect(parsed.providers.custom?.baseUrl).toBe("https://config.example/v1"); + }); + }); + it("refreshes stale explicit moonshot model capabilities from implicit catalog", async () => { await withTempHome(async () => { const prevKey = process.env.MOONSHOT_API_KEY;