From 1cdfe570efd1904ce4dc01908585b9b08782a4c3 Mon Sep 17 00:00:00 2001 From: Developers Digest <124798203+developersdigest@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:33:45 +0300 Subject: [PATCH] Create provider-manager.ts update provider manager --- lib/ai/provider-manager.ts | 122 +++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 lib/ai/provider-manager.ts diff --git a/lib/ai/provider-manager.ts b/lib/ai/provider-manager.ts new file mode 100644 index 0000000..c3b5b02 --- /dev/null +++ b/lib/ai/provider-manager.ts @@ -0,0 +1,122 @@ +import { appConfig } from '@/config/app.config'; +import { createGroq } from '@ai-sdk/groq'; +import { createAnthropic } from '@ai-sdk/anthropic'; +import { createOpenAI } from '@ai-sdk/openai'; +import { createGoogleGenerativeAI } from '@ai-sdk/google'; + +type ProviderName = 'openai' | 'anthropic' | 'groq' | 'google'; + +// Client function type returned by @ai-sdk providers +export type ProviderClient = + | ReturnType + | ReturnType + | ReturnType + | ReturnType; + +export interface ProviderResolution { + client: ProviderClient; + actualModel: string; +} + +const aiGatewayApiKey = process.env.AI_GATEWAY_API_KEY; +const aiGatewayBaseURL = 'https://ai-gateway.vercel.sh/v1'; +const isUsingAIGateway = !!aiGatewayApiKey; + +// Cache provider clients by a stable key to avoid recreating +const clientCache = new Map(); + +function getEnvDefaults(provider: ProviderName): { apiKey?: string; baseURL?: string } { + if (isUsingAIGateway) { + return { apiKey: aiGatewayApiKey, baseURL: aiGatewayBaseURL }; + } + + switch (provider) { + case 'openai': + return { apiKey: process.env.OPENAI_API_KEY, baseURL: process.env.OPENAI_BASE_URL }; + case 'anthropic': + // Default Anthropic base URL mirrors existing routes + return { apiKey: process.env.ANTHROPIC_API_KEY, baseURL: process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1' }; + case 'groq': + return { apiKey: process.env.GROQ_API_KEY, baseURL: process.env.GROQ_BASE_URL }; + case 'google': + return { apiKey: process.env.GEMINI_API_KEY, baseURL: process.env.GEMINI_BASE_URL }; + default: + return {}; + } +} + +function getOrCreateClient(provider: ProviderName, apiKey?: string, baseURL?: string): ProviderClient { + const effective = isUsingAIGateway + ? { apiKey: aiGatewayApiKey, baseURL: aiGatewayBaseURL } + : { apiKey, baseURL }; + + const cacheKey = `${provider}:${effective.apiKey || ''}:${effective.baseURL || ''}`; + const cached = clientCache.get(cacheKey); + if (cached) return cached; + + let client: ProviderClient; + switch (provider) { + case 'openai': + client = createOpenAI({ apiKey: effective.apiKey || getEnvDefaults('openai').apiKey, baseURL: effective.baseURL ?? getEnvDefaults('openai').baseURL }); + break; + case 'anthropic': + client = createAnthropic({ apiKey: effective.apiKey || getEnvDefaults('anthropic').apiKey, baseURL: effective.baseURL ?? getEnvDefaults('anthropic').baseURL }); + break; + case 'groq': + client = createGroq({ apiKey: effective.apiKey || getEnvDefaults('groq').apiKey, baseURL: effective.baseURL ?? getEnvDefaults('groq').baseURL }); + break; + case 'google': + client = createGoogleGenerativeAI({ apiKey: effective.apiKey || getEnvDefaults('google').apiKey, baseURL: effective.baseURL ?? getEnvDefaults('google').baseURL }); + break; + default: + client = createGroq({ apiKey: effective.apiKey || getEnvDefaults('groq').apiKey, baseURL: effective.baseURL ?? getEnvDefaults('groq').baseURL }); + } + + clientCache.set(cacheKey, client); + return client; +} + +export function getProviderForModel(modelId: string): ProviderResolution { + // 1) Check explicit model configuration in app config (custom models) + const configured = appConfig.ai.modelApiConfig?.[modelId as keyof typeof appConfig.ai.modelApiConfig]; + if (configured) { + const { provider, apiKey, baseURL, model } = configured as { provider: ProviderName; apiKey?: string; baseURL?: string; model: string }; + const client = getOrCreateClient(provider, apiKey, baseURL); + return { client, actualModel: model }; + } + + // 2) Fallback logic based on prefixes and special cases + const isAnthropic = modelId.startsWith('anthropic/'); + const isOpenAI = modelId.startsWith('openai/'); + const isGoogle = modelId.startsWith('google/'); + const isKimiGroq = modelId === 'moonshotai/kimi-k2-instruct-0905'; + + if (isKimiGroq) { + const client = getOrCreateClient('groq'); + return { client, actualModel: 'moonshotai/kimi-k2-instruct-0905' }; + } + + if (isAnthropic) { + const client = getOrCreateClient('anthropic'); + return { client, actualModel: modelId.replace('anthropic/', '') }; + } + + if (isOpenAI) { + const client = getOrCreateClient('openai'); + return { client, actualModel: modelId.replace('openai/', '') }; + } + + if (isGoogle) { + const client = getOrCreateClient('google'); + return { client, actualModel: modelId.replace('google/', '') }; + } + + // Default: use Groq with modelId as-is + const client = getOrCreateClient('groq'); + return { client, actualModel: modelId }; +} + +export default getProviderForModel; + + +