From 2ba16b7bada9e42eb2585b9072bc3cb791e4d47b Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:20:31 -0400 Subject: [PATCH] feat: add Ollama API key support for Turbo mode (#7425) * feat: add Ollama API key support for Turbo mode - Add ollamaApiKey field to ProviderSettings schema - Add ollamaApiKey to SECRET_STATE_KEYS for secure storage - Update Ollama and NativeOllama providers to use API key for authentication - Add UI field for Ollama API key (shown when custom base URL is provided) - Add test coverage for API key functionality This enables users to use Ollama Turbo with datacenter-grade hardware by providing an API key for authenticated Ollama instances or cloud services. * fix: use VSCodeTextField for Ollama API key field Remove non-existent ApiKeyField import and use standard VSCodeTextField with password type, matching other provider implementations * Add missing translation keys for Ollama API key support - Add providers.ollama.apiKey and providers.ollama.apiKeyHelp to all 18 language files - Support for authenticated Ollama instances and cloud services - Relates to PR #7425 * refactor: improve type safety for Ollama client configuration - Replace 'any' type with proper OllamaOptions (Config) type - Import Config type from ollama package for better type checking --------- Co-authored-by: Roo Code Co-authored-by: Daniel Riccio --- packages/types/src/global-settings.ts | 1 + packages/types/src/provider-settings.ts | 1 + src/api/providers/__tests__/ollama.spec.ts | 11 +++++++++++ src/api/providers/native-ollama.ts | 15 ++++++++++++--- src/api/providers/ollama.ts | 12 +++++++++++- .../src/components/settings/providers/Ollama.tsx | 13 +++++++++++++ webview-ui/src/i18n/locales/ca/settings.json | 2 ++ webview-ui/src/i18n/locales/de/settings.json | 2 ++ webview-ui/src/i18n/locales/en/settings.json | 2 ++ webview-ui/src/i18n/locales/es/settings.json | 2 ++ webview-ui/src/i18n/locales/fr/settings.json | 2 ++ webview-ui/src/i18n/locales/hi/settings.json | 2 ++ webview-ui/src/i18n/locales/id/settings.json | 2 ++ webview-ui/src/i18n/locales/it/settings.json | 2 ++ webview-ui/src/i18n/locales/ja/settings.json | 2 ++ webview-ui/src/i18n/locales/ko/settings.json | 2 ++ webview-ui/src/i18n/locales/nl/settings.json | 2 ++ webview-ui/src/i18n/locales/pl/settings.json | 2 ++ webview-ui/src/i18n/locales/pt-BR/settings.json | 2 ++ webview-ui/src/i18n/locales/ru/settings.json | 2 ++ webview-ui/src/i18n/locales/tr/settings.json | 2 ++ webview-ui/src/i18n/locales/vi/settings.json | 2 ++ webview-ui/src/i18n/locales/zh-CN/settings.json | 2 ++ webview-ui/src/i18n/locales/zh-TW/settings.json | 2 ++ 24 files changed, 85 insertions(+), 4 deletions(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index b05edc59fc..e17e56829b 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -179,6 +179,7 @@ export const SECRET_STATE_KEYS = [ "awsSecretKey", "awsSessionToken", "openAiApiKey", + "ollamaApiKey", "geminiApiKey", "openAiNativeApiKey", "cerebrasApiKey", diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 45d4a7e780..090dfe6693 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -188,6 +188,7 @@ const openAiSchema = baseProviderSettingsSchema.extend({ const ollamaSchema = baseProviderSettingsSchema.extend({ ollamaModelId: z.string().optional(), ollamaBaseUrl: z.string().optional(), + ollamaApiKey: z.string().optional(), }) const vsCodeLmSchema = baseProviderSettingsSchema.extend({ diff --git a/src/api/providers/__tests__/ollama.spec.ts b/src/api/providers/__tests__/ollama.spec.ts index fa98a56e8d..bbd43d3b45 100644 --- a/src/api/providers/__tests__/ollama.spec.ts +++ b/src/api/providers/__tests__/ollama.spec.ts @@ -92,6 +92,17 @@ describe("OllamaHandler", () => { }) expect(handlerWithoutUrl).toBeInstanceOf(OllamaHandler) }) + + it("should use API key when provided", () => { + const handlerWithApiKey = new OllamaHandler({ + apiModelId: "llama2", + ollamaModelId: "llama2", + ollamaBaseUrl: "https://ollama.com", + ollamaApiKey: "test-api-key", + }) + expect(handlerWithApiKey).toBeInstanceOf(OllamaHandler) + // The API key will be used in the Authorization header + }) }) describe("createMessage", () => { diff --git a/src/api/providers/native-ollama.ts b/src/api/providers/native-ollama.ts index 8ab4ebe2e1..06c1c33d23 100644 --- a/src/api/providers/native-ollama.ts +++ b/src/api/providers/native-ollama.ts @@ -1,5 +1,5 @@ import { Anthropic } from "@anthropic-ai/sdk" -import { Message, Ollama } from "ollama" +import { Message, Ollama, type Config as OllamaOptions } from "ollama" import { ModelInfo, openAiModelInfoSaneDefaults, DEEP_SEEK_DEFAULT_TEMPERATURE } from "@roo-code/types" import { ApiStream } from "../transform/stream" import { BaseProvider } from "./base-provider" @@ -140,10 +140,19 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio private ensureClient(): Ollama { if (!this.client) { try { - this.client = new Ollama({ + const clientOptions: OllamaOptions = { host: this.options.ollamaBaseUrl || "http://localhost:11434", // Note: The ollama npm package handles timeouts internally - }) + } + + // Add API key if provided (for Ollama cloud or authenticated instances) + if (this.options.ollamaApiKey) { + clientOptions.headers = { + Authorization: `Bearer ${this.options.ollamaApiKey}`, + } + } + + this.client = new Ollama(clientOptions) } catch (error: any) { throw new Error(`Error creating Ollama client: ${error.message}`) } diff --git a/src/api/providers/ollama.ts b/src/api/providers/ollama.ts index 54666be58d..75895908e9 100644 --- a/src/api/providers/ollama.ts +++ b/src/api/providers/ollama.ts @@ -25,10 +25,20 @@ export class OllamaHandler extends BaseProvider implements SingleCompletionHandl super() this.options = options + // Use the API key if provided (for Ollama cloud or authenticated instances) + // Otherwise use "ollama" as a placeholder for local instances + const apiKey = this.options.ollamaApiKey || "ollama" + + const headers: Record = {} + if (this.options.ollamaApiKey) { + headers["Authorization"] = `Bearer ${this.options.ollamaApiKey}` + } + this.client = new OpenAI({ baseURL: (this.options.ollamaBaseUrl || "http://localhost:11434") + "/v1", - apiKey: "ollama", + apiKey: apiKey, timeout: getApiRequestTimeout(), + defaultHeaders: headers, }) } diff --git a/webview-ui/src/components/settings/providers/Ollama.tsx b/webview-ui/src/components/settings/providers/Ollama.tsx index 263c3892f2..b09ecad5d6 100644 --- a/webview-ui/src/components/settings/providers/Ollama.tsx +++ b/webview-ui/src/components/settings/providers/Ollama.tsx @@ -86,6 +86,19 @@ export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaPro className="w-full"> + {apiConfiguration?.ollamaBaseUrl && ( + + +
+ {t("settings:providers.ollama.apiKeyHelp")} +
+
+ )}