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 9b6318153d3..ef03fb3863b 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 @@ -65,7 +65,7 @@ async function runCustomProviderMergeTest(params: { baseUrl: string; apiKey: string; api: string; - models: Array<{ id: string; name: string; input: string[] }>; + models: Array<{ id: string; name: string; input: string[]; api?: string }>; }; existingProviderKey?: string; configProviderKey?: string; @@ -261,6 +261,28 @@ describe("models-config", () => { }); }); + it("replaces stale merged baseUrl when only model-level apis change", async () => { + await withTempHome(async () => { + const parsed = await runCustomProviderMergeTest({ + seedProvider: { + baseUrl: "https://agent.example/v1", + apiKey: "AGENT_KEY", // pragma: allowlist secret + api: "", + models: [ + { + id: "agent-model", + name: "Agent model", + input: ["text"], + api: "openai-completions", + }, + ], + }, + }); + expect(parsed.providers.custom?.apiKey).toBe("AGENT_KEY"); + expect(parsed.providers.custom?.baseUrl).toBe("https://config.example/v1"); + }); + }); + it("replaces stale merged apiKey when provider is SecretRef-managed in current config", async () => { await withTempHome(async () => { await writeAgentModelsJson({ diff --git a/src/agents/models-config.ts b/src/agents/models-config.ts index 7e96e685fe9..db7e3a5f1a7 100644 --- a/src/agents/models-config.ts +++ b/src/agents/models-config.ts @@ -178,6 +178,33 @@ function resolveProviderApi(entry: { api?: unknown } | undefined): string | unde return api || undefined; } +function resolveModelApiSurface(entry: { models?: unknown } | undefined): string | undefined { + if (!Array.isArray(entry?.models)) { + return undefined; + } + + const apis = entry.models + .flatMap((model) => { + if (!model || typeof model !== "object") { + return []; + } + const api = (model as { api?: unknown }).api; + return typeof api === "string" && api.trim() ? [api.trim()] : []; + }) + .toSorted(); + + if (apis.length === 0) { + return undefined; + } + return JSON.stringify(apis); +} + +function resolveProviderApiSurface( + entry: ({ api?: unknown; models?: unknown } & Record) | undefined, +): string | undefined { + return resolveProviderApi(entry) ?? resolveModelApiSurface(entry); +} + function shouldPreserveExistingApiKey(params: { providerKey: string; existing: ExistingProviderConfig; @@ -207,8 +234,8 @@ function shouldPreserveExistingBaseUrl(params: { return false; } - const existingApi = resolveProviderApi(existing); - const nextApi = resolveProviderApi(nextEntry); + const existingApi = resolveProviderApiSurface(existing); + const nextApi = resolveProviderApiSurface(nextEntry); return !existingApi || !nextApi || existingApi === nextApi; }