diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 02e22c7feb..d2433a044d 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -132,12 +132,17 @@ const glamaSchema = baseProviderSettingsSchema.extend({ glamaApiKey: z.string().optional(), }) +/** + * Maximum number of OpenRouter providers that can be configured for failover + */ +export const MAX_OPENROUTER_PROVIDERS = 4 + const openRouterSchema = baseProviderSettingsSchema.extend({ openRouterApiKey: z.string().optional(), openRouterModelId: z.string().optional(), openRouterBaseUrl: z.string().optional(), openRouterSpecificProvider: z.string().optional(), // Keep for backward compatibility - openRouterProviders: z.array(z.string()).max(4).optional(), // New multi-provider support + openRouterProviders: z.array(z.string()).max(MAX_OPENROUTER_PROVIDERS).optional(), // New multi-provider support openRouterFailoverEnabled: z.boolean().optional(), // Enable automatic failover openRouterUseMiddleOutTransform: z.boolean().optional(), }) diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 0b83aa682e..ed1b2de337 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -75,7 +75,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH } /** - * Get the list of providers to use, supporting both new multi-provider and legacy single provider config + * Get the list of providers to use for requests, supporting both multi-provider and legacy configurations + * @returns Array of provider names in priority order */ private getProvidersToUse(): string[] { // New multi-provider configuration takes precedence @@ -99,6 +100,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH /** * Check if an error should trigger failover to the next provider + * @param error - The error object to check + * @returns true if the error is eligible for failover, false otherwise */ private shouldFailover(error: any): boolean { if (!error) return false @@ -131,6 +134,16 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH /** * Create completion parameters for a specific provider attempt + * @param modelId - The model ID to use + * @param maxTokens - Maximum tokens to generate + * @param temperature - Temperature for generation + * @param topP - Top-p sampling parameter + * @param openAiMessages - Messages in OpenAI format + * @param transforms - OpenRouter transforms to apply + * @param reasoning - Reasoning parameters for the model + * @param providers - List of all available providers + * @param providerIndex - Current provider index being attempted + * @returns OpenRouter chat completion parameters */ private createCompletionParams( modelId: string, @@ -244,9 +257,11 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH for (let providerIndex = 0; providerIndex < providers.length; providerIndex++) { try { const currentProvider = providers[providerIndex] - console.log( - `[OpenRouter] Attempting request with provider: ${currentProvider} (${providerIndex + 1}/${providers.length})`, - ) + if (process.env.NODE_ENV === "development") { + console.log( + `[OpenRouter] Attempting request with provider: ${currentProvider} (${providerIndex + 1}/${providers.length})`, + ) + } // Create completion parameters for this provider attempt const completionParams = this.createCompletionParams( @@ -303,29 +318,41 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH } // Success - no need to try additional providers - console.log(`[OpenRouter] Request succeeded with provider: ${currentProvider}`) + if (process.env.NODE_ENV === "development") { + console.log(`[OpenRouter] Request succeeded with provider: ${currentProvider}`) + } return } catch (error) { lastError = error const isLastProvider = providerIndex >= providers.length - 1 if (this.shouldFailover(error) && !isLastProvider) { - console.warn( - `[OpenRouter] Provider ${providers[providerIndex]} failed with error: ${error.message}. Trying next provider...`, - ) + if (process.env.NODE_ENV === "development") { + console.warn( + `[OpenRouter] Provider ${providers[providerIndex]} failed with error: ${error.message}. Trying next provider...`, + ) + } continue // Try next provider } else { // Either not a failover-eligible error, or this was the last provider - console.error( - `[OpenRouter] ${isLastProvider ? "All providers failed" : "Non-failover error"} with provider ${providers[providerIndex]}: ${error.message}`, - ) + if (process.env.NODE_ENV === "development") { + console.error( + `[OpenRouter] ${isLastProvider ? "All providers failed" : "Non-failover error"} with provider ${providers[providerIndex]}: ${error.message}`, + ) + } throw error } } } // This should never be reached, but just in case - throw lastError || new Error("All OpenRouter providers failed") + const providersSummary = providers.join(", ") + throw ( + lastError || + new Error( + `All OpenRouter providers failed (tried: ${providersSummary}). Last error: ${lastError?.message || "Unknown"}`, + ) + ) } /** @@ -465,9 +492,11 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH for (let providerIndex = 0; providerIndex < providers.length; providerIndex++) { try { const currentProvider = providers[providerIndex] - console.log( - `[OpenRouter] Attempting completePrompt with provider: ${currentProvider} (${providerIndex + 1}/${providers.length})`, - ) + if (process.env.NODE_ENV === "development") { + console.log( + `[OpenRouter] Attempting completePrompt with provider: ${currentProvider} (${providerIndex + 1}/${providers.length})`, + ) + } const completionParams: OpenRouterChatCompletionParams = { model: modelId, @@ -493,29 +522,41 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH } const completion = response as OpenAI.Chat.ChatCompletion - console.log(`[OpenRouter] completePrompt succeeded with provider: ${currentProvider}`) + if (process.env.NODE_ENV === "development") { + console.log(`[OpenRouter] completePrompt succeeded with provider: ${currentProvider}`) + } return completion.choices[0]?.message?.content || "" } catch (error) { lastError = error const isLastProvider = providerIndex >= providers.length - 1 if (this.shouldFailover(error) && !isLastProvider) { - console.warn( - `[OpenRouter] Provider ${providers[providerIndex]} failed in completePrompt: ${error.message}. Trying next provider...`, - ) + if (process.env.NODE_ENV === "development") { + console.warn( + `[OpenRouter] Provider ${providers[providerIndex]} failed in completePrompt: ${error.message}. Trying next provider...`, + ) + } continue // Try next provider } else { // Either not a failover-eligible error, or this was the last provider - console.error( - `[OpenRouter] ${isLastProvider ? "All providers failed" : "Non-failover error"} in completePrompt with provider ${providers[providerIndex]}: ${error.message}`, - ) + if (process.env.NODE_ENV === "development") { + console.error( + `[OpenRouter] ${isLastProvider ? "All providers failed" : "Non-failover error"} in completePrompt with provider ${providers[providerIndex]}: ${error.message}`, + ) + } throw error } } } // This should never be reached, but just in case - throw lastError || new Error("All OpenRouter providers failed in completePrompt") + const providersSummary = providers.join(", ") + throw ( + lastError || + new Error( + `All OpenRouter providers failed in completePrompt (tried: ${providersSummary}). Last error: ${lastError?.message || "Unknown"}`, + ) + ) } /** diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 995fefcde2..5d9d0522b3 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -733,7 +733,7 @@ const ApiOptions = ({ {apiConfiguration?.openRouterFailoverEnabled ? (
{[0, 1, 2, 3].map((index) => { const currentProviders = apiConfiguration?.openRouterProviders || [] @@ -745,12 +745,20 @@ const ApiOptions = ({ className="mb-2">