Skip to content

Commit 8bedc35

Browse files
Create provider-manager.ts
update provider manager
1 parent ce3e23c commit 8bedc35

File tree

1 file changed

+122
-0
lines changed

1 file changed

+122
-0
lines changed

lib/ai/provider-manager.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { appConfig } from '@/config/app.config';
2+
import { createGroq } from '@ai-sdk/groq';
3+
import { createAnthropic } from '@ai-sdk/anthropic';
4+
import { createOpenAI } from '@ai-sdk/openai';
5+
import { createGoogleGenerativeAI } from '@ai-sdk/google';
6+
7+
type ProviderName = 'openai' | 'anthropic' | 'groq' | 'google';
8+
9+
// Client function type returned by @ai-sdk providers
10+
export type ProviderClient =
11+
| ReturnType<typeof createOpenAI>
12+
| ReturnType<typeof createAnthropic>
13+
| ReturnType<typeof createGroq>
14+
| ReturnType<typeof createGoogleGenerativeAI>;
15+
16+
export interface ProviderResolution {
17+
client: ProviderClient;
18+
actualModel: string;
19+
}
20+
21+
const aiGatewayApiKey = process.env.AI_GATEWAY_API_KEY;
22+
const aiGatewayBaseURL = 'https://ai-gateway.vercel.sh/v1';
23+
const isUsingAIGateway = !!aiGatewayApiKey;
24+
25+
// Cache provider clients by a stable key to avoid recreating
26+
const clientCache = new Map<string, ProviderClient>();
27+
28+
function getEnvDefaults(provider: ProviderName): { apiKey?: string; baseURL?: string } {
29+
if (isUsingAIGateway) {
30+
return { apiKey: aiGatewayApiKey, baseURL: aiGatewayBaseURL };
31+
}
32+
33+
switch (provider) {
34+
case 'openai':
35+
return { apiKey: process.env.OPENAI_API_KEY, baseURL: process.env.OPENAI_BASE_URL };
36+
case 'anthropic':
37+
// Default Anthropic base URL mirrors existing routes
38+
return { apiKey: process.env.ANTHROPIC_API_KEY, baseURL: process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1' };
39+
case 'groq':
40+
return { apiKey: process.env.GROQ_API_KEY, baseURL: process.env.GROQ_BASE_URL };
41+
case 'google':
42+
return { apiKey: process.env.GEMINI_API_KEY, baseURL: process.env.GEMINI_BASE_URL };
43+
default:
44+
return {};
45+
}
46+
}
47+
48+
function getOrCreateClient(provider: ProviderName, apiKey?: string, baseURL?: string): ProviderClient {
49+
const effective = isUsingAIGateway
50+
? { apiKey: aiGatewayApiKey, baseURL: aiGatewayBaseURL }
51+
: { apiKey, baseURL };
52+
53+
const cacheKey = `${provider}:${effective.apiKey || ''}:${effective.baseURL || ''}`;
54+
const cached = clientCache.get(cacheKey);
55+
if (cached) return cached;
56+
57+
let client: ProviderClient;
58+
switch (provider) {
59+
case 'openai':
60+
client = createOpenAI({ apiKey: effective.apiKey || getEnvDefaults('openai').apiKey, baseURL: effective.baseURL ?? getEnvDefaults('openai').baseURL });
61+
break;
62+
case 'anthropic':
63+
client = createAnthropic({ apiKey: effective.apiKey || getEnvDefaults('anthropic').apiKey, baseURL: effective.baseURL ?? getEnvDefaults('anthropic').baseURL });
64+
break;
65+
case 'groq':
66+
client = createGroq({ apiKey: effective.apiKey || getEnvDefaults('groq').apiKey, baseURL: effective.baseURL ?? getEnvDefaults('groq').baseURL });
67+
break;
68+
case 'google':
69+
client = createGoogleGenerativeAI({ apiKey: effective.apiKey || getEnvDefaults('google').apiKey, baseURL: effective.baseURL ?? getEnvDefaults('google').baseURL });
70+
break;
71+
default:
72+
client = createGroq({ apiKey: effective.apiKey || getEnvDefaults('groq').apiKey, baseURL: effective.baseURL ?? getEnvDefaults('groq').baseURL });
73+
}
74+
75+
clientCache.set(cacheKey, client);
76+
return client;
77+
}
78+
79+
export function getProviderForModel(modelId: string): ProviderResolution {
80+
// 1) Check explicit model configuration in app config (custom models)
81+
const configured = appConfig.ai.modelApiConfig?.[modelId as keyof typeof appConfig.ai.modelApiConfig];
82+
if (configured) {
83+
const { provider, apiKey, baseURL, model } = configured as { provider: ProviderName; apiKey?: string; baseURL?: string; model: string };
84+
const client = getOrCreateClient(provider, apiKey, baseURL);
85+
return { client, actualModel: model };
86+
}
87+
88+
// 2) Fallback logic based on prefixes and special cases
89+
const isAnthropic = modelId.startsWith('anthropic/');
90+
const isOpenAI = modelId.startsWith('openai/');
91+
const isGoogle = modelId.startsWith('google/');
92+
const isKimiGroq = modelId === 'moonshotai/kimi-k2-instruct-0905';
93+
94+
if (isKimiGroq) {
95+
const client = getOrCreateClient('groq');
96+
return { client, actualModel: 'moonshotai/kimi-k2-instruct-0905' };
97+
}
98+
99+
if (isAnthropic) {
100+
const client = getOrCreateClient('anthropic');
101+
return { client, actualModel: modelId.replace('anthropic/', '') };
102+
}
103+
104+
if (isOpenAI) {
105+
const client = getOrCreateClient('openai');
106+
return { client, actualModel: modelId.replace('openai/', '') };
107+
}
108+
109+
if (isGoogle) {
110+
const client = getOrCreateClient('google');
111+
return { client, actualModel: modelId.replace('google/', '') };
112+
}
113+
114+
// Default: use Groq with modelId as-is
115+
const client = getOrCreateClient('groq');
116+
return { client, actualModel: modelId };
117+
}
118+
119+
export default getProviderForModel;
120+
121+
122+

0 commit comments

Comments
 (0)