From 2c1b09cc0909eb69e0440ca3bb4976a92121f54f Mon Sep 17 00:00:00 2001 From: David Lo Dico Date: Thu, 2 Apr 2026 14:11:22 +0200 Subject: [PATCH 1/3] feat: add EUrouter AI provider --- .env.example | 4 ++ app/lib/modules/llm/providers/eurouter.ts | 54 +++++++++++++++++++++++ app/lib/modules/llm/registry.ts | 2 + 3 files changed, 60 insertions(+) create mode 100644 app/lib/modules/llm/providers/eurouter.ts diff --git a/.env.example b/.env.example index b724838845..2c8e3c1bfa 100644 --- a/.env.example +++ b/.env.example @@ -17,6 +17,10 @@ ANTHROPIC_API_KEY=your_anthropic_api_key_here CEREBRAS_API_KEY=your_cerebras_api_key_here # Fireworks AI (Fast inference with FireAttention engine) +# EUrouter (EU-hosted, GDPR-friendly AI gateway) +# Get your API key from: https://www.eurouter.ai +EUROUTER_API_KEY=your_eurouter_api_key_here + # Get your API key from: https://fireworks.ai/api-keys FIREWORKS_API_KEY=your_fireworks_api_key_here diff --git a/app/lib/modules/llm/providers/eurouter.ts b/app/lib/modules/llm/providers/eurouter.ts new file mode 100644 index 0000000000..ae9895820f --- /dev/null +++ b/app/lib/modules/llm/providers/eurouter.ts @@ -0,0 +1,54 @@ +import { BaseProvider } from '~/lib/modules/llm/base-provider'; +import type { ModelInfo } from '~/lib/modules/llm/types'; +import type { IProviderSetting } from '~/types/model'; +import type { LanguageModelV1 } from 'ai'; +import { createOpenAI } from '@ai-sdk/openai'; + +export default class EUrouterProvider extends BaseProvider { + name = 'EUrouter'; + getApiKeyLink = 'https://www.eurouter.ai'; + + config = { + apiTokenKey: 'EUROUTER_API_KEY', + }; + + staticModels: ModelInfo[] = [ + { name: 'deepseek-r1', label: 'DeepSeek R1', provider: 'EUrouter', maxTokenAllowed: 64000 }, + { name: 'kimi-k2.5', label: 'Kimi K2.5', provider: 'EUrouter', maxTokenAllowed: 128000 }, + { + name: 'mistral-large-latest', + label: 'Mistral Large 3', + provider: 'EUrouter', + maxTokenAllowed: 128000, + }, + { name: 'minimax-m2.5', label: 'MiniMax M2.5', provider: 'EUrouter', maxTokenAllowed: 128000 }, + ]; + + getModelInstance(options: { + model: string; + serverEnv: Env; + apiKeys?: Record; + providerSettings?: Record; + }): LanguageModelV1 { + const { model, serverEnv, apiKeys, providerSettings } = options; + + const { apiKey } = this.getProviderBaseUrlAndKey({ + apiKeys, + providerSettings: providerSettings?.[this.name], + serverEnv: serverEnv as any, + defaultBaseUrlKey: '', + defaultApiTokenKey: 'EUROUTER_API_KEY', + }); + + if (!apiKey) { + throw new Error(`Missing API key for ${this.name} provider`); + } + + const openai = createOpenAI({ + baseURL: 'https://api.eurouter.ai/api/v1', + apiKey, + }); + + return openai(model); + } +} diff --git a/app/lib/modules/llm/registry.ts b/app/lib/modules/llm/registry.ts index 01bbe81140..40bc5eb78e 100644 --- a/app/lib/modules/llm/registry.ts +++ b/app/lib/modules/llm/registry.ts @@ -2,6 +2,7 @@ import AnthropicProvider from './providers/anthropic'; import CerebrasProvider from './providers/cerebras'; import CohereProvider from './providers/cohere'; import DeepseekProvider from './providers/deepseek'; +import EUrouterProvider from './providers/eurouter'; import FireworksProvider from './providers/fireworks'; import GoogleProvider from './providers/google'; import GroqProvider from './providers/groq'; @@ -26,6 +27,7 @@ export { CerebrasProvider, CohereProvider, DeepseekProvider, + EUrouterProvider, FireworksProvider, GoogleProvider, GroqProvider, From cef3a3d488ca94df229764fe1e122e9befa7f366 Mon Sep 17 00:00:00 2001 From: David Lo Dico Date: Thu, 2 Apr 2026 14:34:09 +0200 Subject: [PATCH 2/3] feat: add dynamic model fetching from EUrouter API --- app/lib/modules/llm/providers/eurouter.ts | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/app/lib/modules/llm/providers/eurouter.ts b/app/lib/modules/llm/providers/eurouter.ts index ae9895820f..8d58bdb7e1 100644 --- a/app/lib/modules/llm/providers/eurouter.ts +++ b/app/lib/modules/llm/providers/eurouter.ts @@ -24,6 +24,56 @@ export default class EUrouterProvider extends BaseProvider { { name: 'minimax-m2.5', label: 'MiniMax M2.5', provider: 'EUrouter', maxTokenAllowed: 128000 }, ]; + async getDynamicModels( + apiKeys?: Record, + settings?: IProviderSetting, + serverEnv?: Record, + ): Promise { + const { apiKey } = this.getProviderBaseUrlAndKey({ + apiKeys, + providerSettings: settings, + serverEnv: serverEnv as any, + defaultBaseUrlKey: '', + defaultApiTokenKey: 'EUROUTER_API_KEY', + }); + + if (!apiKey) { + return []; + } + + try { + const response = await fetch('https://api.eurouter.ai/api/v1/models', { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + signal: this.createTimeoutSignal(5000), + }); + + if (!response.ok) { + console.error(`EUrouter API error: ${response.statusText}`); + return []; + } + + const data = (await response.json()) as any; + const staticModelIds = this.staticModels.map((m) => m.name); + + const dynamicModels = + data.data + ?.filter((model: any) => !staticModelIds.includes(model.id)) + .map((m: any) => ({ + name: m.id, + label: m.id, + provider: this.name, + maxTokenAllowed: 128000, + })) || []; + + return dynamicModels; + } catch (error) { + console.error('Failed to fetch EUrouter models:', error); + return []; + } + } + getModelInstance(options: { model: string; serverEnv: Env; From d74df7b82f7a29cd43eb2490e21b7d287e92a396 Mon Sep 17 00:00:00 2001 From: David Lo Dico Date: Mon, 6 Apr 2026 16:29:50 +0200 Subject: [PATCH 3/3] feat: add EUrouter app attribution headers --- app/lib/modules/llm/providers/eurouter.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/lib/modules/llm/providers/eurouter.ts b/app/lib/modules/llm/providers/eurouter.ts index 8d58bdb7e1..9887a0c0df 100644 --- a/app/lib/modules/llm/providers/eurouter.ts +++ b/app/lib/modules/llm/providers/eurouter.ts @@ -97,6 +97,10 @@ export default class EUrouterProvider extends BaseProvider { const openai = createOpenAI({ baseURL: 'https://api.eurouter.ai/api/v1', apiKey, + headers: { + 'HTTP-Referer': 'https://bolt.diy', + 'X-EUrouter-Title': 'bolt.diy', + }, }); return openai(model);