From f0b1d13767b16f164be7b08cbc130874bf899d9f Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Mon, 24 Nov 2025 11:06:14 -0500 Subject: [PATCH] fix: flush LiteLLM cache when credentials change on refresh When clicking 'Refresh Models' in LiteLLM settings after changing the API key or base URL, the model list wasn't updating because the cache wasn't being invalidated. The fix detects when explicit credentials are provided via the 'Refresh Models' button (message.values) and flushes the cache before fetching, while keeping the cache for automatic/debounced refreshes for better performance. This is consistent with how Ollama and LMStudio handle explicit model refreshes. Fixes #reported-by-user --- ...webviewMessageHandler.routerModels.spec.ts | 59 ++++++++++++++++++- src/core/webview/webviewMessageHandler.ts | 6 ++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/core/webview/__tests__/webviewMessageHandler.routerModels.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.routerModels.spec.ts index 746a013f0f1..12ffb025883 100644 --- a/src/core/webview/__tests__/webviewMessageHandler.routerModels.spec.ts +++ b/src/core/webview/__tests__/webviewMessageHandler.routerModels.spec.ts @@ -36,9 +36,10 @@ vi.mock("vscode", () => ({ // Mock modelCache getModels/flushModels used by the handler const getModelsMock = vi.fn() +const flushModelsMock = vi.fn() vi.mock("../../../api/providers/fetchers/modelCache", () => ({ getModels: (...args: any[]) => getModelsMock(...args), - flushModels: vi.fn(), + flushModels: (...args: any[]) => flushModelsMock(...args), })) describe("webviewMessageHandler - requestRouterModels provider filter", () => { @@ -164,4 +165,60 @@ describe("webviewMessageHandler - requestRouterModels provider filter", () => { const providersCalled = getModelsMock.mock.calls.map((c: any[]) => c[0]?.provider) expect(providersCalled).toEqual(["openrouter"]) }) + + it("flushes cache when LiteLLM credentials are provided in message values", async () => { + // Provide LiteLLM credentials via message.values (simulating Refresh Models button) + await webviewMessageHandler( + mockProvider as any, + { + type: "requestRouterModels", + values: { + litellmApiKey: "test-api-key", + litellmBaseUrl: "http://localhost:4000", + }, + } as any, + ) + + // flushModels should have been called for litellm with refresh=true + expect(flushModelsMock).toHaveBeenCalledWith("litellm", true) + + // getModels should have been called with the provided credentials + const litellmCalls = getModelsMock.mock.calls.filter((c: any[]) => c[0]?.provider === "litellm") + expect(litellmCalls.length).toBe(1) + expect(litellmCalls[0][0]).toEqual({ + provider: "litellm", + apiKey: "test-api-key", + baseUrl: "http://localhost:4000", + }) + }) + + it("does not flush cache when using stored LiteLLM credentials", async () => { + // Provide stored credentials via apiConfiguration + mockProvider.getState.mockResolvedValue({ + apiConfiguration: { + litellmApiKey: "stored-api-key", + litellmBaseUrl: "http://stored:4000", + }, + }) + + await webviewMessageHandler( + mockProvider as any, + { + type: "requestRouterModels", + } as any, + ) + + // flushModels should NOT have been called for litellm + const litellmFlushCalls = flushModelsMock.mock.calls.filter((c: any[]) => c[0] === "litellm") + expect(litellmFlushCalls.length).toBe(0) + + // getModels should still have been called with stored credentials + const litellmCalls = getModelsMock.mock.calls.filter((c: any[]) => c[0]?.provider === "litellm") + expect(litellmCalls.length).toBe(1) + expect(litellmCalls[0][0]).toEqual({ + provider: "litellm", + apiKey: "stored-api-key", + baseUrl: "http://stored:4000", + }) + }) }) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index c769fc05fd7..7ccee8c100d 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -882,6 +882,12 @@ export const webviewMessageHandler = async ( const litellmBaseUrl = apiConfiguration.litellmBaseUrl || message?.values?.litellmBaseUrl if (litellmApiKey && litellmBaseUrl) { + // If explicit credentials are provided in message.values (from Refresh Models button), + // flush the cache first to ensure we fetch fresh data with the new credentials + if (message?.values?.litellmApiKey || message?.values?.litellmBaseUrl) { + await flushModels("litellm", true) + } + candidates.push({ key: "litellm", options: { provider: "litellm", apiKey: litellmApiKey, baseUrl: litellmBaseUrl },