From bc0b5a49f6e56b25677d88b34912fa3237fefe5f Mon Sep 17 00:00:00 2001 From: Toray Altas Date: Thu, 25 Dec 2025 18:50:32 -0500 Subject: [PATCH] Enhancement: Adds OpenRouter Base URL for OpenRouter embedder provider - Adds UI with standard text input within CodeIndexPopover - Modifies interface accordingly - Adds UI locales for english (other languages to come) - Updates the config-manager with boiler plate - Update openrouter embedder code to use baseURL if available, otherwise use default. --- src/services/code-index/config-manager.ts | 18 +++++++++-- .../code-index/embedders/openrouter.ts | 11 ++++++- src/services/code-index/interfaces/config.ts | 3 +- .../src/components/chat/CodeIndexPopover.tsx | 32 +++++++++++++++++++ webview-ui/src/i18n/locales/en/settings.json | 2 ++ 5 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts index e7f239e621..b77d09d4cc 100644 --- a/src/services/code-index/config-manager.ts +++ b/src/services/code-index/config-manager.ts @@ -21,7 +21,7 @@ export class CodeIndexConfigManager { private mistralOptions?: { apiKey: string } private vercelAiGatewayOptions?: { apiKey: string } private bedrockOptions?: { region: string; profile?: string } - private openRouterOptions?: { apiKey: string; specificProvider?: string } + private openRouterOptions?: { apiKey: string; specificProvider?: string; openRouterBaseUrl?: string } private qdrantUrl?: string = "http://localhost:6333" private qdrantApiKey?: string private searchMinScore?: number @@ -142,7 +142,11 @@ export class CodeIndexConfigManager { this.mistralOptions = mistralApiKey ? { apiKey: mistralApiKey } : undefined this.vercelAiGatewayOptions = vercelAiGatewayApiKey ? { apiKey: vercelAiGatewayApiKey } : undefined this.openRouterOptions = openRouterApiKey - ? { apiKey: openRouterApiKey, specificProvider: openRouterSpecificProvider || undefined } + ? { + apiKey: openRouterApiKey, + openRouterBaseUrl: codebaseIndexEmbedderBaseUrl, + specificProvider: openRouterSpecificProvider || undefined, + } : undefined // Set bedrockOptions if region is provided (profile is optional) this.bedrockOptions = bedrockRegion @@ -167,7 +171,7 @@ export class CodeIndexConfigManager { mistralOptions?: { apiKey: string } vercelAiGatewayOptions?: { apiKey: string } bedrockOptions?: { region: string; profile?: string } - openRouterOptions?: { apiKey: string } + openRouterOptions?: { apiKey: string; specificProvider?: string; openRouterBaseUrl?: string } qdrantUrl?: string qdrantApiKey?: string searchMinScore?: number @@ -191,6 +195,7 @@ export class CodeIndexConfigManager { bedrockRegion: this.bedrockOptions?.region ?? "", bedrockProfile: this.bedrockOptions?.profile ?? "", openRouterApiKey: this.openRouterOptions?.apiKey ?? "", + openRouterBaseUrl: this.openRouterOptions?.openRouterBaseUrl ?? "", openRouterSpecificProvider: this.openRouterOptions?.specificProvider ?? "", qdrantUrl: this.qdrantUrl ?? "", qdrantApiKey: this.qdrantApiKey ?? "", @@ -269,6 +274,7 @@ export class CodeIndexConfigManager { return isConfigured } else if (this.embedderProvider === "openrouter") { const apiKey = this.openRouterOptions?.apiKey + const openRouterBaseUrl = this.openRouterOptions?.openRouterBaseUrl const qdrantUrl = this.qdrantUrl const isConfigured = !!(apiKey && qdrantUrl) return isConfigured @@ -310,6 +316,7 @@ export class CodeIndexConfigManager { const prevBedrockRegion = prev?.bedrockRegion ?? "" const prevBedrockProfile = prev?.bedrockProfile ?? "" const prevOpenRouterApiKey = prev?.openRouterApiKey ?? "" + const prevOpenRouterBaseUrl = prev?.openRouterBaseUrl ?? "" const prevOpenRouterSpecificProvider = prev?.openRouterSpecificProvider ?? "" const prevQdrantUrl = prev?.qdrantUrl ?? "" const prevQdrantApiKey = prev?.qdrantApiKey ?? "" @@ -353,6 +360,7 @@ export class CodeIndexConfigManager { const currentBedrockProfile = this.bedrockOptions?.profile ?? "" const currentOpenRouterApiKey = this.openRouterOptions?.apiKey ?? "" const currentOpenRouterSpecificProvider = this.openRouterOptions?.specificProvider ?? "" + const currentOpenRouterBaseUrl = this.openRouterOptions?.openRouterBaseUrl ?? "" const currentQdrantUrl = this.qdrantUrl ?? "" const currentQdrantApiKey = this.qdrantApiKey ?? "" @@ -396,6 +404,10 @@ export class CodeIndexConfigManager { return true } + if (prevOpenRouterBaseUrl !== currentOpenRouterBaseUrl) { + return true + } + // Check for model dimension changes (generic for all providers) if (prevModelDimension !== currentModelDimension) { return true diff --git a/src/services/code-index/embedders/openrouter.ts b/src/services/code-index/embedders/openrouter.ts index 2ffdd7afb6..57da3dffbc 100644 --- a/src/services/code-index/embedders/openrouter.ts +++ b/src/services/code-index/embedders/openrouter.ts @@ -58,13 +58,22 @@ export class OpenRouterEmbedder implements IEmbedder { * @param apiKey The API key for authentication * @param modelId Optional model identifier (defaults to "openai/text-embedding-3-large") * @param maxItemTokens Optional maximum tokens per item (defaults to MAX_ITEM_TOKENS) + * @param openRouterBaseUrl Optional custom OpenRouter base URL * @param specificProvider Optional specific provider to route requests to */ - constructor(apiKey: string, modelId?: string, maxItemTokens?: number, specificProvider?: string) { + constructor( + apiKey: string, + modelId?: string, + maxItemTokens?: number, + openRouterBaseUrl?: string, + specificProvider?: string, + ) { if (!apiKey) { throw new Error(t("embeddings:validation.apiKeyRequired")) } + let baseUrl = openRouterBaseUrl || "https://openrouter.ai/api/v1" + this.apiKey = apiKey // Only set specificProvider if it's not the default value this.specificProvider = diff --git a/src/services/code-index/interfaces/config.ts b/src/services/code-index/interfaces/config.ts index f52f98aaa0..19bf2e4252 100644 --- a/src/services/code-index/interfaces/config.ts +++ b/src/services/code-index/interfaces/config.ts @@ -16,7 +16,7 @@ export interface CodeIndexConfig { mistralOptions?: { apiKey: string } vercelAiGatewayOptions?: { apiKey: string } bedrockOptions?: { region: string; profile?: string } - openRouterOptions?: { apiKey: string; specificProvider?: string } + openRouterOptions?: { apiKey: string; specificProvider?: string; openRouterBaseUrl?: string } qdrantUrl?: string qdrantApiKey?: string searchMinScore?: number @@ -42,6 +42,7 @@ export type PreviousConfigSnapshot = { bedrockRegion?: string bedrockProfile?: string openRouterApiKey?: string + openRouterBaseUrl?: string openRouterSpecificProvider?: string qdrantUrl?: string qdrantApiKey?: string diff --git a/webview-ui/src/components/chat/CodeIndexPopover.tsx b/webview-ui/src/components/chat/CodeIndexPopover.tsx index 368f0395ea..1a9ba4679c 100644 --- a/webview-ui/src/components/chat/CodeIndexPopover.tsx +++ b/webview-ui/src/components/chat/CodeIndexPopover.tsx @@ -53,6 +53,7 @@ import { // Default URLs for providers const DEFAULT_QDRANT_URL = "http://localhost:6333" const DEFAULT_OLLAMA_URL = "http://localhost:11434" +const DEFAULT_OPENROUTER_URL = "https://openrouter.ai/api/v1" interface CodeIndexPopoverProps { children: React.ReactNode @@ -1321,6 +1322,37 @@ export const CodeIndexPopover: React.FC = ({ {currentSettings.codebaseIndexEmbedderProvider === "openrouter" && ( <> +
+ + + updateSetting("codebaseIndexEmbedderBaseUrl", e.target.value) + } + onBlur={(e: any) => { + // Set default OpenRouter URL if field is empty + if (!e.target.value.trim()) { + e.target.value = DEFAULT_OPENROUTER_URL + updateSetting( + "codebaseIndexEmbedderBaseUrl", + DEFAULT_OPENROUTER_URL, + ) + } + }} + placeholder={t("settings:codeIndex.openRouterUrlPlaceholder")} + className={cn("w-full", { + "border-red-500": formErrors.codebaseIndexEmbedderBaseUrl, + })} + /> + {formErrors.codebaseIndexEmbedderBaseUrl && ( +

+ {formErrors.codebaseIndexEmbedderBaseUrl} +

+ )} +
+