diff --git a/sdk/packages/core/src/services/llms/provider-defaults.test.ts b/sdk/packages/core/src/services/llms/provider-defaults.test.ts index 1436116750..2f1e2293c1 100644 --- a/sdk/packages/core/src/services/llms/provider-defaults.test.ts +++ b/sdk/packages/core/src/services/llms/provider-defaults.test.ts @@ -309,6 +309,47 @@ describe("resolveProviderConfig", () => { expect(resolved?.knownModels?.["gpt-5.4"]).toBeUndefined(); }); + it("returns an empty authoritative LiteLLM model list without auth", async () => { + const fetchMock = vi.fn(); + vi.stubGlobal("fetch", fetchMock); + + const resolved = await resolveProviderConfig( + "litellm", + { failOnError: true, cacheTtlMs: 0 }, + { + providerId: "litellm", + modelId: "", + baseUrl: "http://localhost:4000/v1/", + }, + ); + + expect(fetchMock).not.toHaveBeenCalled(); + expect(resolved?.knownModels).toEqual({}); + expect(resolved?.knownModels?.["gpt-5.4"]).toBeUndefined(); + }); + + it("does not fall back to bundled LiteLLM models when private model fetch fails non-strictly", async () => { + const fetchMock = vi.fn( + async () => new Response('{"error":"unauthorized"}', { status: 401 }), + ); + vi.stubGlobal("fetch", fetchMock); + + const resolved = await resolveProviderConfig( + "litellm", + { failOnError: false, cacheTtlMs: 0 }, + { + providerId: "litellm", + modelId: "", + apiKey: "litellm-key", + baseUrl: "http://localhost:4000", + }, + ); + + expect(fetchMock).toHaveBeenCalled(); + expect(resolved?.knownModels).toEqual({}); + expect(resolved?.knownModels?.["gpt-5.4"]).toBeUndefined(); + }); + it("reports attempted path, auth header, status, and body for LiteLLM model fetch failures", async () => { const fetchMock = vi.fn( async () => new Response('{"error":"unauthorized"}', { status: 401 }), diff --git a/sdk/packages/core/src/services/llms/provider-defaults.ts b/sdk/packages/core/src/services/llms/provider-defaults.ts index ad0360183e..7293c4fee4 100644 --- a/sdk/packages/core/src/services/llms/provider-defaults.ts +++ b/sdk/packages/core/src/services/llms/provider-defaults.ts @@ -149,6 +149,10 @@ async function mergeKnownModels( publicModels: Record = {}, userKnownModels: Record = {}, ): Promise> { + if (providerId === "litellm") { + return Llms.sortModelsByReleaseDate(privateModels); + } + const generatedProviderModels = await loadGeneratedProviderModels(); const generatedKeys = Llms.resolveProviderModelCatalogKeys(providerId); const generated = Object.assign( @@ -171,16 +175,6 @@ async function mergeKnownModels( ...userKnownModels, }); } - // LiteLLM model access is configured by the user's proxy. When the proxy - // returns a private model list, treat it as the authoritative allowlist so - // the picker does not show bundled OpenAI-compatible catalog entries that the - // proxy may not actually expose. - if (providerId === "litellm" && Object.keys(privateModels).length > 0) { - return Llms.sortModelsByReleaseDate({ - ...privateModels, - ...userKnownModels, - }); - } if (providerId === "openai-codex") { return Llms.sortModelsByReleaseDate({ ...defaultKnownModels, @@ -937,6 +931,12 @@ export async function resolveProviderConfig( if (modelCatalog?.failOnError) { throw error; } + if (providerId === "litellm") { + return { + ...defaults, + knownModels: {}, + }; + } return defaults; } } diff --git a/sdk/packages/core/src/services/providers/local-provider-service.test.ts b/sdk/packages/core/src/services/providers/local-provider-service.test.ts index 662a4d4cfb..f6d085cf82 100644 --- a/sdk/packages/core/src/services/providers/local-provider-service.test.ts +++ b/sdk/packages/core/src/services/providers/local-provider-service.test.ts @@ -598,7 +598,7 @@ describe("addLocalProvider – capabilities", () => { expect(models[0].supportsReasoning).toBeFalsy(); }); - it("merges LiteLLM private models into the provider model listing when auth is configured", async () => { + it("uses LiteLLM private models as the authoritative provider model listing when auth is configured", async () => { manager.saveProviderSettings( { provider: "litellm", @@ -632,8 +632,11 @@ describe("addLocalProvider – capabilities", () => { manager.getProviderConfig("litellm"), ); - expect(models.map((model) => model.id)).toContain("private-proxy-model"); - expect(models.map((model) => model.id)).toContain("openai/gpt-4o-mini"); + expect(models.map((model) => model.id).sort()).toEqual([ + "openai/gpt-4o-mini", + "private-proxy-model", + ]); + expect(models.map((model) => model.id)).not.toContain("gpt-5.4"); expect( models.find((model) => model.id === "private-proxy-model"), ).toMatchObject({ @@ -641,6 +644,27 @@ describe("addLocalProvider – capabilities", () => { supportsReasoning: true, }); }); + + it("uses an empty LiteLLM model list when no private model list is fetched", async () => { + manager.saveProviderSettings( + { + provider: "litellm", + baseUrl: "http://localhost:4010", + model: "gpt-4o", + }, + { setLastUsed: false }, + ); + const fetchMock = vi.fn(); + vi.stubGlobal("fetch", fetchMock); + + const { models } = await getLocalProviderModels( + "litellm", + manager.getProviderConfig("litellm"), + ); + + expect(fetchMock).not.toHaveBeenCalled(); + expect(models).toEqual([]); + }); }); // =========================================================================== diff --git a/sdk/packages/core/src/services/providers/local-provider-service.ts b/sdk/packages/core/src/services/providers/local-provider-service.ts index 86d4c4b732..981134023a 100644 --- a/sdk/packages/core/src/services/providers/local-provider-service.ts +++ b/sdk/packages/core/src/services/providers/local-provider-service.ts @@ -136,6 +136,10 @@ async function resolveProviderModelMap( config, ); + if (providerId === "litellm" && resolved?.knownModels) { + return resolved.knownModels; + } + return resolved?.knownModels ? { ...registeredModels,