From def3d0fac060a61fee3f413481fb639f6b60a881 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 13 Mar 2026 10:20:41 +0000 Subject: [PATCH] feat: make LiteLLM API key optional for self-hosted instances The LiteLLM provider now only requires a Base URL. The API Key field is optional, allowing users with self-hosted or local LiteLLM proxies to use the extension without entering a dummy key. Changes: - validate.ts: require only litellmBaseUrl (not litellmApiKey) for LiteLLM - LiteLLM.tsx: allow Refresh Models with just a Base URL - shared/api.ts: make apiKey optional in litellm type - webviewMessageHandler.ts: fetch models when baseUrl is set (no key needed) - litellm.ts fetcher: accept undefined apiKey, skip Auth header when absent - lite-llm.ts handler: use empty string instead of "dummy-key" fallback - Added "baseUrl" validation translation key - Added tests for optional API key scenarios Fixes #11918 --- .../fetchers/__tests__/litellm.spec.ts | 20 ++++++++++++ src/api/providers/fetchers/litellm.ts | 2 +- src/api/providers/lite-llm.ts | 2 +- src/core/webview/webviewMessageHandler.ts | 15 ++++++--- src/shared/api.ts | 2 +- .../components/settings/providers/LiteLLM.tsx | 6 ++-- webview-ui/src/i18n/locales/en/settings.json | 1 + .../src/utils/__tests__/validate.spec.ts | 32 +++++++++++++++++++ webview-ui/src/utils/validate.ts | 4 +-- 9 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/api/providers/fetchers/__tests__/litellm.spec.ts b/src/api/providers/fetchers/__tests__/litellm.spec.ts index c05cda8839..212dc6a7c6 100644 --- a/src/api/providers/fetchers/__tests__/litellm.spec.ts +++ b/src/api/providers/fetchers/__tests__/litellm.spec.ts @@ -262,6 +262,26 @@ describe("getLiteLLMModels", () => { }) }) + it("makes request without authorization header when API key is undefined", async () => { + const mockResponse = { + data: { + data: [], + }, + } + + mockedAxios.get.mockResolvedValue(mockResponse) + + await getLiteLLMModels(undefined, "http://localhost:4000") + + expect(mockedAxios.get).toHaveBeenCalledWith("http://localhost:4000/v1/model/info", { + headers: { + "Content-Type": "application/json", + ...DEFAULT_HEADERS, + }, + timeout: 5000, + }) + }) + it("handles computer use models correctly", async () => { const mockResponse = { data: { diff --git a/src/api/providers/fetchers/litellm.ts b/src/api/providers/fetchers/litellm.ts index de895d01fb..639db46fb5 100644 --- a/src/api/providers/fetchers/litellm.ts +++ b/src/api/providers/fetchers/litellm.ts @@ -11,7 +11,7 @@ import { DEFAULT_HEADERS } from "../constants" * @returns A promise that resolves to a record of model IDs to model info * @throws Will throw an error if the request fails or the response is not as expected. */ -export async function getLiteLLMModels(apiKey: string, baseUrl: string): Promise { +export async function getLiteLLMModels(apiKey: string | undefined, baseUrl: string): Promise { try { const headers: Record = { "Content-Type": "application/json", diff --git a/src/api/providers/lite-llm.ts b/src/api/providers/lite-llm.ts index cf8d16a112..e195a4da3b 100644 --- a/src/api/providers/lite-llm.ts +++ b/src/api/providers/lite-llm.ts @@ -26,7 +26,7 @@ export class LiteLLMHandler extends RouterProvider implements SingleCompletionHa options, name: "litellm", baseURL: `${options.litellmBaseUrl || "http://localhost:4000"}`, - apiKey: options.litellmApiKey || "dummy-key", + apiKey: options.litellmApiKey || "", modelId: options.litellmModelId, defaultModelId: litellmDefaultModelId, defaultModelInfo: litellmDefaultModelInfo, diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index d27fd6bec0..02345f5b48 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1001,20 +1001,27 @@ export const webviewMessageHandler = async ( }, ] - // LiteLLM is conditional on baseUrl+apiKey + // LiteLLM is conditional on baseUrl (apiKey is optional for self-hosted instances) const litellmApiKey = apiConfiguration.litellmApiKey || message?.values?.litellmApiKey const litellmBaseUrl = apiConfiguration.litellmBaseUrl || message?.values?.litellmBaseUrl - if (litellmApiKey && litellmBaseUrl) { + if (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({ provider: "litellm", apiKey: litellmApiKey, baseUrl: litellmBaseUrl }, true) + await flushModels( + { provider: "litellm", apiKey: litellmApiKey || "", baseUrl: litellmBaseUrl }, + true, + ) } candidates.push({ key: "litellm", - options: { provider: "litellm", apiKey: litellmApiKey, baseUrl: litellmBaseUrl }, + options: { + provider: "litellm", + apiKey: litellmApiKey || "", + baseUrl: litellmBaseUrl, + }, }) } diff --git a/src/shared/api.ts b/src/shared/api.ts index 52af6b2072..4721dbad63 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -171,7 +171,7 @@ type CommonFetchParams = { const dynamicProviderExtras = { openrouter: {} as {}, // eslint-disable-line @typescript-eslint/no-empty-object-type "vercel-ai-gateway": {} as {}, // eslint-disable-line @typescript-eslint/no-empty-object-type - litellm: {} as { apiKey: string; baseUrl: string }, + litellm: {} as { apiKey?: string; baseUrl: string }, requesty: {} as { apiKey?: string; baseUrl?: string }, unbound: {} as { apiKey?: string }, ollama: {} as {}, // eslint-disable-line @typescript-eslint/no-empty-object-type diff --git a/webview-ui/src/components/settings/providers/LiteLLM.tsx b/webview-ui/src/components/settings/providers/LiteLLM.tsx index 38ae1f3a96..ea30872ca3 100644 --- a/webview-ui/src/components/settings/providers/LiteLLM.tsx +++ b/webview-ui/src/components/settings/providers/LiteLLM.tsx @@ -86,7 +86,7 @@ export const LiteLLM = ({ const key = apiConfiguration.litellmApiKey const url = apiConfiguration.litellmBaseUrl - if (!key || !url) { + if (!url) { setRefreshStatus("error") setRefreshError(t("settings:providers.refreshModels.missingConfig")) return @@ -121,9 +121,7 @@ export const LiteLLM = ({