| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296 |
- /**
- * 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.minimaxi.com/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 IDs whose logos are monochrome-dark and need `dark:invert` in dark mode */
- export const MONO_LOGO_PROVIDERS: ReadonlySet<string> = new Set(['openai', 'ollama']);
- /**
- * 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-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',
- name: 'MiniMax M2',
- 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-highspeed',
- name: 'MiniMax M2.1 Highspeed',
- contextWindow: 204800,
- outputWindow: 8192,
- capabilities: { streaming: true, tools: true, vision: false },
- },
- {
- id: 'MiniMax-M2.5',
- name: 'MiniMax M2.5',
- contextWindow: 204800,
- outputWindow: 8192,
- capabilities: { streaming: true, tools: true, vision: false },
- },
- {
- id: 'MiniMax-M2.5-highspeed',
- name: 'MiniMax M2.5 Highspeed',
- contextWindow: 204800,
- outputWindow: 8192,
- capabilities: { streaming: true, tools: true, vision: false },
- },
- {
- id: 'MiniMax-M2.7',
- name: 'MiniMax M2.7',
- contextWindow: 204800,
- outputWindow: 8192,
- capabilities: { streaming: true, tools: true, vision: false },
- },
- {
- id: 'MiniMax-M2.7-highspeed',
- name: 'MiniMax M2.7 Highspeed',
- 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 },
- },
- ],
- },
- grok: {
- id: 'grok',
- name: 'Grok',
- type: 'openai',
- defaultBaseUrl: 'https://api.x.ai/v1',
- requiresApiKey: true,
- icon: '/logos/grok.svg',
- models: [
- {
- id: 'grok-4.20-beta-0309-reasoning',
- name: 'Grok 4.20 Reasoning',
- contextWindow: 2000000,
- outputWindow: 131072,
- capabilities: {
- streaming: true,
- tools: true,
- vision: true,
- thinking: {
- toggleable: false,
- budgetAdjustable: false,
- defaultEnabled: true,
- },
- },
- },
- {
- id: 'grok-4.20-beta-0309-non-reasoning',
- name: 'Grok 4.20',
- contextWindow: 2000000,
- outputWindow: 131072,
- capabilities: { streaming: true, tools: true, vision: true },
- },
- {
- id: 'grok-code-fast-1',
- name: 'Grok Code Fast',
- contextWindow: 256000,
- outputWindow: 32768,
- capabilities: { streaming: true, tools: true, vision: false },
- },
- {
- id: 'grok-4-fast-reasoning',
- name: 'Grok 4 Fast Reasoning',
- contextWindow: 2000000,
- outputWindow: 131072,
- capabilities: {
- streaming: true,
- tools: true,
- vision: true,
- thinking: {
- toggleable: false,
- budgetAdjustable: false,
- defaultEnabled: true,
- },
- },
- },
- {
- id: 'grok-4-fast-non-reasoning',
- name: 'Grok 4 Fast',
- contextWindow: 2000000,
- outputWindow: 131072,
- capabilities: { streaming: true, tools: true, vision: true },
- },
- {
- id: 'grok-4-1-fast-reasoning',
- name: 'Grok 4.1 Fast Reasoning',
- contextWindow: 2000000,
- outputWindow: 131072,
- capabilities: {
- streaming: true,
- tools: true,
- vision: true,
- thinking: {
- toggleable: false,
- budgetAdjustable: false,
- defaultEnabled: true,
- },
- },
- },
- {
- id: 'grok-4-1-fast-non-reasoning',
- name: 'Grok 4.1 Fast',
- contextWindow: 2000000,
- outputWindow: 131072,
- capabilities: { streaming: true, tools: true, vision: true },
- },
- {
- id: 'grok-4-0709',
- name: 'Grok 4',
- contextWindow: 256000,
- outputWindow: 32768,
- capabilities: { streaming: true, tools: true, vision: true },
- },
- {
- id: 'grok-3',
- name: 'Grok 3',
- contextWindow: 131072,
- outputWindow: 32768,
- capabilities: { streaming: true, tools: true, vision: false },
- },
- {
- id: 'grok-3-mini',
- name: 'Grok 3 Mini',
- contextWindow: 131072,
- outputWindow: 32768,
- capabilities: { streaming: true, tools: true, vision: false },
- },
- ],
- },
- ollama: {
- id: 'ollama',
- name: 'Ollama',
- type: 'openai',
- defaultBaseUrl: 'http://localhost:11434/v1',
- requiresApiKey: false,
- icon: '/logos/ollama.svg',
- models: [
- {
- id: 'llama3.3',
- name: 'Llama 3.3 70B',
- contextWindow: 131072,
- outputWindow: 4096,
- capabilities: { streaming: true, tools: true, vision: false },
- },
- {
- id: 'llama3.2',
- name: 'Llama 3.2 3B',
- contextWindow: 131072,
- outputWindow: 4096,
- capabilities: { streaming: true, tools: true, vision: false },
- },
- {
- id: 'qwen2.5',
- name: 'Qwen 2.5 7B',
- contextWindow: 131072,
- outputWindow: 8192,
- capabilities: { streaming: true, tools: true, vision: false },
- },
- {
- id: 'qwen2.5:32b',
- name: 'Qwen 2.5 32B',
- contextWindow: 131072,
- outputWindow: 8192,
- capabilities: { streaming: true, tools: true, vision: false },
- },
- {
- id: 'mistral',
- name: 'Mistral 7B',
- contextWindow: 32768,
- outputWindow: 4096,
- capabilities: { streaming: true, tools: false, vision: false },
- },
- {
- id: 'gemma3',
- name: 'Gemma 3 12B',
- contextWindow: 131072,
- outputWindow: 8192,
- capabilities: { streaming: true, tools: true, vision: true },
- },
- {
- id: 'deepseek-r1',
- name: 'DeepSeek R1',
- contextWindow: 131072,
- outputWindow: 8192,
- capabilities: { streaming: true, tools: false, vision: false },
- },
- {
- id: 'phi4',
- name: 'Phi-4 14B',
- contextWindow: 16384,
- outputWindow: 4096,
- capabilities: { streaming: true, tools: false, vision: false },
- },
- ],
- },
- };
- /**
- * 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;
- }
- function normalizeMiniMaxAnthropicBaseUrl(
- providerId: ProviderId,
- baseUrl?: string,
- ): string | undefined {
- if (providerId !== 'minimax' || !baseUrl) {
- return baseUrl;
- }
- const trimmed = baseUrl.replace(/\/$/, '');
- if (trimmed.endsWith('/anthropic/v1')) {
- return trimmed;
- }
- if (trimmed.endsWith('/anthropic')) {
- return `${trimmed}/v1`;
- }
- return `${trimmed}/anthropic/v1`;
- }
- /** Returns true if the provider requires an API key (defaults to true for unknown providers). */
- export function isProviderKeyRequired(providerId: string): boolean {
- return getProviderConfig(providerId as ProviderId)?.requiresApiKey ?? true;
- }
- /**
- * Get a configured language model instance with its info
- * Accepts individual parameters for flexibility and security
- */
- export function getModel(config: ModelConfig): ModelWithInfo {
- // providerType can come from client for custom providers; fall back to registry.
- let providerType = config.providerType;
- const provider = getProviderConfig(config.providerId);
- const requiresApiKey = provider?.requiresApiKey ?? true;
- if (!providerType) {
- if (provider) {
- providerType = provider.type;
- } 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 effectiveBaseUrl = normalizeMiniMaxAnthropicBaseUrl(
- config.providerId,
- 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);
- }
|