arena-learning / studyArena /lib /ai /providers.ts
Nitish kumar
Upload folder using huggingface_hub
c20f20c verified
/**
* Unified AI Provider Configuration
*
* Supports multiple AI providers through Vercel AI SDK:
* - OpenAI (native)
* - Anthropic Claude (native)
* - Google Gemini (native)
* - MiniMax (Anthropic-compatible, recommended by official)
* - OpenAI-compatible providers (DeepSeek, Kimi, GLM, SiliconFlow, Doubao, etc.)
*
* Sources:
* - https://platform.openai.com/docs/models
* - https://platform.claude.com/docs/en/about-claude/models/overview
* - https://ai.google.dev/gemini-api/docs/models
* - https://api-docs.deepseek.com/quick_start/pricing
* - https://platform.moonshot.cn/docs/pricing/chat
* - https://platform.minimaxi.com/docs/guides/text-generation
* - https://platform.minimax.io/docs/api-reference/text-anthropic-api
* - https://docs.bigmodel.cn/cn/guide/start/model-overview
* - https://help.aliyun.com/zh/model-studio/models (Qwen/DashScope)
* - https://siliconflow.cn/models
* - https://siliconflow.cn/pricing
* - https://www.volcengine.com/docs/82379/1330310
*/
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import type { LanguageModel } from 'ai';
import type {
ProviderId,
ProviderConfig,
ModelInfo,
ModelConfig,
ThinkingConfig,
} from '@/lib/types/provider';
import { createLogger } from '@/lib/logger';
// NOTE: Do NOT import thinking-context.ts here — it uses node:async_hooks
// which is server-only, and this file is also used on the client via
// settings.ts. The thinking context is read from globalThis instead
// (set by thinking-context.ts at module load time on the server).
const log = createLogger('AIProviders');
// Re-export types for backward compatibility
export type { ProviderId, ProviderConfig, ModelInfo, ModelConfig };
/**
* Provider registry
*/
export const PROVIDERS: Record<ProviderId, ProviderConfig> = {
openai: {
id: 'openai',
name: 'OpenAI',
type: 'openai',
defaultBaseUrl: 'https://api.openai.com/v1',
requiresApiKey: true,
icon: '/logos/openai.svg',
models: [
{
id: 'gpt-5.2',
name: 'GPT-5.2',
contextWindow: 400000,
outputWindow: 128000,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: true,
budgetAdjustable: true,
defaultEnabled: false,
},
},
},
{
id: 'gpt-5.1',
name: 'GPT-5.1',
contextWindow: 400000,
outputWindow: 128000,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: true,
budgetAdjustable: true,
defaultEnabled: false,
},
},
},
{
id: 'gpt-5',
name: 'GPT-5',
contextWindow: 400000,
outputWindow: 128000,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
{
id: 'gpt-5-mini',
name: 'GPT-5-mini',
contextWindow: 128000,
outputWindow: 4096,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
{
id: 'gpt-5-nano',
name: 'GPT-5-nano',
contextWindow: 128000,
outputWindow: 4096,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
{
id: 'gpt-4o',
name: 'GPT-4o',
contextWindow: 128000,
outputWindow: 4096,
capabilities: { streaming: true, tools: true, vision: true },
},
{
id: 'gpt-4o-mini',
name: 'GPT-4o-mini',
contextWindow: 128000,
outputWindow: 4096,
capabilities: { streaming: true, tools: true, vision: true },
},
{
id: 'gpt-4-turbo',
name: 'GPT-4-turbo',
contextWindow: 128000,
outputWindow: 4096,
capabilities: { streaming: true, tools: true, vision: true },
},
{
id: 'o4-mini',
name: 'o4-mini',
contextWindow: 200000,
outputWindow: 100000,
capabilities: {
streaming: true,
tools: true,
vision: false,
thinking: {
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
{
id: 'o3',
name: 'o3',
contextWindow: 200000,
outputWindow: 100000,
capabilities: {
streaming: true,
tools: true,
vision: false,
thinking: {
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
{
id: 'o3-mini',
name: 'o3-mini',
contextWindow: 200000,
outputWindow: 100000,
capabilities: {
streaming: true,
tools: true,
vision: false,
thinking: {
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
{
id: 'o1',
name: 'o1',
contextWindow: 200000,
outputWindow: 100000,
capabilities: {
streaming: true,
tools: false,
vision: false,
thinking: {
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
],
},
anthropic: {
id: 'anthropic',
name: 'Claude',
type: 'anthropic',
requiresApiKey: true,
defaultBaseUrl: 'https://api.anthropic.com/v1',
icon: '/logos/claude.svg',
models: [
{
id: 'claude-opus-4-6',
name: 'Claude Opus 4.6',
contextWindow: 200000,
outputWindow: 128000,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: true,
budgetAdjustable: true,
defaultEnabled: false,
},
},
},
{
id: 'claude-sonnet-4-6',
name: 'Claude Sonnet 4.6',
contextWindow: 200000,
outputWindow: 128000,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: true,
budgetAdjustable: true,
defaultEnabled: false,
},
},
},
{
id: 'claude-sonnet-4-5',
name: 'Claude Sonnet 4.5',
contextWindow: 200000,
outputWindow: 64000,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: true,
budgetAdjustable: true,
defaultEnabled: false,
},
},
},
{
id: 'claude-haiku-4-5',
name: 'Claude Haiku 4.5',
contextWindow: 200000,
outputWindow: 64000,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: true,
budgetAdjustable: true,
defaultEnabled: false,
},
},
},
],
},
google: {
id: 'google',
name: 'Gemini',
type: 'google',
requiresApiKey: true,
defaultBaseUrl: 'https://generativelanguage.googleapis.com/v1beta',
icon: '/logos/gemini.svg',
models: [
{
id: 'gemini-3.1-pro-preview',
name: 'Gemini 3.1 Pro Preview',
contextWindow: 1048576,
outputWindow: 65536,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
{
id: 'gemini-3-pro-preview',
name: 'Gemini 3 Pro Preview',
contextWindow: 1048576,
outputWindow: 65536,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
{
id: 'gemini-3-flash-preview',
name: 'Gemini 3 Flash Preview',
contextWindow: 1048576,
outputWindow: 65536,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
{
id: 'gemini-2.5-flash',
name: 'Gemini 2.5 Flash',
contextWindow: 1048576,
outputWindow: 65536,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: true,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
{
id: 'gemini-2.5-flash-lite',
name: 'Gemini 2.5 Flash Lite',
contextWindow: 1048576,
outputWindow: 65536,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: true,
budgetAdjustable: true,
defaultEnabled: false,
},
},
},
{
id: 'gemini-2.5-pro',
name: 'Gemini 2.5 Pro',
contextWindow: 1048576,
outputWindow: 65536,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
},
},
},
],
},
glm: {
id: 'glm',
name: 'GLM',
type: 'openai',
defaultBaseUrl: 'https://open.bigmodel.cn/api/paas/v4',
requiresApiKey: true,
icon: '/logos/glm.svg',
models: [
// GLM-5 Series - Latest flagship model
{
id: 'glm-5',
name: 'GLM-5',
contextWindow: 200000,
outputWindow: 128000,
capabilities: { streaming: true, tools: true, vision: false },
},
// GLM-4.7 Series
{
id: 'glm-4.7',
name: 'GLM-4.7',
contextWindow: 200000,
outputWindow: 128000,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'glm-4.7-flashx',
name: 'GLM-4.7-FlashX',
contextWindow: 200000,
outputWindow: 128000,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'glm-4.7-flash',
name: 'GLM-4.7-Flash',
contextWindow: 200000,
outputWindow: 128000,
capabilities: { streaming: true, tools: true, vision: false },
},
// GLM-4.6 Series - Advanced coding & reasoning
{
id: 'glm-4.6',
name: 'GLM-4.6',
contextWindow: 200000,
outputWindow: 128000,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'glm-4.6v',
name: 'GLM-4.6V',
contextWindow: 128000,
outputWindow: 32000,
capabilities: { streaming: true, tools: true, vision: true },
},
{
id: 'glm-4.6v-flash',
name: 'GLM-4.6V-Flash',
contextWindow: 128000,
outputWindow: 32000,
capabilities: { streaming: true, tools: true, vision: true },
},
// GLM-4.5 Series - Cost-effective models
{
id: 'glm-4.5-air',
name: 'GLM-4.5-Air',
contextWindow: 128000,
outputWindow: 96000,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'glm-4.5-airx',
name: 'GLM-4.5-AirX',
contextWindow: 128000,
outputWindow: 96000,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'glm-4.5-flash',
name: 'GLM-4.5-Flash',
contextWindow: 128000,
outputWindow: 96000,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'glm-4-long',
name: 'GLM-4-Long',
contextWindow: 1000000,
outputWindow: 4096,
capabilities: { streaming: true, tools: true, vision: false },
},
],
},
qwen: {
id: 'qwen',
name: 'Qwen',
type: 'openai',
defaultBaseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
requiresApiKey: true,
icon: '/logos/qwen.svg',
models: [
{
id: 'qwen3.5-flash',
name: 'Qwen3.5 Flash',
contextWindow: 1000000,
outputWindow: 65536,
capabilities: { streaming: true, tools: true, vision: true },
},
{
id: 'qwen3.5-plus',
name: 'Qwen3.5 Plus',
contextWindow: 1000000,
outputWindow: 65536,
capabilities: { streaming: true, tools: true, vision: true },
},
{
id: 'qwen3-max',
name: 'Qwen3 Max',
contextWindow: 262144,
outputWindow: 65536,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'qwen3-vl-plus',
name: 'Qwen3 VL Plus',
contextWindow: 262144,
outputWindow: 32768,
capabilities: { streaming: true, tools: true, vision: true },
},
],
},
deepseek: {
id: 'deepseek',
name: 'DeepSeek',
type: 'openai',
defaultBaseUrl: 'https://api.deepseek.com/v1',
requiresApiKey: true,
icon: '/logos/deepseek.svg',
models: [
{
id: 'deepseek-chat',
name: 'DeepSeek-Chat',
contextWindow: 128000,
outputWindow: 4096,
capabilities: {
streaming: true,
tools: true,
vision: false,
thinking: {
toggleable: true,
budgetAdjustable: false,
defaultEnabled: false,
},
},
},
{
id: 'deepseek-reasoner',
name: 'DeepSeek-Reasoner',
contextWindow: 128000,
outputWindow: 32000,
capabilities: {
streaming: true,
tools: true,
vision: false,
thinking: {
toggleable: true,
budgetAdjustable: false,
defaultEnabled: true,
},
},
},
],
},
kimi: {
id: 'kimi',
name: 'Kimi',
type: 'openai',
defaultBaseUrl: 'https://api.moonshot.cn/v1',
requiresApiKey: true,
icon: '/logos/kimi.png',
models: [
// K2.5 Series (2026) - 1T MoE, 32B active parameters
{
id: 'kimi-k2.5',
name: 'Kimi K2.5',
contextWindow: 256000,
outputWindow: 8192,
capabilities: {
streaming: true,
tools: true,
vision: true,
thinking: {
toggleable: true,
budgetAdjustable: false,
defaultEnabled: true,
},
},
},
{
id: 'kimi-k2-0905-preview',
name: 'Kimi K2 0905 Preview',
contextWindow: 256000,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'kimi-k2-thinking',
name: 'Kimi K2 Thinking',
contextWindow: 256000,
outputWindow: 8192,
capabilities: {
streaming: true,
tools: true,
vision: false,
thinking: {
toggleable: true,
budgetAdjustable: false,
defaultEnabled: true,
},
},
},
{
id: 'kimi-k2-turbo-preview',
name: 'Kimi K2 Turbo Preview',
contextWindow: 256000,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'moonshot-v1-128k',
name: 'Moonshot V1 128K',
contextWindow: 128000,
outputWindow: 4096,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'moonshot-v1-32k',
name: 'Moonshot V1 32K',
contextWindow: 32000,
outputWindow: 4096,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'moonshot-v1-8k',
name: 'Moonshot V1 8K',
contextWindow: 8000,
outputWindow: 4096,
capabilities: { streaming: true, tools: true, vision: false },
},
],
},
minimax: {
id: 'minimax',
name: 'MiniMax',
type: 'anthropic',
defaultBaseUrl: 'https://api.minimaxi.com/anthropic/v1',
requiresApiKey: true,
icon: '/logos/minimax.svg',
models: [
{
id: 'MiniMax-M2.5',
name: 'MiniMax M2.5',
contextWindow: 204800,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'MiniMax-M2.1',
name: 'MiniMax M2.1',
contextWindow: 204800,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'MiniMax-M2.1-lightning',
name: 'MiniMax M2.1 Lightning',
contextWindow: 204800,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'MiniMax-M2',
name: 'MiniMax M2',
contextWindow: 204800,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
],
},
siliconflow: {
id: 'siliconflow',
name: '硅基流动',
type: 'openai',
defaultBaseUrl: 'https://api.siliconflow.cn/v1',
requiresApiKey: true,
icon: '/logos/siliconflow.svg',
models: [
// DeepSeek Series
{
id: 'deepseek-ai/DeepSeek-V3.2',
name: 'DeepSeek-V3.2',
contextWindow: 128000,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'deepseek-ai/DeepSeek-V3',
name: 'DeepSeek-V3',
contextWindow: 128000,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'deepseek-ai/DeepSeek-R1',
name: 'DeepSeek-R1',
contextWindow: 128000,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'deepseek-ai/DeepSeek-R1-Distill-Qwen-7B',
name: 'DeepSeek-R1-Distill-Qwen-7B',
contextWindow: 128000,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
// Qwen Series
{
id: 'Qwen/Qwen2.5-72B-Instruct',
name: 'Qwen2.5-72B-Instruct',
contextWindow: 128000,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'Qwen/Qwen2.5-Coder-7B-Instruct',
name: 'Qwen2.5-Coder-7B-Instruct',
contextWindow: 128000,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'Qwen/Qwen2.5-7B-Instruct',
name: 'Qwen2.5-7B-Instruct',
contextWindow: 128000,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'Qwen/Qwen3-VL-32B-Instruct',
name: 'Qwen3-VL-32B-Instruct',
contextWindow: 256000,
outputWindow: 32768,
capabilities: { streaming: true, tools: true, vision: true },
},
// MiniMax Series
{
id: 'MiniMaxAI/MiniMax-M2',
name: 'MiniMax-M2',
contextWindow: 204800,
outputWindow: 131072,
capabilities: { streaming: true, tools: true, vision: false },
},
// Kimi Series
{
id: 'Pro/moonshotai/Kimi-K2.5',
name: 'Kimi-K2.5',
contextWindow: 256000,
outputWindow: 96000,
capabilities: { streaming: true, tools: true, vision: false },
},
// GLM Series
{
id: 'THUDM/GLM-Z1-Rumination-32B-0414',
name: 'GLM-Z1-Rumination-32B',
contextWindow: 32000,
outputWindow: 16384,
capabilities: { streaming: true, tools: true, vision: false },
},
{
id: 'THUDM/GLM-4.1V-9B-Thinking',
name: 'GLM-4.1V-9B-Thinking',
contextWindow: 64000,
outputWindow: 8192,
capabilities: { streaming: true, tools: true, vision: true },
},
],
},
doubao: {
id: 'doubao',
name: '豆包',
type: 'openai',
defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
requiresApiKey: true,
icon: '/logos/doubao.svg',
models: [
{
id: 'doubao-seed-2-0-pro-260215',
name: 'Doubao Seed 2.0 Pro',
contextWindow: 128000,
outputWindow: 32768,
capabilities: { streaming: true, tools: true, vision: true },
},
{
id: 'doubao-seed-2-0-lite-260215',
name: 'Doubao Seed 2.0 Lite',
contextWindow: 128000,
outputWindow: 32768,
capabilities: { streaming: true, tools: true, vision: true },
},
{
id: 'doubao-seed-2-0-mini-260215',
name: 'Doubao Seed 2.0 Mini',
contextWindow: 128000,
outputWindow: 32768,
capabilities: { streaming: true, tools: true, vision: true },
},
{
id: 'doubao-seed-1-8-251228',
name: 'Doubao Seed 1.8',
contextWindow: 128000,
outputWindow: 32768,
capabilities: { streaming: true, tools: true, vision: true },
},
],
},
};
/**
* Get provider config (from built-in or unified config in localStorage)
*/
function getProviderConfig(providerId: ProviderId): ProviderConfig | null {
// Check built-in providers first
if (PROVIDERS[providerId]) {
return PROVIDERS[providerId];
}
// Check unified providersConfig in localStorage (browser only)
if (typeof window !== 'undefined') {
try {
const storedConfig = localStorage.getItem('providersConfig');
if (storedConfig) {
const config = JSON.parse(storedConfig);
const providerSettings = config[providerId];
if (providerSettings) {
return {
id: providerId,
name: providerSettings.name,
type: providerSettings.type,
defaultBaseUrl: providerSettings.defaultBaseUrl,
icon: providerSettings.icon,
requiresApiKey: providerSettings.requiresApiKey,
models: providerSettings.models,
};
}
}
} catch (e) {
log.error('Failed to load provider config:', e);
}
}
return null;
}
/**
* Model instance with its configuration info
*/
export interface ModelWithInfo {
model: LanguageModel;
modelInfo: ModelInfo | null;
}
/**
* Return vendor-specific body params to inject for OpenAI-compatible providers.
* Called from the custom fetch wrapper inside getModel().
*/
function getCompatThinkingBodyParams(
providerId: ProviderId,
config: ThinkingConfig,
): Record<string, unknown> | undefined {
if (config.enabled === false) {
switch (providerId) {
// Kimi / DeepSeek / GLM use { thinking: { type: "disabled" } }
case 'kimi':
case 'deepseek':
case 'glm':
return { thinking: { type: 'disabled' } };
// Qwen / SiliconFlow use { enable_thinking: false }
case 'qwen':
case 'siliconflow':
return { enable_thinking: false };
default:
return undefined;
}
}
if (config.enabled === true) {
switch (providerId) {
case 'kimi':
case 'deepseek':
case 'glm':
return { thinking: { type: 'enabled' } };
case 'qwen':
case 'siliconflow':
return { enable_thinking: true };
default:
return undefined;
}
}
return undefined;
}
/**
* Get a configured language model instance with its info
* Accepts individual parameters for flexibility and security
*/
export function getModel(config: ModelConfig): ModelWithInfo {
// Get provider type and requiresApiKey, with fallback to registry
let providerType = config.providerType;
let requiresApiKey = config.requiresApiKey ?? true;
if (!providerType) {
const provider = getProviderConfig(config.providerId);
if (provider) {
providerType = provider.type;
requiresApiKey = provider.requiresApiKey;
} else {
throw new Error(`Unknown provider: ${config.providerId}. Please provide providerType.`);
}
}
// Validate API key if required
if (requiresApiKey && !config.apiKey) {
throw new Error(`API key required for provider: ${config.providerId}`);
}
// Use provided API key, or empty string for providers that don't require one
const effectiveApiKey = config.apiKey || '';
// Resolve base URL: explicit > provider default > SDK default
const provider = getProviderConfig(config.providerId);
const effectiveBaseUrl = config.baseUrl || provider?.defaultBaseUrl || undefined;
let model: LanguageModel;
switch (providerType) {
case 'openai': {
const openaiOptions: Parameters<typeof createOpenAI>[0] = {
apiKey: effectiveApiKey,
baseURL: effectiveBaseUrl,
};
// For OpenAI-compatible providers (not native OpenAI), add a fetch
// wrapper that injects vendor-specific thinking params into the HTTP
// body. The thinking config is read from AsyncLocalStorage, set by
// callLLM / streamLLM at call time.
if (config.providerId !== 'openai') {
const providerId = config.providerId;
openaiOptions.fetch = async (url: RequestInfo | URL, init?: RequestInit) => {
// Read thinking config from globalThis (set by thinking-context.ts)
const thinkingCtx = (globalThis as Record<string, unknown>).__thinkingContext as
| { getStore?: () => unknown }
| undefined;
const thinking = thinkingCtx?.getStore?.() as ThinkingConfig | undefined;
if (thinking && init?.body && typeof init.body === 'string') {
const extra = getCompatThinkingBodyParams(providerId, thinking);
if (extra) {
try {
const body = JSON.parse(init.body);
Object.assign(body, extra);
init = { ...init, body: JSON.stringify(body) };
} catch {
/* leave body as-is */
}
}
}
return globalThis.fetch(url, init);
};
}
const openai = createOpenAI(openaiOptions);
model = openai.chat(config.modelId);
break;
}
case 'anthropic': {
const anthropic = createAnthropic({
apiKey: effectiveApiKey,
baseURL: effectiveBaseUrl,
});
model = anthropic.chat(config.modelId);
break;
}
case 'google': {
const googleOptions: Parameters<typeof createGoogleGenerativeAI>[0] = {
apiKey: effectiveApiKey,
baseURL: effectiveBaseUrl,
};
if (config.proxy) {
// Dynamic require to avoid bundling undici on the client side
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { ProxyAgent, fetch: undiciFetch } = require('undici');
const agent = new ProxyAgent(config.proxy);
googleOptions.fetch = ((input: RequestInfo | URL, init?: RequestInit) =>
undiciFetch(input as string, {
...(init as Record<string, unknown>),
dispatcher: agent,
}).then((r: unknown) => r as Response)) as typeof fetch;
}
const google = createGoogleGenerativeAI(googleOptions);
model = google.chat(config.modelId);
break;
}
default:
throw new Error(`Unsupported provider type: ${providerType}`);
}
// Look up model info from the provider registry
const modelInfo = provider?.models.find((m) => m.id === config.modelId) || null;
return { model, modelInfo };
}
/**
* Parse model string in format "providerId:modelId" or just "modelId" (defaults to OpenAI)
*/
export function parseModelString(modelString: string): {
providerId: ProviderId;
modelId: string;
} {
// Split only on the first colon to handle model IDs that contain colons
const colonIndex = modelString.indexOf(':');
if (colonIndex > 0) {
return {
providerId: modelString.slice(0, colonIndex) as ProviderId,
modelId: modelString.slice(colonIndex + 1),
};
}
// Default to OpenAI for backward compatibility
return {
providerId: 'openai',
modelId: modelString,
};
}
/**
* Get all available models grouped by provider
*/
export function getAllModels(): {
provider: ProviderConfig;
models: ModelInfo[];
}[] {
return Object.values(PROVIDERS).map((provider) => ({
provider,
models: provider.models,
}));
}
/**
* Get provider by ID
*/
export function getProvider(providerId: ProviderId): ProviderConfig | undefined {
return PROVIDERS[providerId];
}
/**
* Get model info
*/
export function getModelInfo(providerId: ProviderId, modelId: string): ModelInfo | undefined {
const provider = PROVIDERS[providerId];
return provider?.models.find((m) => m.id === modelId);
}