provider-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,840 @@
1
+ /**
2
+ * OpenAI Compatible Provider
3
+ *
4
+ * 统一适配器 - 支持 90% 的 AI 服务商
5
+ *
6
+ * 兼容的服务商:
7
+ * - OpenAI, DeepSeek, 智谱GLM, 通义千问, SiliconFlow
8
+ * - OpenRouter, Groq, xAI, Moonshot, Ollama, LM Studio
9
+ * - 以及所有支持 OpenAI API 格式的服务商
10
+ *
11
+ * 使用方式:
12
+ * const provider = new OpenAICompatibleProvider({
13
+ * id: 'deepseek',
14
+ * name: 'DeepSeek',
15
+ * baseUrl: 'https://api.deepseek.com/v1',
16
+ * apiKey: 'sk-xxx'
17
+ * });
18
+ */
19
+
20
+ export class OpenAICompatibleProvider {
21
+ constructor(config) {
22
+ this.id = config.id || 'openai-compatible';
23
+ this.name = config.name || config.id || 'Unknown';
24
+ this.nameCn = config.nameCn || this.name;
25
+ this.baseUrl = config.baseUrl || 'https://api.openai.com/v1';
26
+ this.apiKey = config.apiKey || null;
27
+ this.defaultModel = config.defaultModel || null;
28
+ this.models = config.models || [];
29
+ this.connected = false;
30
+ this.description = config.description || '';
31
+
32
+ // 可选配置
33
+ this.timeout = config.timeout || 60000;
34
+ this.headers = config.headers || {};
35
+ this.skipAuth = config.skipAuth || false; // Ollama 等本地服务不需要 auth
36
+ }
37
+
38
+ /**
39
+ * 连接/验证
40
+ */
41
+ async connect(apiKey) {
42
+ if (apiKey) this.apiKey = apiKey;
43
+
44
+ if (!this.skipAuth && !this.apiKey) {
45
+ throw new Error(`API Key required for ${this.name}`);
46
+ }
47
+
48
+ // 尝试获取模型列表来验证连接
49
+ try {
50
+ await this.fetchModels();
51
+ this.connected = true;
52
+ return true;
53
+ } catch (e) {
54
+ // 如果获取模型失败,但 API Key 存在,也认为连接成功
55
+ if (this.apiKey || this.skipAuth) {
56
+ this.connected = true;
57
+ return true;
58
+ }
59
+ throw e;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * 断开连接
65
+ */
66
+ disconnect() {
67
+ this.connected = false;
68
+ }
69
+
70
+ /**
71
+ * 发送聊天消息
72
+ */
73
+ async chat(model, messages, options = {}) {
74
+ if (!this.connected && !this.skipAuth) {
75
+ throw new Error(`Provider ${this.name} not connected`);
76
+ }
77
+
78
+ const url = `${this.baseUrl}/chat/completions`;
79
+
80
+ const body = {
81
+ model: model || this.defaultModel,
82
+ messages,
83
+ stream: options.stream || false,
84
+ ...options.extra
85
+ };
86
+
87
+ // 支持 Function Calling
88
+ if (options.tools && options.tools.length > 0) {
89
+ body.tools = options.tools;
90
+ if (options.tool_choice) {
91
+ body.tool_choice = options.tool_choice;
92
+ }
93
+ }
94
+
95
+ const headers = {
96
+ 'Content-Type': 'application/json',
97
+ ...this.getAuthHeader(),
98
+ ...this.headers
99
+ };
100
+
101
+ const response = await fetch(url, {
102
+ method: 'POST',
103
+ headers,
104
+ body: JSON.stringify(body),
105
+ signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined
106
+ });
107
+
108
+ if (!response.ok) {
109
+ const error = await response.json().catch(() => ({}));
110
+ throw new Error(error.error?.message || `${this.name} API error: ${response.status}`);
111
+ }
112
+
113
+ const data = await response.json();
114
+ const choice = data.choices?.[0];
115
+
116
+ // 处理 Function Calling 响应
117
+ const result = {
118
+ content: choice?.message?.content || '',
119
+ model: data.model,
120
+ usage: data.usage,
121
+ raw: data
122
+ };
123
+
124
+ // 如果有工具调用,解析并返回
125
+ if (choice?.message?.tool_calls && choice.message.tool_calls.length > 0) {
126
+ result.toolCalls = choice.message.tool_calls.map(tc => ({
127
+ id: tc.id,
128
+ name: tc.function.name,
129
+ arguments: tc.function.arguments
130
+ }));
131
+ }
132
+
133
+ return result;
134
+ }
135
+
136
+ /**
137
+ * 流式聊天
138
+ */
139
+ async *chatStream(model, messages, options = {}) {
140
+ if (!this.connected && !this.skipAuth) {
141
+ throw new Error(`Provider ${this.name} not connected`);
142
+ }
143
+
144
+ const url = `${this.baseUrl}/chat/completions`;
145
+
146
+ const body = {
147
+ model: model || this.defaultModel,
148
+ messages,
149
+ stream: true,
150
+ ...options.extra
151
+ };
152
+
153
+ // 支持 Function Calling (部分模型支持流式 FC)
154
+ if (options.tools && options.tools.length > 0) {
155
+ body.tools = options.tools;
156
+ if (options.tool_choice) {
157
+ body.tool_choice = options.tool_choice;
158
+ }
159
+ }
160
+
161
+ const headers = {
162
+ 'Content-Type': 'application/json',
163
+ ...this.getAuthHeader(),
164
+ ...this.headers
165
+ };
166
+
167
+ const response = await fetch(url, {
168
+ method: 'POST',
169
+ headers,
170
+ body: JSON.stringify(body),
171
+ signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined
172
+ });
173
+
174
+ if (!response.ok) {
175
+ const error = await response.json().catch(() => ({}));
176
+ throw new Error(error.error?.message || `${this.name} API error: ${response.status}`);
177
+ }
178
+
179
+ const reader = response.body.getReader();
180
+ const decoder = new TextDecoder();
181
+ let buffer = '';
182
+
183
+ // 用于累积工具调用
184
+ const toolCallChunks = new Map();
185
+
186
+ while (true) {
187
+ const { done, value } = await reader.read();
188
+ if (done) break;
189
+
190
+ buffer += decoder.decode(value, { stream: true });
191
+ const lines = buffer.split('\n');
192
+ buffer = lines.pop() || '';
193
+
194
+ for (const line of lines) {
195
+ if (line.startsWith('data: ')) {
196
+ const data = line.slice(6);
197
+ if (data === '[DONE]') {
198
+ // 处理累积的工具调用
199
+ if (toolCallChunks.size > 0) {
200
+ const toolCalls = Array.from(toolCallChunks.values()).map(tc => ({
201
+ id: tc.id,
202
+ name: tc.name,
203
+ arguments: tc.arguments
204
+ }));
205
+ yield { type: 'tool_calls', toolCalls, done: false };
206
+ }
207
+ return;
208
+ }
209
+
210
+ try {
211
+ const json = JSON.parse(data);
212
+ const delta = json.choices?.[0]?.delta;
213
+
214
+ // 处理内容
215
+ if (delta?.content) {
216
+ yield { type: 'content', content: delta.content, done: false };
217
+ }
218
+
219
+ // 处理流式工具调用
220
+ if (delta?.tool_calls) {
221
+ for (const tc of delta.tool_calls) {
222
+ const idx = tc.index || 0;
223
+ if (!toolCallChunks.has(idx)) {
224
+ toolCallChunks.set(idx, { id: '', name: '', arguments: '' });
225
+ }
226
+ const chunk = toolCallChunks.get(idx);
227
+ if (tc.id) chunk.id = tc.id;
228
+ if (tc.function?.name) chunk.name = tc.function.name;
229
+ if (tc.function?.arguments) chunk.arguments += tc.function.arguments;
230
+ }
231
+ }
232
+ } catch (e) {
233
+ // 忽略解析错误
234
+ }
235
+ }
236
+ }
237
+ }
238
+
239
+ yield { done: true };
240
+ }
241
+
242
+ /**
243
+ * 获取模型列表
244
+ */
245
+ async fetchModels() {
246
+ const url = `${this.baseUrl}/models`;
247
+
248
+ const headers = {
249
+ 'Content-Type': 'application/json',
250
+ ...this.getAuthHeader(),
251
+ ...this.headers
252
+ };
253
+
254
+ const response = await fetch(url, {
255
+ method: 'GET',
256
+ headers,
257
+ signal: AbortSignal.timeout(10000)
258
+ });
259
+
260
+ if (!response.ok) {
261
+ throw new Error(`Failed to fetch models: ${response.status}`);
262
+ }
263
+
264
+ const data = await response.json();
265
+
266
+ // 解析模型列表
267
+ this.models = (data.data || data.models || [])
268
+ .map(m => typeof m === 'string' ? m : (m.id || m.name))
269
+ .filter(Boolean)
270
+ .sort();
271
+
272
+ return this.models;
273
+ }
274
+
275
+ /**
276
+ * 获取模型列表(本地缓存)
277
+ */
278
+ getModels() {
279
+ return this.models;
280
+ }
281
+
282
+ /**
283
+ * 获取认证头
284
+ */
285
+ getAuthHeader() {
286
+ if (this.skipAuth || !this.apiKey) {
287
+ return {};
288
+ }
289
+ return { 'Authorization': `Bearer ${this.apiKey}` };
290
+ }
291
+
292
+ /**
293
+ * Embedding API
294
+ */
295
+ async embeddings(input, model = 'text-embedding-3-small') {
296
+ if (!this.connected && !this.skipAuth) {
297
+ throw new Error(`Provider ${this.name} not connected`);
298
+ }
299
+
300
+ const url = `${this.baseUrl}/embeddings`;
301
+
302
+ const body = {
303
+ model,
304
+ input: Array.isArray(input) ? input : [input]
305
+ };
306
+
307
+ const headers = {
308
+ 'Content-Type': 'application/json',
309
+ ...this.getAuthHeader(),
310
+ ...this.headers
311
+ };
312
+
313
+ const response = await fetch(url, {
314
+ method: 'POST',
315
+ headers,
316
+ body: JSON.stringify(body)
317
+ });
318
+
319
+ if (!response.ok) {
320
+ const error = await response.json().catch(() => ({}));
321
+ throw new Error(error.error?.message || `Embedding API error: ${response.status}`);
322
+ }
323
+
324
+ const data = await response.json();
325
+ return data.data.map(d => d.embedding);
326
+ }
327
+
328
+ /**
329
+ * 获取状态
330
+ */
331
+ getStatus() {
332
+ return {
333
+ id: this.id,
334
+ name: this.name,
335
+ nameCn: this.nameCn,
336
+ baseUrl: this.baseUrl,
337
+ connected: this.connected,
338
+ modelCount: this.models.length,
339
+ defaultModel: this.defaultModel,
340
+ hasApiKey: !!this.apiKey
341
+ };
342
+ }
343
+ }
344
+
345
+ /**
346
+ * 预设 Provider 配置
347
+ * 只需要 baseUrl,其他用默认值
348
+ */
349
+ export const PRESET_PROVIDERS = {
350
+ // ═══════════════════════════════════════════════════════════
351
+ // 国际主流服务商
352
+ // ═══════════════════════════════════════════════════════════
353
+ openai: {
354
+ name: 'OpenAI',
355
+ nameCn: 'OpenAI',
356
+ baseUrl: 'https://api.openai.com/v1',
357
+ defaultModel: 'gpt-4o',
358
+ description: 'GPT-4, GPT-3.5, DALL-E, Whisper'
359
+ },
360
+ anthropic: {
361
+ name: 'Claude',
362
+ nameCn: 'Anthropic Claude',
363
+ baseUrl: 'https://api.anthropic.com/v1',
364
+ defaultModel: 'claude-sonnet-4-20250514',
365
+ description: 'Claude 3.5/4 系列',
366
+ special: true
367
+ },
368
+ google: {
369
+ name: 'Gemini',
370
+ nameCn: 'Google Gemini',
371
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta',
372
+ defaultModel: 'gemini-2.0-flash',
373
+ description: 'Gemini 2.0/1.5 Pro/Flash'
374
+ },
375
+ deepseek: {
376
+ name: 'DeepSeek',
377
+ nameCn: 'DeepSeek',
378
+ baseUrl: 'https://api.deepseek.com/v1',
379
+ defaultModel: 'deepseek-chat',
380
+ description: 'DeepSeek-V3, DeepSeek-Reasoner'
381
+ },
382
+ openrouter: {
383
+ name: 'OpenRouter',
384
+ nameCn: 'OpenRouter',
385
+ baseUrl: 'https://openrouter.ai/api/v1',
386
+ defaultModel: 'openrouter/auto',
387
+ description: '200+ 模型聚合平台'
388
+ },
389
+ groq: {
390
+ name: 'Groq',
391
+ nameCn: 'Groq',
392
+ baseUrl: 'https://api.groq.com/openai/v1',
393
+ defaultModel: 'llama-3.3-70b-versatile',
394
+ description: '极速推理,LPU芯片'
395
+ },
396
+ xai: {
397
+ name: 'xAI',
398
+ nameCn: 'xAI (Grok)',
399
+ baseUrl: 'https://api.x.ai/v1',
400
+ defaultModel: 'grok-2-latest',
401
+ description: 'Elon Musk 的 AI 公司'
402
+ },
403
+ mistral: {
404
+ name: 'Mistral',
405
+ nameCn: 'Mistral AI',
406
+ baseUrl: 'https://api.mistral.ai/v1',
407
+ defaultModel: 'mistral-large-latest',
408
+ description: '欧洲开源模型领导者'
409
+ },
410
+ cohere: {
411
+ name: 'Cohere',
412
+ nameCn: 'Cohere',
413
+ baseUrl: 'https://api.cohere.ai/v1',
414
+ defaultModel: 'command-r-plus',
415
+ description: '企业级 AI,擅长 RAG'
416
+ },
417
+ replicate: {
418
+ name: 'Replicate',
419
+ nameCn: 'Replicate',
420
+ baseUrl: 'https://api.replicate.com/v1',
421
+ defaultModel: 'meta/llama-2-70b-chat',
422
+ description: '云端运行开源模型'
423
+ },
424
+ together: {
425
+ name: 'Together',
426
+ nameCn: 'Together AI',
427
+ baseUrl: 'https://api.together.xyz/v1',
428
+ defaultModel: 'meta-llama/Llama-3-70b-chat-hf',
429
+ description: '开源模型云服务'
430
+ },
431
+ perplexity: {
432
+ name: 'Perplexity',
433
+ nameCn: 'Perplexity',
434
+ baseUrl: 'https://api.perplexity.ai',
435
+ defaultModel: 'llama-3.1-sonar-small-128k-online',
436
+ description: 'AI 搜索引擎'
437
+ },
438
+ fireworks: {
439
+ name: 'Fireworks',
440
+ nameCn: 'Fireworks AI',
441
+ baseUrl: 'https://api.fireworks.ai/inference/v1',
442
+ defaultModel: 'accounts/fireworks/models/llama-v3-70b',
443
+ description: '高速推理平台'
444
+ },
445
+ anyscale: {
446
+ name: 'Anyscale',
447
+ nameCn: 'Anyscale',
448
+ baseUrl: 'https://api.endpoints.anyscale.com/v1',
449
+ defaultModel: 'meta-llama/Llama-2-70b-chat-hf',
450
+ description: 'Ray 团队出品'
451
+ },
452
+ octoai: {
453
+ name: 'OctoAI',
454
+ nameCn: 'OctoAI',
455
+ baseUrl: 'https://text.octoai.run/v1',
456
+ defaultModel: 'meta-llama-3-70b-instruct',
457
+ description: '高效模型服务'
458
+ },
459
+ lepton: {
460
+ name: 'Lepton',
461
+ nameCn: 'Lepton AI',
462
+ baseUrl: 'https://api.lepton.ai/v1',
463
+ defaultModel: 'llama3-70b',
464
+ description: '一键部署 AI 应用'
465
+ },
466
+ predibase: {
467
+ name: 'Predibase',
468
+ nameCn: 'Predibase',
469
+ baseUrl: 'https://api.predibase.com/v1',
470
+ defaultModel: 'llama-2-70b',
471
+ description: '微调和部署平台'
472
+ },
473
+ nomic: {
474
+ name: 'Nomic',
475
+ nameCn: 'Nomic AI',
476
+ baseUrl: 'https://api-atlas.nomic.ai/v1',
477
+ defaultModel: 'nomic-embed-text-v1',
478
+ description: '向量嵌入专家'
479
+ },
480
+ voyage: {
481
+ name: 'Voyage',
482
+ nameCn: 'Voyage AI',
483
+ baseUrl: 'https://api.voyageai.com/v1',
484
+ defaultModel: 'voyage-large-2',
485
+ description: '高质量嵌入模型'
486
+ },
487
+ alephalpha: {
488
+ name: 'AlephAlpha',
489
+ nameCn: 'Aleph Alpha',
490
+ baseUrl: 'https://api.aleph-alpha.com/v1',
491
+ defaultModel: 'luminous-supreme',
492
+ description: '欧洲企业 AI'
493
+ },
494
+ ai21: {
495
+ name: 'AI21',
496
+ nameCn: 'AI21 Labs',
497
+ baseUrl: 'https://api.ai21.com/v1',
498
+ defaultModel: 'jamba-1-5-large',
499
+ description: 'Jamba 混合架构模型'
500
+ },
501
+ inflection: {
502
+ name: 'Inflection',
503
+ nameCn: 'Inflection AI',
504
+ baseUrl: 'https://api.inflection.ai/v1',
505
+ defaultModel: 'inflection-3-pi',
506
+ description: 'Pi 助手'
507
+ },
508
+ reka: {
509
+ name: 'Reka',
510
+ nameCn: 'Reka AI',
511
+ baseUrl: 'https://api.reka.ai/v1',
512
+ defaultModel: 'reka-core',
513
+ description: '多模态模型'
514
+ },
515
+ databricks: {
516
+ name: 'Databricks',
517
+ nameCn: 'Databricks',
518
+ baseUrl: 'https://models.databricks.com/v1',
519
+ defaultModel: 'databricks-dbrx-instruct',
520
+ description: '企业数据 AI'
521
+ },
522
+
523
+ // ═══════════════════════════════════════════════════════════
524
+ // 云服务商 AI
525
+ // ═══════════════════════════════════════════════════════════
526
+ azure: {
527
+ name: 'Azure',
528
+ nameCn: 'Azure OpenAI',
529
+ baseUrl: 'https://YOUR_RESOURCE.openai.azure.com/openai/deployments',
530
+ defaultModel: 'gpt-4o',
531
+ description: '微软 Azure 托管 OpenAI',
532
+ special: true
533
+ },
534
+ aws_bedrock: {
535
+ name: 'Bedrock',
536
+ nameCn: 'AWS Bedrock',
537
+ baseUrl: 'https://bedrock-runtime.us-east-1.amazonaws.com/v1',
538
+ defaultModel: 'anthropic.claude-3-sonnet',
539
+ description: 'AWS 托管模型服务',
540
+ special: true
541
+ },
542
+ vertex: {
543
+ name: 'Vertex',
544
+ nameCn: 'Google Vertex AI',
545
+ baseUrl: 'https://us-central1-aiplatform.googleapis.com/v1',
546
+ defaultModel: 'gemini-pro',
547
+ description: 'GCP 托管模型服务',
548
+ special: true
549
+ },
550
+
551
+ // ═══════════════════════════════════════════════════════════
552
+ // 国内服务商
553
+ // ═══════════════════════════════════════════════════════════
554
+ zhipu: {
555
+ name: 'GLM',
556
+ nameCn: '智谱GLM',
557
+ baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
558
+ defaultModel: 'glm-4',
559
+ description: '清华系,GLM-4 系列'
560
+ },
561
+ alibaba: {
562
+ name: 'Qwen',
563
+ nameCn: '通义千问',
564
+ baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
565
+ defaultModel: 'qwen-turbo',
566
+ description: '阿里云,Qwen 系列'
567
+ },
568
+ baidu: {
569
+ name: 'Qianfan',
570
+ nameCn: '百度千帆',
571
+ baseUrl: 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop',
572
+ defaultModel: 'ernie-4.0-8k',
573
+ description: '文心一言系列',
574
+ special: true
575
+ },
576
+ moonshot: {
577
+ name: 'Moonshot',
578
+ nameCn: 'Moonshot (Kimi)',
579
+ baseUrl: 'https://api.moonshot.cn/v1',
580
+ defaultModel: 'moonshot-v1-8k',
581
+ description: '长文本专家'
582
+ },
583
+ minimax: {
584
+ name: 'MiniMax',
585
+ nameCn: 'MiniMax',
586
+ baseUrl: 'https://api.minimax.chat/v1',
587
+ defaultModel: 'abab6.5s-chat',
588
+ description: '海螺AI'
589
+ },
590
+ siliconflow: {
591
+ name: 'SiliconFlow',
592
+ nameCn: '硅基流动',
593
+ baseUrl: 'https://api.siliconflow.cn/v1',
594
+ defaultModel: 'Qwen/Qwen2.5-72B-Instruct',
595
+ description: '国内模型聚合平台'
596
+ },
597
+ volcengine: {
598
+ name: 'VolcEngine',
599
+ nameCn: '火山引擎',
600
+ baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
601
+ defaultModel: 'doubao-pro-4k',
602
+ description: '字节跳动,豆包系列'
603
+ },
604
+ spark: {
605
+ name: 'Spark',
606
+ nameCn: '讯飞星火',
607
+ baseUrl: 'https://spark-api-open.xf-yun.com/v1',
608
+ defaultModel: 'generalv3.5',
609
+ description: '科大讯飞'
610
+ },
611
+ baichuan: {
612
+ name: 'Baichuan',
613
+ nameCn: '百川智能',
614
+ baseUrl: 'https://api.baichuan-ai.com/v1',
615
+ defaultModel: 'Baichuan4',
616
+ description: '前搜狗王小川'
617
+ },
618
+ yi: {
619
+ name: 'Yi',
620
+ nameCn: '零一万物',
621
+ baseUrl: 'https://api.lingyiwanwu.com/v1',
622
+ defaultModel: 'yi-large',
623
+ description: '李开复创立'
624
+ },
625
+ stepfun: {
626
+ name: 'StepFun',
627
+ nameCn: '阶跃星辰',
628
+ baseUrl: 'https://api.stepfun.com/v1',
629
+ defaultModel: 'step-1-8k',
630
+ description: '上海阶跃'
631
+ },
632
+ lingji: {
633
+ name: 'Lingji',
634
+ nameCn: '无问芯穹',
635
+ baseUrl: 'https://inference-model.lingji.ai/v1',
636
+ defaultModel: 'qwen1.5-110b-chat',
637
+ description: '清华系'
638
+ },
639
+ iflow: {
640
+ name: 'iFlow',
641
+ nameCn: '心流',
642
+ baseUrl: 'https://api.xinliu.ai/v1',
643
+ defaultModel: 'xinliu-7b-chat',
644
+ description: '中文优化模型'
645
+ },
646
+ bailian: {
647
+ name: 'Bailian',
648
+ nameCn: '阿里百炼',
649
+ baseUrl: 'https://bailian.cn-beijing.aliyuncs.com/v1',
650
+ defaultModel: 'qwen-plus',
651
+ description: '阿里云百炼平台'
652
+ },
653
+ tencent: {
654
+ name: 'Tencent',
655
+ nameCn: '腾讯混元',
656
+ baseUrl: 'https://hunyuan.tencentcloudapi.com/v1',
657
+ defaultModel: 'hunyuan-lite',
658
+ description: '腾讯混元大模型'
659
+ },
660
+ 360: {
661
+ name: '360Zhinao',
662
+ nameCn: '360智脑',
663
+ baseUrl: 'https://api.360.cn/v1',
664
+ defaultModel: '360gpt-pro',
665
+ description: '360 安全 AI'
666
+ },
667
+ langboat: {
668
+ name: 'Langboat',
669
+ nameCn: '澜舟科技',
670
+ baseUrl: 'https://api.langboat.com/v1',
671
+ defaultModel: 'mengzi-gpt',
672
+ description: '孟子模型'
673
+ },
674
+ sensetime: {
675
+ name: 'SenseTime',
676
+ nameCn: '商汤日日新',
677
+ baseUrl: 'https://api.sensenova.cn/v1',
678
+ defaultModel: 'nova-ptc-xl-v1',
679
+ description: '商汤科技'
680
+ },
681
+ unisound: {
682
+ name: 'Unisound',
683
+ nameCn: '云知声',
684
+ baseUrl: 'https://api.unisound.com/v1',
685
+ defaultModel: '山海大模型',
686
+ description: '语音 AI 专家'
687
+ },
688
+ teleai: {
689
+ name: 'TeleAI',
690
+ nameCn: '电信星辰',
691
+ baseUrl: 'https://api.teleai.cn/v1',
692
+ defaultModel: 'telechat-12b',
693
+ description: '中国电信'
694
+ },
695
+ mita: {
696
+ name: 'Mita',
697
+ nameCn: '秘塔AI',
698
+ baseUrl: 'https://api.metaso.cn/v1',
699
+ defaultModel: 'metaso-search',
700
+ description: 'AI 搜索'
701
+ },
702
+
703
+ // ═══════════════════════════════════════════════════════════
704
+ // 本地服务
705
+ // ═══════════════════════════════════════════════════════════
706
+ ollama: {
707
+ name: 'Ollama',
708
+ nameCn: 'Ollama',
709
+ baseUrl: 'http://localhost:11434/v1',
710
+ defaultModel: 'llama3',
711
+ skipAuth: true,
712
+ description: '本地运行,无需 API Key'
713
+ },
714
+ lmstudio: {
715
+ name: 'LM Studio',
716
+ nameCn: 'LM Studio',
717
+ baseUrl: 'http://localhost:1234/v1',
718
+ defaultModel: 'local-model',
719
+ skipAuth: true,
720
+ description: '本地运行,可视化界面'
721
+ },
722
+ vllm: {
723
+ name: 'vLLM',
724
+ nameCn: 'vLLM',
725
+ baseUrl: 'http://localhost:8000/v1',
726
+ defaultModel: 'meta-llama/Llama-2-70b-hf',
727
+ skipAuth: true,
728
+ description: '高性能推理引擎'
729
+ },
730
+ localai: {
731
+ name: 'LocalAI',
732
+ nameCn: 'LocalAI',
733
+ baseUrl: 'http://localhost:8080/v1',
734
+ defaultModel: 'gpt-3.5-turbo',
735
+ skipAuth: true,
736
+ description: 'OpenAI 兼容本地服务'
737
+ },
738
+ textgen: {
739
+ name: 'TextGen',
740
+ nameCn: 'Text Generation WebUI',
741
+ baseUrl: 'http://localhost:5000/v1',
742
+ defaultModel: 'model',
743
+ skipAuth: true,
744
+ description: 'Oobabooga WebUI'
745
+ },
746
+
747
+ // ═══════════════════════════════════════════════════════════
748
+ // 其他聚合/特殊平台
749
+ // ═══════════════════════════════════════════════════════════
750
+ huggingface: {
751
+ name: 'HuggingFace',
752
+ nameCn: 'HuggingFace',
753
+ baseUrl: 'https://api-inference.huggingface.co/models',
754
+ defaultModel: 'meta-llama/Llama-3.2-3B-Instruct',
755
+ description: '开源模型中心'
756
+ },
757
+ monsterapi: {
758
+ name: 'MonsterAPI',
759
+ nameCn: 'MonsterAPI',
760
+ baseUrl: 'https://api.monsterapi.ai/v1',
761
+ defaultModel: 'meta-llama/Llama-2-70b-chat-hf',
762
+ description: '低成本 AI API'
763
+ },
764
+ glidian: {
765
+ name: 'Glidian',
766
+ nameCn: 'Glidian',
767
+ baseUrl: 'https://api.glidian.com/v1',
768
+ defaultModel: 'gpt-4',
769
+ description: 'AI API 网关'
770
+ },
771
+ inferless: {
772
+ name: 'Inferless',
773
+ nameCn: 'Inferless',
774
+ baseUrl: 'https://api.inferless.com/v1',
775
+ defaultModel: 'llama-2-70b',
776
+ description: '无服务器推理'
777
+ },
778
+ cerebras: {
779
+ name: 'Cerebras',
780
+ nameCn: 'Cerebras AI',
781
+ baseUrl: 'https://api.cerebras.ai/v1',
782
+ defaultModel: 'llama3.1-70b',
783
+ description: '极速推理芯片'
784
+ },
785
+ sambanova: {
786
+ name: 'SambaNova',
787
+ nameCn: 'SambaNova',
788
+ baseUrl: 'https://api.sambanova.ai/v1',
789
+ defaultModel: 'Meta-Llama-3.1-70B-Instruct',
790
+ description: '企业 AI 平台'
791
+ }
792
+ };
793
+
794
+ /**
795
+ * 创建 Provider 实例
796
+ */
797
+ export function createProvider(providerId, apiKey = null, overrides = {}) {
798
+ const preset = PRESET_PROVIDERS[providerId];
799
+
800
+ if (!preset) {
801
+ // 未知 provider,尝试用通用配置
802
+ return new OpenAICompatibleProvider({
803
+ id: providerId,
804
+ name: providerId,
805
+ baseUrl: overrides.baseUrl || `https://api.${providerId}.com/v1`,
806
+ apiKey,
807
+ ...overrides
808
+ });
809
+ }
810
+
811
+ // 特殊 provider 需要单独处理
812
+ if (preset.special) {
813
+ // 将在单独文件中处理
814
+ console.log(`[Provider] ${providerId} requires special adapter`);
815
+ }
816
+
817
+ return new OpenAICompatibleProvider({
818
+ id: providerId,
819
+ ...preset,
820
+ apiKey,
821
+ ...overrides
822
+ });
823
+ }
824
+
825
+ /**
826
+ * 获取所有预设 Provider
827
+ */
828
+ export function listPresetProviders() {
829
+ return Object.entries(PRESET_PROVIDERS).map(([id, config]) => ({
830
+ id,
831
+ name: config.name,
832
+ nameCn: config.nameCn,
833
+ defaultModel: config.defaultModel,
834
+ description: config.description || '',
835
+ skipAuth: config.skipAuth || false,
836
+ special: config.special || false
837
+ }));
838
+ }
839
+
840
+ export default OpenAICompatibleProvider;