Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/services/code-index/config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 ?? "",
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 ?? ""
Expand Down Expand Up @@ -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 ?? ""

Expand Down Expand Up @@ -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
Expand Down
14 changes: 11 additions & 3 deletions src/services/code-index/embedders/openrouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export class OpenRouterEmbedder implements IEmbedder {
private readonly defaultModelId: string
private readonly apiKey: string
private readonly maxItemTokens: number
private readonly baseUrl: string = "https://openrouter.ai/api/v1"
private readonly specificProvider?: string

// Global rate limiting state shared across all instances
Expand All @@ -58,13 +57,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,
specificProvider?: string,
openRouterBaseUrl?: string,
) {
if (!apiKey) {
throw new Error(t("embeddings:validation.apiKeyRequired"))
}

const baseUrl = openRouterBaseUrl || "https://openrouter.ai/api/v1"

this.apiKey = apiKey
// Only set specificProvider if it's not the default value
this.specificProvider =
Expand All @@ -73,7 +81,7 @@ export class OpenRouterEmbedder implements IEmbedder {
// Wrap OpenAI client creation to handle invalid API key characters
try {
this.embeddingsClient = new OpenAI({
baseURL: this.baseUrl,
baseURL: baseUrl,
apiKey: apiKey,
defaultHeaders: {
"HTTP-Referer": "https://github.com/RooCodeInc/Roo-Code",
Expand Down
3 changes: 2 additions & 1 deletion src/services/code-index/interfaces/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -42,6 +42,7 @@ export type PreviousConfigSnapshot = {
bedrockRegion?: string
bedrockProfile?: string
openRouterApiKey?: string
openRouterBaseUrl?: string
openRouterSpecificProvider?: string
qdrantUrl?: string
qdrantApiKey?: string
Expand Down
32 changes: 32 additions & 0 deletions webview-ui/src/components/chat/CodeIndexPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1321,6 +1322,37 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({

{currentSettings.codebaseIndexEmbedderProvider === "openrouter" && (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new UI block for OpenRouter base URL reuses the codebaseIndexEmbedderBaseUrl state, which is also used by Ollama. Ensure that sharing this field between providers is intentional to prevent accidental overwrites when switching providers.

<>
<div className="space-y-2">
<label className="text-sm font-medium">
{t("settings:codeIndex.openRouterBaseUrlLabel")}
</label>
<VSCodeTextField
value={currentSettings.codebaseIndexEmbedderBaseUrl || ""}
onInput={(e: any) =>
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 && (
<p className="text-xs text-vscode-errorForeground mt-1 mb-0">
{formErrors.codebaseIndexEmbedderBaseUrl}
</p>
)}
</div>

<div className="space-y-2">
<label className="text-sm font-medium">
{t("settings:codeIndex.openRouterApiKeyLabel")}
Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@
"openRouterApiKeyPlaceholder": "Enter your OpenRouter API key",
"openRouterProviderRoutingLabel": "OpenRouter Provider Routing",
"openRouterProviderRoutingDescription": "OpenRouter routes requests to the best available providers for your embedding model. By default, requests are load balanced across the top providers to maximize uptime. However, you can choose a specific provider to use for this model.",
"openRouterBaseUrlLabel": "OpenRouter Base URL",
"openRouterUrlPlaceholder": "https://openrouter.ai/api/v1",
"openaiCompatibleProvider": "OpenAI Compatible",
"openAiKeyLabel": "OpenAI API Key",
"openAiKeyPlaceholder": "Enter your OpenAI API key",
Expand Down
Loading