@weisiren000/oiiai 0.1.2 → 0.1.4
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.
- package/README.md +165 -201
- package/dist/index.d.mts +339 -15
- package/dist/index.d.ts +339 -15
- package/dist/index.js +1648 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1640 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +59 -55
package/dist/index.js
CHANGED
|
@@ -21,43 +21,395 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
BaseProvider: () => BaseProvider,
|
|
24
|
-
|
|
24
|
+
EFFORT_TOKEN_MAP: () => EFFORT_TOKEN_MAP,
|
|
25
|
+
GeminiProvider: () => GeminiProvider,
|
|
26
|
+
GroqProvider: () => GroqProvider,
|
|
27
|
+
HuggingFaceProvider: () => HuggingFaceProvider,
|
|
28
|
+
ModelScopeProvider: () => ModelScopeProvider,
|
|
29
|
+
OpenRouterProvider: () => OpenRouterProvider,
|
|
30
|
+
ai: () => ai,
|
|
31
|
+
createProvider: () => createProvider
|
|
25
32
|
});
|
|
26
33
|
module.exports = __toCommonJS(index_exports);
|
|
27
34
|
|
|
35
|
+
// src/providers/openrouter.ts
|
|
36
|
+
var import_sdk = require("@openrouter/sdk");
|
|
37
|
+
|
|
38
|
+
// src/providers/__model-detection__.ts
|
|
39
|
+
var THINKING_MODEL_PATTERNS = [
|
|
40
|
+
// 明确的思考/推理标识
|
|
41
|
+
/[-_]think(?:ing)?(?:[-_:]|$)/i,
|
|
42
|
+
// *-think, *-thinking
|
|
43
|
+
/[-_]reason(?:ing)?(?:[-_:]|$)/i,
|
|
44
|
+
// *-reason, *-reasoning
|
|
45
|
+
/[-_]cot(?:[-_:]|$)/i,
|
|
46
|
+
// chain-of-thought
|
|
47
|
+
/[-_]reflect(?:ion)?(?:[-_:]|$)/i,
|
|
48
|
+
// reflection models
|
|
49
|
+
// 知名推理模型系列
|
|
50
|
+
/\bo1[-_]?/i,
|
|
51
|
+
// OpenAI o1 系列
|
|
52
|
+
/\bo3[-_]?/i,
|
|
53
|
+
// OpenAI o3 系列
|
|
54
|
+
/\br1[-_]?/i,
|
|
55
|
+
// DeepSeek R1 等
|
|
56
|
+
/\bqwq\b/i,
|
|
57
|
+
// Qwen QwQ
|
|
58
|
+
/\bn1[-_]?/i,
|
|
59
|
+
// nex-n1 等
|
|
60
|
+
// 通用思考关键词(较低优先级)
|
|
61
|
+
/think/i
|
|
62
|
+
// 包含 think
|
|
63
|
+
];
|
|
64
|
+
var DIRECT_ANSWER_PATTERNS = [
|
|
65
|
+
/[-_]chat$/i,
|
|
66
|
+
// *-chat (结尾)
|
|
67
|
+
/[-_]instruct/i,
|
|
68
|
+
// *-instruct
|
|
69
|
+
/[-_]turbo/i,
|
|
70
|
+
// *-turbo
|
|
71
|
+
/[-_]flash/i,
|
|
72
|
+
// gemini-flash 等快速模型
|
|
73
|
+
/[-_]lite[-_v]/i,
|
|
74
|
+
// lite 版本(但不匹配 lite 结尾,避免误判)
|
|
75
|
+
/[-_]fast/i
|
|
76
|
+
// fast 模型
|
|
77
|
+
];
|
|
78
|
+
var PROBLEMATIC_MODEL_PATTERNS = [
|
|
79
|
+
/nova[-_]?\d*[-_]lite/i
|
|
80
|
+
// Amazon Nova Lite 系列
|
|
81
|
+
];
|
|
82
|
+
function isProblematicModel(modelId) {
|
|
83
|
+
return PROBLEMATIC_MODEL_PATTERNS.some((pattern) => pattern.test(modelId));
|
|
84
|
+
}
|
|
85
|
+
function detectByModelName(modelId) {
|
|
86
|
+
const normalizedId = modelId.toLowerCase();
|
|
87
|
+
const isThinkingPattern = THINKING_MODEL_PATTERNS.some(
|
|
88
|
+
(pattern) => pattern.test(normalizedId)
|
|
89
|
+
);
|
|
90
|
+
const isDirectPattern = DIRECT_ANSWER_PATTERNS.some(
|
|
91
|
+
(pattern) => pattern.test(normalizedId)
|
|
92
|
+
);
|
|
93
|
+
const isProblematic = isProblematicModel(normalizedId);
|
|
94
|
+
if (isThinkingPattern) {
|
|
95
|
+
return {
|
|
96
|
+
behavior: "thinking-first",
|
|
97
|
+
supportsReasoningConfig: true,
|
|
98
|
+
recommendedMinTokens: 500,
|
|
99
|
+
confidence: 0.7,
|
|
100
|
+
detectedBy: "pattern"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (isDirectPattern) {
|
|
104
|
+
return {
|
|
105
|
+
behavior: "direct-answer",
|
|
106
|
+
supportsReasoningConfig: false,
|
|
107
|
+
// 问题模型需要更多 token
|
|
108
|
+
recommendedMinTokens: isProblematic ? 300 : 100,
|
|
109
|
+
confidence: 0.6,
|
|
110
|
+
detectedBy: "pattern"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
behavior: "unknown",
|
|
115
|
+
supportsReasoningConfig: false,
|
|
116
|
+
recommendedMinTokens: isProblematic ? 300 : 200,
|
|
117
|
+
confidence: 0.3,
|
|
118
|
+
detectedBy: "pattern"
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
var modelCharacteristicsCache = /* @__PURE__ */ new Map();
|
|
122
|
+
function detectByResponse(modelId, result) {
|
|
123
|
+
const hasReasoning = !!result.reasoning && result.reasoning.length > 0;
|
|
124
|
+
const hasContent = !!result.content && result.content.trim().length > 0;
|
|
125
|
+
const reasoningLength = result.reasoning?.length ?? 0;
|
|
126
|
+
const contentLength = result.content?.length ?? 0;
|
|
127
|
+
let behavior;
|
|
128
|
+
let supportsReasoningConfig = false;
|
|
129
|
+
let recommendedMinTokens = 200;
|
|
130
|
+
if (hasReasoning && !hasContent) {
|
|
131
|
+
behavior = "thinking-first";
|
|
132
|
+
supportsReasoningConfig = true;
|
|
133
|
+
recommendedMinTokens = Math.max(500, reasoningLength + 200);
|
|
134
|
+
} else if (hasReasoning && hasContent) {
|
|
135
|
+
if (reasoningLength > contentLength * 2) {
|
|
136
|
+
behavior = "thinking-first";
|
|
137
|
+
supportsReasoningConfig = true;
|
|
138
|
+
recommendedMinTokens = 500;
|
|
139
|
+
} else {
|
|
140
|
+
behavior = "hybrid";
|
|
141
|
+
supportsReasoningConfig = true;
|
|
142
|
+
recommendedMinTokens = 300;
|
|
143
|
+
}
|
|
144
|
+
} else if (hasContent && !hasReasoning) {
|
|
145
|
+
behavior = "direct-answer";
|
|
146
|
+
supportsReasoningConfig = false;
|
|
147
|
+
recommendedMinTokens = 100;
|
|
148
|
+
} else {
|
|
149
|
+
behavior = "unknown";
|
|
150
|
+
recommendedMinTokens = 500;
|
|
151
|
+
}
|
|
152
|
+
const characteristics = {
|
|
153
|
+
behavior,
|
|
154
|
+
supportsReasoningConfig,
|
|
155
|
+
recommendedMinTokens,
|
|
156
|
+
confidence: 0.9,
|
|
157
|
+
detectedBy: "runtime"
|
|
158
|
+
};
|
|
159
|
+
modelCharacteristicsCache.set(modelId, characteristics);
|
|
160
|
+
return characteristics;
|
|
161
|
+
}
|
|
162
|
+
function getModelCharacteristics(modelId) {
|
|
163
|
+
const cached = modelCharacteristicsCache.get(modelId);
|
|
164
|
+
if (cached) {
|
|
165
|
+
return { ...cached, detectedBy: "cache" };
|
|
166
|
+
}
|
|
167
|
+
return detectByModelName(modelId);
|
|
168
|
+
}
|
|
169
|
+
var DEFAULT_FALLBACK_CONFIG = {
|
|
170
|
+
enabled: true,
|
|
171
|
+
returnReasoningAsContent: true,
|
|
172
|
+
extractConclusionFromReasoning: true,
|
|
173
|
+
autoRetryWithMoreTokens: false,
|
|
174
|
+
// 默认关闭自动重试,避免额外消耗
|
|
175
|
+
retryTokenIncrement: 300,
|
|
176
|
+
maxRetries: 2
|
|
177
|
+
};
|
|
178
|
+
function extractConclusionFromReasoning(reasoning) {
|
|
179
|
+
if (!reasoning) return null;
|
|
180
|
+
const conclusionPatterns = [
|
|
181
|
+
/(?:therefore|thus|so|hence|finally|in conclusion|the answer is|result is)[:\s]*(.+?)(?:\n|$)/i,
|
|
182
|
+
/(?:答案是|结论是|因此|所以|最终)[::\s]*(.+?)(?:\n|$)/,
|
|
183
|
+
/(?:\*\*answer\*\*|\*\*result\*\*)[:\s]*(.+?)(?:\n|$)/i,
|
|
184
|
+
/=\s*(.+?)(?:\n|$)/
|
|
185
|
+
// 数学等式结果
|
|
186
|
+
];
|
|
187
|
+
for (const pattern of conclusionPatterns) {
|
|
188
|
+
const match = reasoning.match(pattern);
|
|
189
|
+
if (match && match[1]) {
|
|
190
|
+
return match[1].trim();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const paragraphs = reasoning.split(/\n\n+/).filter((p) => p.trim());
|
|
194
|
+
if (paragraphs.length > 0) {
|
|
195
|
+
const lastParagraph = paragraphs[paragraphs.length - 1].trim();
|
|
196
|
+
if (lastParagraph.length < 500) {
|
|
197
|
+
return lastParagraph;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
function applyFallbackStrategy(result, config = {}) {
|
|
203
|
+
const finalConfig = { ...DEFAULT_FALLBACK_CONFIG, ...config };
|
|
204
|
+
if (result.content && result.content.trim().length > 0) {
|
|
205
|
+
return {
|
|
206
|
+
content: result.content,
|
|
207
|
+
didFallback: false,
|
|
208
|
+
originalReasoning: result.reasoning
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (!finalConfig.enabled) {
|
|
212
|
+
return {
|
|
213
|
+
content: "",
|
|
214
|
+
didFallback: false,
|
|
215
|
+
originalReasoning: result.reasoning
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
if (finalConfig.extractConclusionFromReasoning && result.reasoning) {
|
|
219
|
+
const conclusion = extractConclusionFromReasoning(result.reasoning);
|
|
220
|
+
if (conclusion) {
|
|
221
|
+
return {
|
|
222
|
+
content: conclusion,
|
|
223
|
+
didFallback: true,
|
|
224
|
+
fallbackReason: "extracted_conclusion_from_reasoning",
|
|
225
|
+
originalReasoning: result.reasoning
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (finalConfig.returnReasoningAsContent && result.reasoning) {
|
|
230
|
+
return {
|
|
231
|
+
content: result.reasoning,
|
|
232
|
+
didFallback: true,
|
|
233
|
+
fallbackReason: "returned_reasoning_as_content",
|
|
234
|
+
originalReasoning: result.reasoning
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
content: "",
|
|
239
|
+
didFallback: false,
|
|
240
|
+
fallbackReason: "no_fallback_available",
|
|
241
|
+
originalReasoning: result.reasoning
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function adjustOptionsForModel(modelId, options) {
|
|
245
|
+
const characteristics = getModelCharacteristics(modelId);
|
|
246
|
+
if (characteristics.behavior === "thinking-first" && (!options.maxTokens || options.maxTokens < characteristics.recommendedMinTokens)) {
|
|
247
|
+
return {
|
|
248
|
+
...options,
|
|
249
|
+
maxTokens: Math.max(
|
|
250
|
+
options.maxTokens ?? 0,
|
|
251
|
+
characteristics.recommendedMinTokens
|
|
252
|
+
)
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return options;
|
|
256
|
+
}
|
|
257
|
+
function getRecommendedConfig(modelId, scenario = "simple") {
|
|
258
|
+
const characteristics = getModelCharacteristics(modelId);
|
|
259
|
+
if (characteristics.behavior !== "thinking-first") {
|
|
260
|
+
return {};
|
|
261
|
+
}
|
|
262
|
+
const configs = {
|
|
263
|
+
simple: {
|
|
264
|
+
maxTokens: 300,
|
|
265
|
+
reasoning: { effort: "low" }
|
|
266
|
+
},
|
|
267
|
+
math: {
|
|
268
|
+
maxTokens: 600,
|
|
269
|
+
reasoning: { effort: "high" }
|
|
270
|
+
},
|
|
271
|
+
reasoning: {
|
|
272
|
+
maxTokens: 800,
|
|
273
|
+
reasoning: { effort: "medium" }
|
|
274
|
+
},
|
|
275
|
+
fast: {
|
|
276
|
+
maxTokens: 200,
|
|
277
|
+
reasoning: { effort: "off" }
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
return configs[scenario] ?? configs.simple;
|
|
281
|
+
}
|
|
282
|
+
var ModelDetection = {
|
|
283
|
+
detectByModelName,
|
|
284
|
+
detectByResponse,
|
|
285
|
+
getModelCharacteristics,
|
|
286
|
+
applyFallbackStrategy,
|
|
287
|
+
adjustOptionsForModel,
|
|
288
|
+
getRecommendedConfig,
|
|
289
|
+
extractConclusionFromReasoning,
|
|
290
|
+
isProblematicModel,
|
|
291
|
+
clearCache: () => modelCharacteristicsCache.clear()
|
|
292
|
+
};
|
|
293
|
+
|
|
28
294
|
// src/providers/__base__.ts
|
|
29
295
|
var BaseProvider = class {
|
|
296
|
+
/** 降级策略配置 */
|
|
297
|
+
fallbackConfig = DEFAULT_FALLBACK_CONFIG;
|
|
298
|
+
/** 是否启用自动参数调整 */
|
|
299
|
+
autoAdjustEnabled = true;
|
|
300
|
+
/**
|
|
301
|
+
* 配置降级策略
|
|
302
|
+
*/
|
|
303
|
+
configureFallback(config) {
|
|
304
|
+
this.fallbackConfig = { ...this.fallbackConfig, ...config };
|
|
305
|
+
return this;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* 启用/禁用自动参数调整
|
|
309
|
+
*/
|
|
310
|
+
setAutoAdjust(enabled) {
|
|
311
|
+
this.autoAdjustEnabled = enabled;
|
|
312
|
+
return this;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* 获取模型特性信息
|
|
316
|
+
*/
|
|
317
|
+
getModelCharacteristics(modelId) {
|
|
318
|
+
return ModelDetection.getModelCharacteristics(modelId);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* 智能聊天:自动检测模型特性并应用降级策略
|
|
322
|
+
*/
|
|
323
|
+
async chatSmart(options) {
|
|
324
|
+
const adjustedOptions = this.autoAdjustEnabled ? ModelDetection.adjustOptionsForModel(options.model, options) : options;
|
|
325
|
+
const result = await this.chat(adjustedOptions);
|
|
326
|
+
ModelDetection.detectByResponse(options.model, result);
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
30
329
|
/**
|
|
31
330
|
* 简单对话:单轮问答(默认实现)
|
|
32
|
-
*
|
|
331
|
+
*
|
|
332
|
+
* 智能处理思考模型:
|
|
333
|
+
* 1. 自动检测模型类型
|
|
334
|
+
* 2. 为思考模型自动调整 maxTokens
|
|
335
|
+
* 3. 如果 content 为空,智能降级(提取结论或返回 reasoning)
|
|
33
336
|
*/
|
|
34
337
|
async ask(model, question, options) {
|
|
35
|
-
const
|
|
338
|
+
const { fallback, autoAdjust = this.autoAdjustEnabled, ...chatOptions } = options ?? {};
|
|
339
|
+
let finalOptions = {
|
|
36
340
|
model,
|
|
37
341
|
messages: [{ role: "user", content: question }],
|
|
38
|
-
...
|
|
342
|
+
...chatOptions
|
|
343
|
+
};
|
|
344
|
+
if (autoAdjust) {
|
|
345
|
+
finalOptions = ModelDetection.adjustOptionsForModel(model, finalOptions);
|
|
346
|
+
}
|
|
347
|
+
const result = await this.chat(finalOptions);
|
|
348
|
+
ModelDetection.detectByResponse(model, result);
|
|
349
|
+
const fallbackResult = ModelDetection.applyFallbackStrategy(result, {
|
|
350
|
+
...this.fallbackConfig,
|
|
351
|
+
...fallback
|
|
39
352
|
});
|
|
40
|
-
return
|
|
353
|
+
return fallbackResult.content;
|
|
41
354
|
}
|
|
42
355
|
/**
|
|
43
356
|
* 带系统提示的对话(默认实现)
|
|
44
|
-
*
|
|
357
|
+
*
|
|
358
|
+
* 智能处理思考模型:
|
|
359
|
+
* 1. 自动检测模型类型
|
|
360
|
+
* 2. 为思考模型自动调整 maxTokens
|
|
361
|
+
* 3. 如果 content 为空,智能降级(提取结论或返回 reasoning)
|
|
45
362
|
*/
|
|
46
363
|
async askWithSystem(model, systemPrompt, userMessage, options) {
|
|
47
|
-
const
|
|
364
|
+
const { fallback, autoAdjust = this.autoAdjustEnabled, ...chatOptions } = options ?? {};
|
|
365
|
+
let finalOptions = {
|
|
48
366
|
model,
|
|
49
367
|
messages: [
|
|
50
368
|
{ role: "system", content: systemPrompt },
|
|
51
369
|
{ role: "user", content: userMessage }
|
|
52
370
|
],
|
|
371
|
+
...chatOptions
|
|
372
|
+
};
|
|
373
|
+
if (autoAdjust) {
|
|
374
|
+
finalOptions = ModelDetection.adjustOptionsForModel(model, finalOptions);
|
|
375
|
+
}
|
|
376
|
+
const result = await this.chat(finalOptions);
|
|
377
|
+
ModelDetection.detectByResponse(model, result);
|
|
378
|
+
const fallbackResult = ModelDetection.applyFallbackStrategy(result, {
|
|
379
|
+
...this.fallbackConfig,
|
|
380
|
+
...fallback
|
|
381
|
+
});
|
|
382
|
+
return fallbackResult.content;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* 场景化问答:根据场景自动配置参数
|
|
386
|
+
*
|
|
387
|
+
* @param model 模型 ID
|
|
388
|
+
* @param question 问题
|
|
389
|
+
* @param scenario 场景类型
|
|
390
|
+
* - 'simple': 简单问答(默认)
|
|
391
|
+
* - 'math': 数学计算
|
|
392
|
+
* - 'reasoning': 逻辑推理
|
|
393
|
+
* - 'fast': 快速回答(关闭思考)
|
|
394
|
+
*/
|
|
395
|
+
async askWithScenario(model, question, scenario = "simple", options) {
|
|
396
|
+
const recommendedConfig = ModelDetection.getRecommendedConfig(model, scenario);
|
|
397
|
+
return this.ask(model, question, {
|
|
398
|
+
...recommendedConfig,
|
|
53
399
|
...options
|
|
54
400
|
});
|
|
55
|
-
return result.content || result.reasoning || "";
|
|
56
401
|
}
|
|
57
402
|
};
|
|
58
403
|
|
|
404
|
+
// src/providers/__types__.ts
|
|
405
|
+
var EFFORT_TOKEN_MAP = {
|
|
406
|
+
off: 0,
|
|
407
|
+
low: 1024,
|
|
408
|
+
medium: 4096,
|
|
409
|
+
high: 16384
|
|
410
|
+
};
|
|
411
|
+
|
|
59
412
|
// src/providers/openrouter.ts
|
|
60
|
-
var import_sdk = require("@openrouter/sdk");
|
|
61
413
|
function extractTextContent(content) {
|
|
62
414
|
if (typeof content === "string") {
|
|
63
415
|
return content;
|
|
@@ -71,19 +423,19 @@ function extractTextContent(content) {
|
|
|
71
423
|
}
|
|
72
424
|
function buildReasoningParam(config) {
|
|
73
425
|
if (!config) return void 0;
|
|
426
|
+
if (config.effort === "off") return void 0;
|
|
74
427
|
const param = {};
|
|
75
|
-
if (config.effort
|
|
428
|
+
if (config.effort) {
|
|
76
429
|
param.effort = config.effort;
|
|
77
430
|
}
|
|
78
|
-
if (config.
|
|
79
|
-
param.max_tokens = config.
|
|
431
|
+
if (config.budgetTokens !== void 0) {
|
|
432
|
+
param.max_tokens = config.budgetTokens;
|
|
433
|
+
} else if (config.effort && EFFORT_TOKEN_MAP[config.effort]) {
|
|
434
|
+
param.max_tokens = EFFORT_TOKEN_MAP[config.effort];
|
|
80
435
|
}
|
|
81
436
|
if (config.exclude !== void 0) {
|
|
82
437
|
param.exclude = config.exclude;
|
|
83
438
|
}
|
|
84
|
-
if (config.enabled !== void 0) {
|
|
85
|
-
param.enabled = config.enabled;
|
|
86
|
-
}
|
|
87
439
|
return Object.keys(param).length > 0 ? param : void 0;
|
|
88
440
|
}
|
|
89
441
|
var OpenRouterProvider = class extends BaseProvider {
|
|
@@ -97,7 +449,13 @@ var OpenRouterProvider = class extends BaseProvider {
|
|
|
97
449
|
* 发送聊天请求(非流式)
|
|
98
450
|
*/
|
|
99
451
|
async chat(options) {
|
|
100
|
-
const {
|
|
452
|
+
const {
|
|
453
|
+
model,
|
|
454
|
+
messages,
|
|
455
|
+
temperature = 0.7,
|
|
456
|
+
maxTokens,
|
|
457
|
+
reasoning
|
|
458
|
+
} = options;
|
|
101
459
|
const reasoningParam = buildReasoningParam(reasoning);
|
|
102
460
|
const requestParams = {
|
|
103
461
|
model,
|
|
@@ -132,7 +490,13 @@ var OpenRouterProvider = class extends BaseProvider {
|
|
|
132
490
|
* 发送流式聊天请求
|
|
133
491
|
*/
|
|
134
492
|
async *chatStream(options) {
|
|
135
|
-
const {
|
|
493
|
+
const {
|
|
494
|
+
model,
|
|
495
|
+
messages,
|
|
496
|
+
temperature = 0.7,
|
|
497
|
+
maxTokens,
|
|
498
|
+
reasoning
|
|
499
|
+
} = options;
|
|
136
500
|
const reasoningParam = buildReasoningParam(reasoning);
|
|
137
501
|
const requestParams = {
|
|
138
502
|
model,
|
|
@@ -144,7 +508,9 @@ var OpenRouterProvider = class extends BaseProvider {
|
|
|
144
508
|
if (reasoningParam) {
|
|
145
509
|
requestParams.reasoning = reasoningParam;
|
|
146
510
|
}
|
|
147
|
-
const stream = await this.client.chat.send(
|
|
511
|
+
const stream = await this.client.chat.send(
|
|
512
|
+
requestParams
|
|
513
|
+
);
|
|
148
514
|
for await (const chunk of stream) {
|
|
149
515
|
const delta = chunk.choices?.[0]?.delta;
|
|
150
516
|
if (!delta) continue;
|
|
@@ -186,9 +552,1272 @@ var OpenRouterProvider = class extends BaseProvider {
|
|
|
186
552
|
}));
|
|
187
553
|
}
|
|
188
554
|
};
|
|
555
|
+
|
|
556
|
+
// src/providers/gemini.ts
|
|
557
|
+
var BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai";
|
|
558
|
+
function extractTextContent2(content) {
|
|
559
|
+
if (typeof content === "string") {
|
|
560
|
+
return content;
|
|
561
|
+
}
|
|
562
|
+
if (Array.isArray(content)) {
|
|
563
|
+
return content.filter(
|
|
564
|
+
(item) => typeof item === "object" && item !== null && item.type === "text" && typeof item.text === "string"
|
|
565
|
+
).map((item) => item.text).join("");
|
|
566
|
+
}
|
|
567
|
+
return "";
|
|
568
|
+
}
|
|
569
|
+
var GeminiProvider = class extends BaseProvider {
|
|
570
|
+
name = "gemini";
|
|
571
|
+
apiKey;
|
|
572
|
+
baseUrl;
|
|
573
|
+
constructor(config) {
|
|
574
|
+
super();
|
|
575
|
+
if (typeof config === "string") {
|
|
576
|
+
this.apiKey = config;
|
|
577
|
+
this.baseUrl = BASE_URL;
|
|
578
|
+
} else {
|
|
579
|
+
this.apiKey = config.apiKey;
|
|
580
|
+
this.baseUrl = config.baseUrl ?? BASE_URL;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* 发送聊天请求(非流式)
|
|
585
|
+
*/
|
|
586
|
+
async chat(options) {
|
|
587
|
+
const {
|
|
588
|
+
model,
|
|
589
|
+
messages,
|
|
590
|
+
temperature = 0.7,
|
|
591
|
+
maxTokens,
|
|
592
|
+
reasoning
|
|
593
|
+
} = options;
|
|
594
|
+
const body = {
|
|
595
|
+
model,
|
|
596
|
+
messages,
|
|
597
|
+
temperature,
|
|
598
|
+
stream: false
|
|
599
|
+
};
|
|
600
|
+
if (maxTokens) {
|
|
601
|
+
body.max_tokens = maxTokens;
|
|
602
|
+
}
|
|
603
|
+
if (reasoning?.effort && reasoning.effort !== "off") {
|
|
604
|
+
body.reasoning_effort = reasoning.effort;
|
|
605
|
+
}
|
|
606
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
607
|
+
method: "POST",
|
|
608
|
+
headers: {
|
|
609
|
+
"Content-Type": "application/json",
|
|
610
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
611
|
+
},
|
|
612
|
+
body: JSON.stringify(body)
|
|
613
|
+
});
|
|
614
|
+
if (!response.ok) {
|
|
615
|
+
const error = await response.text();
|
|
616
|
+
throw new Error(`Gemini API error: ${response.status} ${error}`);
|
|
617
|
+
}
|
|
618
|
+
const result = await response.json();
|
|
619
|
+
const choice = result.choices?.[0];
|
|
620
|
+
if (!choice) {
|
|
621
|
+
throw new Error("No response from model");
|
|
622
|
+
}
|
|
623
|
+
const msg = choice.message;
|
|
624
|
+
const reasoningContent = msg?.reasoning_content ?? null;
|
|
625
|
+
return {
|
|
626
|
+
content: extractTextContent2(msg?.content),
|
|
627
|
+
reasoning: reasoningContent ? extractTextContent2(reasoningContent) : null,
|
|
628
|
+
model: result.model ?? model,
|
|
629
|
+
usage: {
|
|
630
|
+
promptTokens: result.usage?.prompt_tokens ?? 0,
|
|
631
|
+
completionTokens: result.usage?.completion_tokens ?? 0,
|
|
632
|
+
totalTokens: result.usage?.total_tokens ?? 0
|
|
633
|
+
},
|
|
634
|
+
finishReason: choice.finish_reason ?? null
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* 发送流式聊天请求
|
|
639
|
+
*/
|
|
640
|
+
async *chatStream(options) {
|
|
641
|
+
const {
|
|
642
|
+
model,
|
|
643
|
+
messages,
|
|
644
|
+
temperature = 0.7,
|
|
645
|
+
maxTokens,
|
|
646
|
+
reasoning
|
|
647
|
+
} = options;
|
|
648
|
+
const body = {
|
|
649
|
+
model,
|
|
650
|
+
messages,
|
|
651
|
+
temperature,
|
|
652
|
+
stream: true
|
|
653
|
+
};
|
|
654
|
+
if (maxTokens) {
|
|
655
|
+
body.max_tokens = maxTokens;
|
|
656
|
+
}
|
|
657
|
+
if (reasoning?.effort && reasoning.effort !== "off") {
|
|
658
|
+
body.reasoning_effort = reasoning.effort;
|
|
659
|
+
}
|
|
660
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
661
|
+
method: "POST",
|
|
662
|
+
headers: {
|
|
663
|
+
"Content-Type": "application/json",
|
|
664
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
665
|
+
},
|
|
666
|
+
body: JSON.stringify(body)
|
|
667
|
+
});
|
|
668
|
+
if (!response.ok) {
|
|
669
|
+
const error = await response.text();
|
|
670
|
+
throw new Error(`Gemini API error: ${response.status} ${error}`);
|
|
671
|
+
}
|
|
672
|
+
const reader = response.body?.getReader();
|
|
673
|
+
if (!reader) {
|
|
674
|
+
throw new Error("No response body");
|
|
675
|
+
}
|
|
676
|
+
const decoder = new TextDecoder();
|
|
677
|
+
let buffer = "";
|
|
678
|
+
try {
|
|
679
|
+
while (true) {
|
|
680
|
+
const { done, value } = await reader.read();
|
|
681
|
+
if (done) break;
|
|
682
|
+
buffer += decoder.decode(value, { stream: true });
|
|
683
|
+
const lines = buffer.split("\n");
|
|
684
|
+
buffer = lines.pop() ?? "";
|
|
685
|
+
for (const line of lines) {
|
|
686
|
+
const trimmed = line.trim();
|
|
687
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
688
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
689
|
+
try {
|
|
690
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
691
|
+
const delta = data.choices?.[0]?.delta;
|
|
692
|
+
if (!delta) continue;
|
|
693
|
+
const thought = delta.reasoning_content ?? delta.thoughts;
|
|
694
|
+
if (thought) {
|
|
695
|
+
yield {
|
|
696
|
+
type: "reasoning",
|
|
697
|
+
text: extractTextContent2(thought)
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
if (delta.content) {
|
|
701
|
+
yield {
|
|
702
|
+
type: "content",
|
|
703
|
+
text: extractTextContent2(delta.content)
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
} catch {
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
} finally {
|
|
711
|
+
reader.releaseLock();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
// src/providers/groq.ts
|
|
717
|
+
var BASE_URL2 = "https://api.groq.com/openai/v1";
|
|
718
|
+
function extractTextContent3(content) {
|
|
719
|
+
if (typeof content === "string") {
|
|
720
|
+
return content;
|
|
721
|
+
}
|
|
722
|
+
if (Array.isArray(content)) {
|
|
723
|
+
return content.filter(
|
|
724
|
+
(item) => typeof item === "object" && item !== null && item.type === "text" && typeof item.text === "string"
|
|
725
|
+
).map((item) => item.text).join("");
|
|
726
|
+
}
|
|
727
|
+
return "";
|
|
728
|
+
}
|
|
729
|
+
var GroqProvider = class extends BaseProvider {
|
|
730
|
+
name = "groq";
|
|
731
|
+
apiKey;
|
|
732
|
+
baseUrl;
|
|
733
|
+
constructor(config) {
|
|
734
|
+
super();
|
|
735
|
+
if (typeof config === "string") {
|
|
736
|
+
this.apiKey = config;
|
|
737
|
+
this.baseUrl = BASE_URL2;
|
|
738
|
+
} else {
|
|
739
|
+
this.apiKey = config.apiKey;
|
|
740
|
+
this.baseUrl = config.baseUrl ?? BASE_URL2;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* 发送聊天请求(非流式)
|
|
745
|
+
*/
|
|
746
|
+
async chat(options) {
|
|
747
|
+
const { model, messages, temperature = 1, maxTokens, reasoning } = options;
|
|
748
|
+
const body = {
|
|
749
|
+
model,
|
|
750
|
+
messages,
|
|
751
|
+
temperature,
|
|
752
|
+
stream: false,
|
|
753
|
+
top_p: 1
|
|
754
|
+
};
|
|
755
|
+
if (maxTokens) {
|
|
756
|
+
body.max_completion_tokens = maxTokens;
|
|
757
|
+
}
|
|
758
|
+
if (reasoning?.effort && reasoning.effort !== "off") {
|
|
759
|
+
body.reasoning_format = "parsed";
|
|
760
|
+
} else if (reasoning?.effort === "off") {
|
|
761
|
+
body.include_reasoning = false;
|
|
762
|
+
}
|
|
763
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
764
|
+
method: "POST",
|
|
765
|
+
headers: {
|
|
766
|
+
"Content-Type": "application/json",
|
|
767
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
768
|
+
},
|
|
769
|
+
body: JSON.stringify(body)
|
|
770
|
+
});
|
|
771
|
+
if (!response.ok) {
|
|
772
|
+
const error = await response.text();
|
|
773
|
+
throw new Error(`Groq API error: ${response.status} ${error}`);
|
|
774
|
+
}
|
|
775
|
+
const result = await response.json();
|
|
776
|
+
const choice = result.choices?.[0];
|
|
777
|
+
if (!choice) {
|
|
778
|
+
throw new Error("No response from model");
|
|
779
|
+
}
|
|
780
|
+
const msg = choice.message;
|
|
781
|
+
const reasoningContent = msg?.reasoning_content ?? msg?.reasoning ?? null;
|
|
782
|
+
return {
|
|
783
|
+
content: extractTextContent3(msg?.content),
|
|
784
|
+
reasoning: reasoningContent ? extractTextContent3(reasoningContent) : null,
|
|
785
|
+
model: result.model ?? model,
|
|
786
|
+
usage: {
|
|
787
|
+
promptTokens: result.usage?.prompt_tokens ?? 0,
|
|
788
|
+
completionTokens: result.usage?.completion_tokens ?? 0,
|
|
789
|
+
totalTokens: result.usage?.total_tokens ?? 0
|
|
790
|
+
},
|
|
791
|
+
finishReason: choice.finish_reason ?? null
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* 发送流式聊天请求
|
|
796
|
+
*/
|
|
797
|
+
async *chatStream(options) {
|
|
798
|
+
const { model, messages, temperature = 1, maxTokens, reasoning } = options;
|
|
799
|
+
const body = {
|
|
800
|
+
model,
|
|
801
|
+
messages,
|
|
802
|
+
temperature,
|
|
803
|
+
stream: true,
|
|
804
|
+
top_p: 1
|
|
805
|
+
};
|
|
806
|
+
if (maxTokens) {
|
|
807
|
+
body.max_completion_tokens = maxTokens;
|
|
808
|
+
}
|
|
809
|
+
if (reasoning?.effort && reasoning.effort !== "off") {
|
|
810
|
+
body.reasoning_format = "parsed";
|
|
811
|
+
} else if (reasoning?.effort === "off") {
|
|
812
|
+
body.include_reasoning = false;
|
|
813
|
+
}
|
|
814
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
815
|
+
method: "POST",
|
|
816
|
+
headers: {
|
|
817
|
+
"Content-Type": "application/json",
|
|
818
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
819
|
+
},
|
|
820
|
+
body: JSON.stringify(body)
|
|
821
|
+
});
|
|
822
|
+
if (!response.ok) {
|
|
823
|
+
const error = await response.text();
|
|
824
|
+
throw new Error(`Groq API error: ${response.status} ${error}`);
|
|
825
|
+
}
|
|
826
|
+
const reader = response.body?.getReader();
|
|
827
|
+
if (!reader) {
|
|
828
|
+
throw new Error("No response body");
|
|
829
|
+
}
|
|
830
|
+
const decoder = new TextDecoder();
|
|
831
|
+
let buffer = "";
|
|
832
|
+
try {
|
|
833
|
+
while (true) {
|
|
834
|
+
const { done, value } = await reader.read();
|
|
835
|
+
if (done) break;
|
|
836
|
+
buffer += decoder.decode(value, { stream: true });
|
|
837
|
+
const lines = buffer.split("\n");
|
|
838
|
+
buffer = lines.pop() ?? "";
|
|
839
|
+
for (const line of lines) {
|
|
840
|
+
const trimmed = line.trim();
|
|
841
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
842
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
843
|
+
try {
|
|
844
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
845
|
+
const delta = data.choices?.[0]?.delta;
|
|
846
|
+
if (!delta) continue;
|
|
847
|
+
const reasoningContent = delta.reasoning_content ?? delta.reasoning;
|
|
848
|
+
if (reasoningContent) {
|
|
849
|
+
yield {
|
|
850
|
+
type: "reasoning",
|
|
851
|
+
text: extractTextContent3(reasoningContent)
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
if (delta.content) {
|
|
855
|
+
yield {
|
|
856
|
+
type: "content",
|
|
857
|
+
text: extractTextContent3(delta.content)
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
} catch {
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
} finally {
|
|
865
|
+
reader.releaseLock();
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
// src/providers/huggingface.ts
|
|
871
|
+
var BASE_URL3 = "https://router.huggingface.co/v1";
|
|
872
|
+
function extractTextContent4(content) {
|
|
873
|
+
if (typeof content === "string") {
|
|
874
|
+
return content;
|
|
875
|
+
}
|
|
876
|
+
if (Array.isArray(content)) {
|
|
877
|
+
return content.filter(
|
|
878
|
+
(item) => typeof item === "object" && item !== null && item.type === "text" && typeof item.text === "string"
|
|
879
|
+
).map((item) => item.text).join("");
|
|
880
|
+
}
|
|
881
|
+
return "";
|
|
882
|
+
}
|
|
883
|
+
var HuggingFaceProvider = class extends BaseProvider {
|
|
884
|
+
name = "huggingface";
|
|
885
|
+
apiKey;
|
|
886
|
+
baseUrl;
|
|
887
|
+
constructor(config) {
|
|
888
|
+
super();
|
|
889
|
+
if (typeof config === "string") {
|
|
890
|
+
this.apiKey = config;
|
|
891
|
+
this.baseUrl = BASE_URL3;
|
|
892
|
+
} else {
|
|
893
|
+
this.apiKey = config.apiKey;
|
|
894
|
+
this.baseUrl = config.baseUrl ?? BASE_URL3;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* 发送聊天请求(非流式)
|
|
899
|
+
*
|
|
900
|
+
* reasoning 参数说明:
|
|
901
|
+
* - HuggingFace 是模型聚合平台,thinking 支持取决于具体模型
|
|
902
|
+
* - 如果模型支持,会返回 reasoning_content
|
|
903
|
+
*/
|
|
904
|
+
async chat(options) {
|
|
905
|
+
const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
|
|
906
|
+
const body = {
|
|
907
|
+
model,
|
|
908
|
+
messages,
|
|
909
|
+
temperature,
|
|
910
|
+
stream: false
|
|
911
|
+
};
|
|
912
|
+
if (maxTokens) {
|
|
913
|
+
body.max_tokens = maxTokens;
|
|
914
|
+
}
|
|
915
|
+
if (reasoning?.effort && reasoning.effort !== "off") {
|
|
916
|
+
body.reasoning_effort = reasoning.effort;
|
|
917
|
+
}
|
|
918
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
919
|
+
method: "POST",
|
|
920
|
+
headers: {
|
|
921
|
+
"Content-Type": "application/json",
|
|
922
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
923
|
+
},
|
|
924
|
+
body: JSON.stringify(body)
|
|
925
|
+
});
|
|
926
|
+
if (!response.ok) {
|
|
927
|
+
const error = await response.text();
|
|
928
|
+
throw new Error(`HuggingFace API error: ${response.status} ${error}`);
|
|
929
|
+
}
|
|
930
|
+
const result = await response.json();
|
|
931
|
+
const choice = result.choices?.[0];
|
|
932
|
+
if (!choice) {
|
|
933
|
+
throw new Error("No response from model");
|
|
934
|
+
}
|
|
935
|
+
const msg = choice.message;
|
|
936
|
+
const reasoningContent = msg?.reasoning_content ?? null;
|
|
937
|
+
return {
|
|
938
|
+
content: extractTextContent4(msg?.content),
|
|
939
|
+
reasoning: reasoningContent ? extractTextContent4(reasoningContent) : null,
|
|
940
|
+
model: result.model ?? model,
|
|
941
|
+
usage: {
|
|
942
|
+
promptTokens: result.usage?.prompt_tokens ?? 0,
|
|
943
|
+
completionTokens: result.usage?.completion_tokens ?? 0,
|
|
944
|
+
totalTokens: result.usage?.total_tokens ?? 0
|
|
945
|
+
},
|
|
946
|
+
finishReason: choice.finish_reason ?? null
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* 发送流式聊天请求
|
|
951
|
+
*/
|
|
952
|
+
async *chatStream(options) {
|
|
953
|
+
const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
|
|
954
|
+
const body = {
|
|
955
|
+
model,
|
|
956
|
+
messages,
|
|
957
|
+
temperature,
|
|
958
|
+
stream: true
|
|
959
|
+
};
|
|
960
|
+
if (maxTokens) {
|
|
961
|
+
body.max_tokens = maxTokens;
|
|
962
|
+
}
|
|
963
|
+
if (reasoning?.effort && reasoning.effort !== "off") {
|
|
964
|
+
body.reasoning_effort = reasoning.effort;
|
|
965
|
+
}
|
|
966
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
967
|
+
method: "POST",
|
|
968
|
+
headers: {
|
|
969
|
+
"Content-Type": "application/json",
|
|
970
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
971
|
+
},
|
|
972
|
+
body: JSON.stringify(body)
|
|
973
|
+
});
|
|
974
|
+
if (!response.ok) {
|
|
975
|
+
const error = await response.text();
|
|
976
|
+
throw new Error(`HuggingFace API error: ${response.status} ${error}`);
|
|
977
|
+
}
|
|
978
|
+
const reader = response.body?.getReader();
|
|
979
|
+
if (!reader) {
|
|
980
|
+
throw new Error("No response body");
|
|
981
|
+
}
|
|
982
|
+
const decoder = new TextDecoder();
|
|
983
|
+
let buffer = "";
|
|
984
|
+
try {
|
|
985
|
+
while (true) {
|
|
986
|
+
const { done, value } = await reader.read();
|
|
987
|
+
if (done) break;
|
|
988
|
+
buffer += decoder.decode(value, { stream: true });
|
|
989
|
+
const lines = buffer.split("\n");
|
|
990
|
+
buffer = lines.pop() ?? "";
|
|
991
|
+
for (const line of lines) {
|
|
992
|
+
const trimmed = line.trim();
|
|
993
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
994
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
995
|
+
try {
|
|
996
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
997
|
+
const delta = data.choices?.[0]?.delta;
|
|
998
|
+
if (!delta) continue;
|
|
999
|
+
if (delta.reasoning_content) {
|
|
1000
|
+
yield {
|
|
1001
|
+
type: "reasoning",
|
|
1002
|
+
text: extractTextContent4(delta.reasoning_content)
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
if (delta.content) {
|
|
1006
|
+
yield {
|
|
1007
|
+
type: "content",
|
|
1008
|
+
text: extractTextContent4(delta.content)
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
} finally {
|
|
1016
|
+
reader.releaseLock();
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
// src/providers/modelscope.ts
|
|
1022
|
+
var BASE_URL4 = "https://api-inference.modelscope.cn/v1";
|
|
1023
|
+
function extractTextContent5(content) {
|
|
1024
|
+
if (typeof content === "string") {
|
|
1025
|
+
return content;
|
|
1026
|
+
}
|
|
1027
|
+
if (Array.isArray(content)) {
|
|
1028
|
+
return content.filter(
|
|
1029
|
+
(item) => typeof item === "object" && item !== null && item.type === "text" && typeof item.text === "string"
|
|
1030
|
+
).map((item) => item.text).join("");
|
|
1031
|
+
}
|
|
1032
|
+
return "";
|
|
1033
|
+
}
|
|
1034
|
+
var ModelScopeProvider = class extends BaseProvider {
|
|
1035
|
+
name = "modelscope";
|
|
1036
|
+
apiKey;
|
|
1037
|
+
baseUrl;
|
|
1038
|
+
constructor(config) {
|
|
1039
|
+
super();
|
|
1040
|
+
if (typeof config === "string") {
|
|
1041
|
+
this.apiKey = config;
|
|
1042
|
+
this.baseUrl = BASE_URL4;
|
|
1043
|
+
} else {
|
|
1044
|
+
this.apiKey = config.apiKey;
|
|
1045
|
+
this.baseUrl = config.baseUrl ?? BASE_URL4;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* 发送聊天请求(非流式)
|
|
1050
|
+
*/
|
|
1051
|
+
async chat(options) {
|
|
1052
|
+
const {
|
|
1053
|
+
model,
|
|
1054
|
+
messages,
|
|
1055
|
+
temperature = 0.7,
|
|
1056
|
+
maxTokens,
|
|
1057
|
+
reasoning
|
|
1058
|
+
} = options;
|
|
1059
|
+
const body = {
|
|
1060
|
+
model,
|
|
1061
|
+
messages,
|
|
1062
|
+
temperature,
|
|
1063
|
+
stream: false
|
|
1064
|
+
};
|
|
1065
|
+
if (maxTokens) {
|
|
1066
|
+
body.max_tokens = maxTokens;
|
|
1067
|
+
}
|
|
1068
|
+
if (reasoning?.effort) {
|
|
1069
|
+
if (reasoning.effort === "off") {
|
|
1070
|
+
body.enable_thinking = false;
|
|
1071
|
+
} else {
|
|
1072
|
+
body.enable_thinking = true;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
1076
|
+
method: "POST",
|
|
1077
|
+
headers: {
|
|
1078
|
+
"Content-Type": "application/json",
|
|
1079
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1080
|
+
},
|
|
1081
|
+
body: JSON.stringify(body)
|
|
1082
|
+
});
|
|
1083
|
+
if (!response.ok) {
|
|
1084
|
+
const error = await response.text();
|
|
1085
|
+
throw new Error(`ModelScope API error: ${response.status} ${error}`);
|
|
1086
|
+
}
|
|
1087
|
+
const result = await response.json();
|
|
1088
|
+
const choice = result.choices?.[0];
|
|
1089
|
+
if (!choice) {
|
|
1090
|
+
throw new Error("No response from model");
|
|
1091
|
+
}
|
|
1092
|
+
const msg = choice.message;
|
|
1093
|
+
const reasoningContent = msg?.reasoning_content ?? null;
|
|
1094
|
+
return {
|
|
1095
|
+
content: extractTextContent5(msg?.content),
|
|
1096
|
+
reasoning: reasoningContent ? extractTextContent5(reasoningContent) : null,
|
|
1097
|
+
model: result.model ?? model,
|
|
1098
|
+
usage: {
|
|
1099
|
+
promptTokens: result.usage?.prompt_tokens ?? 0,
|
|
1100
|
+
completionTokens: result.usage?.completion_tokens ?? 0,
|
|
1101
|
+
totalTokens: result.usage?.total_tokens ?? 0
|
|
1102
|
+
},
|
|
1103
|
+
finishReason: choice.finish_reason ?? null
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* 发送流式聊天请求
|
|
1108
|
+
*/
|
|
1109
|
+
async *chatStream(options) {
|
|
1110
|
+
const {
|
|
1111
|
+
model,
|
|
1112
|
+
messages,
|
|
1113
|
+
temperature = 0.7,
|
|
1114
|
+
maxTokens,
|
|
1115
|
+
reasoning
|
|
1116
|
+
} = options;
|
|
1117
|
+
const body = {
|
|
1118
|
+
model,
|
|
1119
|
+
messages,
|
|
1120
|
+
temperature,
|
|
1121
|
+
stream: true
|
|
1122
|
+
};
|
|
1123
|
+
if (maxTokens) {
|
|
1124
|
+
body.max_tokens = maxTokens;
|
|
1125
|
+
}
|
|
1126
|
+
if (reasoning?.effort) {
|
|
1127
|
+
if (reasoning.effort === "off") {
|
|
1128
|
+
body.enable_thinking = false;
|
|
1129
|
+
} else {
|
|
1130
|
+
body.enable_thinking = true;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
1134
|
+
method: "POST",
|
|
1135
|
+
headers: {
|
|
1136
|
+
"Content-Type": "application/json",
|
|
1137
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1138
|
+
},
|
|
1139
|
+
body: JSON.stringify(body)
|
|
1140
|
+
});
|
|
1141
|
+
if (!response.ok) {
|
|
1142
|
+
const error = await response.text();
|
|
1143
|
+
throw new Error(`ModelScope API error: ${response.status} ${error}`);
|
|
1144
|
+
}
|
|
1145
|
+
const reader = response.body?.getReader();
|
|
1146
|
+
if (!reader) {
|
|
1147
|
+
throw new Error("No response body");
|
|
1148
|
+
}
|
|
1149
|
+
const decoder = new TextDecoder();
|
|
1150
|
+
let buffer = "";
|
|
1151
|
+
try {
|
|
1152
|
+
while (true) {
|
|
1153
|
+
const { done, value } = await reader.read();
|
|
1154
|
+
if (done) break;
|
|
1155
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1156
|
+
const lines = buffer.split("\n");
|
|
1157
|
+
buffer = lines.pop() ?? "";
|
|
1158
|
+
for (const line of lines) {
|
|
1159
|
+
const trimmed = line.trim();
|
|
1160
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
1161
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
1162
|
+
try {
|
|
1163
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
1164
|
+
const delta = data.choices?.[0]?.delta;
|
|
1165
|
+
if (!delta) continue;
|
|
1166
|
+
if (delta.reasoning_content) {
|
|
1167
|
+
yield {
|
|
1168
|
+
type: "reasoning",
|
|
1169
|
+
text: extractTextContent5(delta.reasoning_content)
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
if (delta.content) {
|
|
1173
|
+
yield {
|
|
1174
|
+
type: "content",
|
|
1175
|
+
text: extractTextContent5(delta.content)
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
} catch {
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
} finally {
|
|
1183
|
+
reader.releaseLock();
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
// src/providers/deepseek.ts
|
|
1189
|
+
var BASE_URL5 = "https://api.deepseek.com";
|
|
1190
|
+
function extractTextContent6(content) {
|
|
1191
|
+
if (typeof content === "string") {
|
|
1192
|
+
return content;
|
|
1193
|
+
}
|
|
1194
|
+
if (Array.isArray(content)) {
|
|
1195
|
+
return content.filter(
|
|
1196
|
+
(item) => typeof item === "object" && item !== null && item.type === "text" && typeof item.text === "string"
|
|
1197
|
+
).map((item) => item.text).join("");
|
|
1198
|
+
}
|
|
1199
|
+
return "";
|
|
1200
|
+
}
|
|
1201
|
+
var DeepSeekProvider = class extends BaseProvider {
|
|
1202
|
+
name = "deepseek";
|
|
1203
|
+
apiKey;
|
|
1204
|
+
baseUrl;
|
|
1205
|
+
constructor(config) {
|
|
1206
|
+
super();
|
|
1207
|
+
if (typeof config === "string") {
|
|
1208
|
+
this.apiKey = config;
|
|
1209
|
+
this.baseUrl = BASE_URL5;
|
|
1210
|
+
} else {
|
|
1211
|
+
this.apiKey = config.apiKey;
|
|
1212
|
+
this.baseUrl = config.baseUrl ?? BASE_URL5;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* 发送聊天请求(非流式)
|
|
1217
|
+
*
|
|
1218
|
+
* reasoning 参数说明:
|
|
1219
|
+
* - effort 不为 'off' 时启用 thinking 模式
|
|
1220
|
+
* - maxTokens 独立控制输出长度,不受 effort 影响
|
|
1221
|
+
*/
|
|
1222
|
+
async chat(options) {
|
|
1223
|
+
const {
|
|
1224
|
+
model,
|
|
1225
|
+
messages,
|
|
1226
|
+
temperature = 0.7,
|
|
1227
|
+
maxTokens,
|
|
1228
|
+
reasoning
|
|
1229
|
+
} = options;
|
|
1230
|
+
const body = {
|
|
1231
|
+
model,
|
|
1232
|
+
messages,
|
|
1233
|
+
temperature,
|
|
1234
|
+
stream: false
|
|
1235
|
+
};
|
|
1236
|
+
if (maxTokens) {
|
|
1237
|
+
body.max_tokens = maxTokens;
|
|
1238
|
+
}
|
|
1239
|
+
if (reasoning?.effort && reasoning.effort !== "off") {
|
|
1240
|
+
body.thinking = { type: "enabled" };
|
|
1241
|
+
}
|
|
1242
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
1243
|
+
method: "POST",
|
|
1244
|
+
headers: {
|
|
1245
|
+
"Content-Type": "application/json",
|
|
1246
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1247
|
+
},
|
|
1248
|
+
body: JSON.stringify(body)
|
|
1249
|
+
});
|
|
1250
|
+
if (!response.ok) {
|
|
1251
|
+
const error = await response.text();
|
|
1252
|
+
throw new Error(`DeepSeek API error: ${response.status} ${error}`);
|
|
1253
|
+
}
|
|
1254
|
+
const result = await response.json();
|
|
1255
|
+
const choice = result.choices?.[0];
|
|
1256
|
+
if (!choice) {
|
|
1257
|
+
throw new Error("No response from model");
|
|
1258
|
+
}
|
|
1259
|
+
const msg = choice.message;
|
|
1260
|
+
const reasoningContent = msg?.reasoning_content ?? null;
|
|
1261
|
+
return {
|
|
1262
|
+
content: extractTextContent6(msg?.content),
|
|
1263
|
+
reasoning: reasoningContent ? extractTextContent6(reasoningContent) : null,
|
|
1264
|
+
model: result.model ?? model,
|
|
1265
|
+
usage: {
|
|
1266
|
+
promptTokens: result.usage?.prompt_tokens ?? 0,
|
|
1267
|
+
completionTokens: result.usage?.completion_tokens ?? 0,
|
|
1268
|
+
totalTokens: result.usage?.total_tokens ?? 0
|
|
1269
|
+
},
|
|
1270
|
+
finishReason: choice.finish_reason ?? null
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* 发送流式聊天请求
|
|
1275
|
+
*/
|
|
1276
|
+
async *chatStream(options) {
|
|
1277
|
+
const {
|
|
1278
|
+
model,
|
|
1279
|
+
messages,
|
|
1280
|
+
temperature = 0.7,
|
|
1281
|
+
maxTokens,
|
|
1282
|
+
reasoning
|
|
1283
|
+
} = options;
|
|
1284
|
+
const body = {
|
|
1285
|
+
model,
|
|
1286
|
+
messages,
|
|
1287
|
+
temperature,
|
|
1288
|
+
stream: true
|
|
1289
|
+
};
|
|
1290
|
+
if (maxTokens) {
|
|
1291
|
+
body.max_tokens = maxTokens;
|
|
1292
|
+
}
|
|
1293
|
+
if (reasoning?.effort && reasoning.effort !== "off") {
|
|
1294
|
+
body.thinking = { type: "enabled" };
|
|
1295
|
+
}
|
|
1296
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
1297
|
+
method: "POST",
|
|
1298
|
+
headers: {
|
|
1299
|
+
"Content-Type": "application/json",
|
|
1300
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1301
|
+
},
|
|
1302
|
+
body: JSON.stringify(body)
|
|
1303
|
+
});
|
|
1304
|
+
if (!response.ok) {
|
|
1305
|
+
const error = await response.text();
|
|
1306
|
+
throw new Error(`DeepSeek API error: ${response.status} ${error}`);
|
|
1307
|
+
}
|
|
1308
|
+
const reader = response.body?.getReader();
|
|
1309
|
+
if (!reader) {
|
|
1310
|
+
throw new Error("No response body");
|
|
1311
|
+
}
|
|
1312
|
+
const decoder = new TextDecoder();
|
|
1313
|
+
let buffer = "";
|
|
1314
|
+
try {
|
|
1315
|
+
while (true) {
|
|
1316
|
+
const { done, value } = await reader.read();
|
|
1317
|
+
if (done) break;
|
|
1318
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1319
|
+
const lines = buffer.split("\n");
|
|
1320
|
+
buffer = lines.pop() ?? "";
|
|
1321
|
+
for (const line of lines) {
|
|
1322
|
+
const trimmed = line.trim();
|
|
1323
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
1324
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
1325
|
+
try {
|
|
1326
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
1327
|
+
const delta = data.choices?.[0]?.delta;
|
|
1328
|
+
if (!delta) continue;
|
|
1329
|
+
if (delta.reasoning_content) {
|
|
1330
|
+
yield {
|
|
1331
|
+
type: "reasoning",
|
|
1332
|
+
text: extractTextContent6(delta.reasoning_content)
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
if (delta.content) {
|
|
1336
|
+
yield {
|
|
1337
|
+
type: "content",
|
|
1338
|
+
text: extractTextContent6(delta.content)
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
} catch {
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
} finally {
|
|
1346
|
+
reader.releaseLock();
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
|
|
1351
|
+
// src/providers/poe.ts
|
|
1352
|
+
var BASE_URL6 = "https://api.poe.com/v1";
|
|
1353
|
+
function extractTextContent7(content) {
|
|
1354
|
+
if (typeof content === "string") {
|
|
1355
|
+
return content;
|
|
1356
|
+
}
|
|
1357
|
+
if (Array.isArray(content)) {
|
|
1358
|
+
return content.filter(
|
|
1359
|
+
(item) => typeof item === "object" && item !== null && item.type === "text" && typeof item.text === "string"
|
|
1360
|
+
).map((item) => item.text).join("");
|
|
1361
|
+
}
|
|
1362
|
+
return "";
|
|
1363
|
+
}
|
|
1364
|
+
function extractThinkingFromContent(content) {
|
|
1365
|
+
const thinkMatch = content.match(/<think>([\s\S]*?)<\/think>/);
|
|
1366
|
+
if (thinkMatch) {
|
|
1367
|
+
const thinking = thinkMatch[1].trim();
|
|
1368
|
+
const cleanContent = content.replace(/<think>[\s\S]*?<\/think>/, "").trim();
|
|
1369
|
+
return { thinking, content: cleanContent };
|
|
1370
|
+
}
|
|
1371
|
+
const thinkingMatch = content.match(
|
|
1372
|
+
/^\*Thinking\.{0,3}\*\s*\n((?:>.*(?:\n|$))+)/
|
|
1373
|
+
);
|
|
1374
|
+
if (thinkingMatch) {
|
|
1375
|
+
const thinking = thinkingMatch[1].split("\n").map((line) => line.replace(/^>\s?/, "")).join("\n").trim();
|
|
1376
|
+
const cleanContent = content.replace(thinkingMatch[0], "").trim();
|
|
1377
|
+
return { thinking, content: cleanContent };
|
|
1378
|
+
}
|
|
1379
|
+
return { thinking: "", content };
|
|
1380
|
+
}
|
|
1381
|
+
function buildExtraBody(reasoning) {
|
|
1382
|
+
if (!reasoning || reasoning.effort === "off") {
|
|
1383
|
+
return void 0;
|
|
1384
|
+
}
|
|
1385
|
+
const extraBody = {};
|
|
1386
|
+
if (reasoning.effort) {
|
|
1387
|
+
extraBody.reasoning_effort = reasoning.effort;
|
|
1388
|
+
}
|
|
1389
|
+
if (reasoning.budgetTokens !== void 0) {
|
|
1390
|
+
extraBody.thinking_budget = reasoning.budgetTokens;
|
|
1391
|
+
} else if (reasoning.effort && EFFORT_TOKEN_MAP[reasoning.effort]) {
|
|
1392
|
+
extraBody.thinking_budget = EFFORT_TOKEN_MAP[reasoning.effort];
|
|
1393
|
+
}
|
|
1394
|
+
return Object.keys(extraBody).length > 0 ? extraBody : void 0;
|
|
1395
|
+
}
|
|
1396
|
+
var PoeProvider = class extends BaseProvider {
|
|
1397
|
+
name = "poe";
|
|
1398
|
+
apiKey;
|
|
1399
|
+
baseUrl;
|
|
1400
|
+
constructor(config) {
|
|
1401
|
+
super();
|
|
1402
|
+
if (typeof config === "string") {
|
|
1403
|
+
this.apiKey = config;
|
|
1404
|
+
this.baseUrl = BASE_URL6;
|
|
1405
|
+
} else {
|
|
1406
|
+
this.apiKey = config.apiKey;
|
|
1407
|
+
this.baseUrl = config.baseUrl ?? BASE_URL6;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* 发送聊天请求(非流式)
|
|
1412
|
+
*/
|
|
1413
|
+
async chat(options) {
|
|
1414
|
+
const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
|
|
1415
|
+
const body = {
|
|
1416
|
+
model,
|
|
1417
|
+
messages,
|
|
1418
|
+
temperature,
|
|
1419
|
+
stream: false
|
|
1420
|
+
};
|
|
1421
|
+
if (maxTokens) {
|
|
1422
|
+
body.max_tokens = maxTokens;
|
|
1423
|
+
}
|
|
1424
|
+
const extraBody = buildExtraBody(reasoning);
|
|
1425
|
+
if (extraBody) {
|
|
1426
|
+
Object.assign(body, extraBody);
|
|
1427
|
+
}
|
|
1428
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
1429
|
+
method: "POST",
|
|
1430
|
+
headers: {
|
|
1431
|
+
"Content-Type": "application/json",
|
|
1432
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1433
|
+
},
|
|
1434
|
+
body: JSON.stringify(body)
|
|
1435
|
+
});
|
|
1436
|
+
if (!response.ok) {
|
|
1437
|
+
const error = await response.text();
|
|
1438
|
+
throw new Error(`Poe API error: ${response.status} ${error}`);
|
|
1439
|
+
}
|
|
1440
|
+
const result = await response.json();
|
|
1441
|
+
const choice = result.choices?.[0];
|
|
1442
|
+
if (!choice) {
|
|
1443
|
+
throw new Error("No response from model");
|
|
1444
|
+
}
|
|
1445
|
+
const msg = choice.message;
|
|
1446
|
+
let reasoningContent = msg?.reasoning_content ?? null;
|
|
1447
|
+
let contentText = extractTextContent7(msg?.content);
|
|
1448
|
+
if (!reasoningContent && contentText) {
|
|
1449
|
+
const extracted = extractThinkingFromContent(contentText);
|
|
1450
|
+
if (extracted.thinking) {
|
|
1451
|
+
reasoningContent = extracted.thinking;
|
|
1452
|
+
contentText = extracted.content;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
return {
|
|
1456
|
+
content: contentText,
|
|
1457
|
+
reasoning: reasoningContent ? extractTextContent7(reasoningContent) : null,
|
|
1458
|
+
model: result.model ?? model,
|
|
1459
|
+
usage: {
|
|
1460
|
+
promptTokens: result.usage?.prompt_tokens ?? 0,
|
|
1461
|
+
completionTokens: result.usage?.completion_tokens ?? 0,
|
|
1462
|
+
totalTokens: result.usage?.total_tokens ?? 0
|
|
1463
|
+
},
|
|
1464
|
+
finishReason: choice.finish_reason ?? null
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* 发送流式聊天请求
|
|
1469
|
+
*/
|
|
1470
|
+
async *chatStream(options) {
|
|
1471
|
+
const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
|
|
1472
|
+
const body = {
|
|
1473
|
+
model,
|
|
1474
|
+
messages,
|
|
1475
|
+
temperature,
|
|
1476
|
+
stream: true
|
|
1477
|
+
};
|
|
1478
|
+
if (maxTokens) {
|
|
1479
|
+
body.max_tokens = maxTokens;
|
|
1480
|
+
}
|
|
1481
|
+
const extraBody = buildExtraBody(reasoning);
|
|
1482
|
+
if (extraBody) {
|
|
1483
|
+
Object.assign(body, extraBody);
|
|
1484
|
+
}
|
|
1485
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
1486
|
+
method: "POST",
|
|
1487
|
+
headers: {
|
|
1488
|
+
"Content-Type": "application/json",
|
|
1489
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1490
|
+
},
|
|
1491
|
+
body: JSON.stringify(body)
|
|
1492
|
+
});
|
|
1493
|
+
if (!response.ok) {
|
|
1494
|
+
const error = await response.text();
|
|
1495
|
+
throw new Error(`Poe API error: ${response.status} ${error}`);
|
|
1496
|
+
}
|
|
1497
|
+
const reader = response.body?.getReader();
|
|
1498
|
+
if (!reader) {
|
|
1499
|
+
throw new Error("No response body");
|
|
1500
|
+
}
|
|
1501
|
+
const decoder = new TextDecoder();
|
|
1502
|
+
let buffer = "";
|
|
1503
|
+
let thinkingMode = "none";
|
|
1504
|
+
let contentBuffer = "";
|
|
1505
|
+
try {
|
|
1506
|
+
while (true) {
|
|
1507
|
+
const { done, value } = await reader.read();
|
|
1508
|
+
if (done) break;
|
|
1509
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1510
|
+
const lines = buffer.split("\n");
|
|
1511
|
+
buffer = lines.pop() ?? "";
|
|
1512
|
+
for (const line of lines) {
|
|
1513
|
+
const trimmed = line.trim();
|
|
1514
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
1515
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
1516
|
+
try {
|
|
1517
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
1518
|
+
const delta = data.choices?.[0]?.delta;
|
|
1519
|
+
if (!delta) continue;
|
|
1520
|
+
if (delta.reasoning_content) {
|
|
1521
|
+
yield {
|
|
1522
|
+
type: "reasoning",
|
|
1523
|
+
text: extractTextContent7(delta.reasoning_content)
|
|
1524
|
+
};
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
if (delta.content) {
|
|
1528
|
+
const text = extractTextContent7(delta.content);
|
|
1529
|
+
contentBuffer += text;
|
|
1530
|
+
while (true) {
|
|
1531
|
+
if (thinkingMode === "none") {
|
|
1532
|
+
const thinkStart = contentBuffer.indexOf("<think>");
|
|
1533
|
+
if (thinkStart !== -1) {
|
|
1534
|
+
if (thinkStart > 0) {
|
|
1535
|
+
yield { type: "content", text: contentBuffer.slice(0, thinkStart) };
|
|
1536
|
+
}
|
|
1537
|
+
contentBuffer = contentBuffer.slice(thinkStart + 7);
|
|
1538
|
+
thinkingMode = "think_tag";
|
|
1539
|
+
continue;
|
|
1540
|
+
}
|
|
1541
|
+
const thinkingMatch = contentBuffer.match(/^\*Thinking\.{0,3}\*\s*\n/);
|
|
1542
|
+
if (thinkingMatch) {
|
|
1543
|
+
contentBuffer = contentBuffer.slice(thinkingMatch[0].length);
|
|
1544
|
+
thinkingMode = "markdown_thinking";
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
if (contentBuffer.length > 0) {
|
|
1548
|
+
const keepLen = Math.min(15, contentBuffer.length);
|
|
1549
|
+
const output = contentBuffer.slice(0, -keepLen) || "";
|
|
1550
|
+
if (output) {
|
|
1551
|
+
yield { type: "content", text: output };
|
|
1552
|
+
contentBuffer = contentBuffer.slice(-keepLen);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
break;
|
|
1556
|
+
} else if (thinkingMode === "think_tag") {
|
|
1557
|
+
const endIdx = contentBuffer.indexOf("</think>");
|
|
1558
|
+
if (endIdx !== -1) {
|
|
1559
|
+
yield { type: "reasoning", text: contentBuffer.slice(0, endIdx) };
|
|
1560
|
+
contentBuffer = contentBuffer.slice(endIdx + 8);
|
|
1561
|
+
thinkingMode = "none";
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
if (contentBuffer.length > 8) {
|
|
1565
|
+
yield { type: "reasoning", text: contentBuffer.slice(0, -8) };
|
|
1566
|
+
contentBuffer = contentBuffer.slice(-8);
|
|
1567
|
+
}
|
|
1568
|
+
break;
|
|
1569
|
+
} else if (thinkingMode === "markdown_thinking") {
|
|
1570
|
+
if (contentBuffer.startsWith(">")) {
|
|
1571
|
+
thinkingMode = "markdown_quote";
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1574
|
+
if (contentBuffer.length > 0 && !contentBuffer.startsWith(">")) {
|
|
1575
|
+
thinkingMode = "none";
|
|
1576
|
+
continue;
|
|
1577
|
+
}
|
|
1578
|
+
break;
|
|
1579
|
+
} else if (thinkingMode === "markdown_quote") {
|
|
1580
|
+
const newlineIdx = contentBuffer.indexOf("\n");
|
|
1581
|
+
if (newlineIdx !== -1) {
|
|
1582
|
+
const quoteLine = contentBuffer.slice(0, newlineIdx);
|
|
1583
|
+
contentBuffer = contentBuffer.slice(newlineIdx + 1);
|
|
1584
|
+
if (quoteLine.startsWith(">")) {
|
|
1585
|
+
const thinkText = quoteLine.replace(/^>\s?/, "");
|
|
1586
|
+
yield { type: "reasoning", text: thinkText + "\n" };
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1589
|
+
if (quoteLine.trim() === "") {
|
|
1590
|
+
yield { type: "reasoning", text: "\n" };
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
thinkingMode = "none";
|
|
1594
|
+
yield { type: "content", text: quoteLine + "\n" };
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
break;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
} catch {
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (contentBuffer.length > 0) {
|
|
1606
|
+
if (thinkingMode === "think_tag" || thinkingMode === "markdown_quote") {
|
|
1607
|
+
yield { type: "reasoning", text: contentBuffer };
|
|
1608
|
+
} else {
|
|
1609
|
+
yield { type: "content", text: contentBuffer };
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
} finally {
|
|
1613
|
+
reader.releaseLock();
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
1617
|
+
|
|
1618
|
+
// src/providers/nova.ts
|
|
1619
|
+
var BASE_URL7 = "https://api.nova.amazon.com/v1";
|
|
1620
|
+
function extractTextContent8(content) {
|
|
1621
|
+
if (typeof content === "string") {
|
|
1622
|
+
return content;
|
|
1623
|
+
}
|
|
1624
|
+
if (Array.isArray(content)) {
|
|
1625
|
+
return content.filter(
|
|
1626
|
+
(item) => typeof item === "object" && item !== null && item.type === "text" && typeof item.text === "string"
|
|
1627
|
+
).map((item) => item.text).join("");
|
|
1628
|
+
}
|
|
1629
|
+
return "";
|
|
1630
|
+
}
|
|
1631
|
+
var NovaProvider = class extends BaseProvider {
|
|
1632
|
+
name = "nova";
|
|
1633
|
+
apiKey;
|
|
1634
|
+
baseUrl;
|
|
1635
|
+
constructor(config) {
|
|
1636
|
+
super();
|
|
1637
|
+
if (typeof config === "string") {
|
|
1638
|
+
this.apiKey = config;
|
|
1639
|
+
this.baseUrl = BASE_URL7;
|
|
1640
|
+
} else {
|
|
1641
|
+
this.apiKey = config.apiKey;
|
|
1642
|
+
this.baseUrl = config.baseUrl ?? BASE_URL7;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* 发送聊天请求(非流式)
|
|
1647
|
+
*
|
|
1648
|
+
* 注意:
|
|
1649
|
+
* - Nova API 的 temperature 范围是 0-1(不是 0-2)
|
|
1650
|
+
* - Nova 2 Lite 支持 extended thinking (reasoningConfig)
|
|
1651
|
+
* - effort 映射为 maxReasoningEffort
|
|
1652
|
+
*/
|
|
1653
|
+
async chat(options) {
|
|
1654
|
+
const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
|
|
1655
|
+
const body = {
|
|
1656
|
+
model,
|
|
1657
|
+
messages,
|
|
1658
|
+
temperature,
|
|
1659
|
+
stream: false
|
|
1660
|
+
};
|
|
1661
|
+
if (maxTokens) {
|
|
1662
|
+
body.max_tokens = maxTokens;
|
|
1663
|
+
}
|
|
1664
|
+
if (reasoning?.effort && reasoning.effort !== "off") {
|
|
1665
|
+
body.reasoningConfig = {
|
|
1666
|
+
type: "enabled",
|
|
1667
|
+
maxReasoningEffort: reasoning.effort
|
|
1668
|
+
// low/medium/high
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
1672
|
+
method: "POST",
|
|
1673
|
+
headers: {
|
|
1674
|
+
"Content-Type": "application/json",
|
|
1675
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1676
|
+
},
|
|
1677
|
+
body: JSON.stringify(body)
|
|
1678
|
+
});
|
|
1679
|
+
if (!response.ok) {
|
|
1680
|
+
const error = await response.text();
|
|
1681
|
+
throw new Error(`Nova API error: ${response.status} ${error}`);
|
|
1682
|
+
}
|
|
1683
|
+
const result = await response.json();
|
|
1684
|
+
const choice = result.choices?.[0];
|
|
1685
|
+
if (!choice) {
|
|
1686
|
+
throw new Error("No response from model");
|
|
1687
|
+
}
|
|
1688
|
+
const msg = choice.message;
|
|
1689
|
+
const reasoningContent = msg?.reasoning_content ?? null;
|
|
1690
|
+
return {
|
|
1691
|
+
content: extractTextContent8(msg?.content),
|
|
1692
|
+
reasoning: reasoningContent ? extractTextContent8(reasoningContent) : null,
|
|
1693
|
+
model: result.model ?? model,
|
|
1694
|
+
usage: {
|
|
1695
|
+
promptTokens: result.usage?.prompt_tokens ?? 0,
|
|
1696
|
+
completionTokens: result.usage?.completion_tokens ?? 0,
|
|
1697
|
+
totalTokens: result.usage?.total_tokens ?? 0
|
|
1698
|
+
},
|
|
1699
|
+
finishReason: choice.finish_reason ?? null
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* 发送流式聊天请求
|
|
1704
|
+
*/
|
|
1705
|
+
async *chatStream(options) {
|
|
1706
|
+
const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
|
|
1707
|
+
const body = {
|
|
1708
|
+
model,
|
|
1709
|
+
messages,
|
|
1710
|
+
temperature,
|
|
1711
|
+
stream: true
|
|
1712
|
+
};
|
|
1713
|
+
if (maxTokens) {
|
|
1714
|
+
body.max_tokens = maxTokens;
|
|
1715
|
+
}
|
|
1716
|
+
if (reasoning?.effort && reasoning.effort !== "off") {
|
|
1717
|
+
body.reasoningConfig = {
|
|
1718
|
+
type: "enabled",
|
|
1719
|
+
maxReasoningEffort: reasoning.effort
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
1723
|
+
method: "POST",
|
|
1724
|
+
headers: {
|
|
1725
|
+
"Content-Type": "application/json",
|
|
1726
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1727
|
+
},
|
|
1728
|
+
body: JSON.stringify(body)
|
|
1729
|
+
});
|
|
1730
|
+
if (!response.ok) {
|
|
1731
|
+
const error = await response.text();
|
|
1732
|
+
throw new Error(`Nova API error: ${response.status} ${error}`);
|
|
1733
|
+
}
|
|
1734
|
+
const reader = response.body?.getReader();
|
|
1735
|
+
if (!reader) {
|
|
1736
|
+
throw new Error("No response body");
|
|
1737
|
+
}
|
|
1738
|
+
const decoder = new TextDecoder();
|
|
1739
|
+
let buffer = "";
|
|
1740
|
+
try {
|
|
1741
|
+
while (true) {
|
|
1742
|
+
const { done, value } = await reader.read();
|
|
1743
|
+
if (done) break;
|
|
1744
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1745
|
+
const lines = buffer.split("\n");
|
|
1746
|
+
buffer = lines.pop() ?? "";
|
|
1747
|
+
for (const line of lines) {
|
|
1748
|
+
const trimmed = line.trim();
|
|
1749
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
1750
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
1751
|
+
try {
|
|
1752
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
1753
|
+
const delta = data.choices?.[0]?.delta;
|
|
1754
|
+
if (!delta) continue;
|
|
1755
|
+
if (delta.reasoning_content) {
|
|
1756
|
+
yield {
|
|
1757
|
+
type: "reasoning",
|
|
1758
|
+
text: extractTextContent8(delta.reasoning_content)
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
if (delta.content) {
|
|
1762
|
+
yield {
|
|
1763
|
+
type: "content",
|
|
1764
|
+
text: extractTextContent8(delta.content)
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
} catch {
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
} finally {
|
|
1772
|
+
reader.releaseLock();
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
|
|
1777
|
+
// src/providers/__factory__.ts
|
|
1778
|
+
function createProvider(config) {
|
|
1779
|
+
const { provider, apiKey, baseUrl } = config;
|
|
1780
|
+
switch (provider) {
|
|
1781
|
+
case "openrouter":
|
|
1782
|
+
return new OpenRouterProvider(apiKey);
|
|
1783
|
+
case "gemini":
|
|
1784
|
+
return new GeminiProvider(baseUrl ? { apiKey, baseUrl } : apiKey);
|
|
1785
|
+
case "groq":
|
|
1786
|
+
return new GroqProvider(baseUrl ? { apiKey, baseUrl } : apiKey);
|
|
1787
|
+
case "huggingface":
|
|
1788
|
+
return new HuggingFaceProvider(baseUrl ? { apiKey, baseUrl } : apiKey);
|
|
1789
|
+
case "modelscope":
|
|
1790
|
+
return new ModelScopeProvider(baseUrl ? { apiKey, baseUrl } : apiKey);
|
|
1791
|
+
case "deepseek":
|
|
1792
|
+
return new DeepSeekProvider(baseUrl ? { apiKey, baseUrl } : apiKey);
|
|
1793
|
+
case "poe":
|
|
1794
|
+
return new PoeProvider(baseUrl ? { apiKey, baseUrl } : apiKey);
|
|
1795
|
+
case "nova":
|
|
1796
|
+
return new NovaProvider(baseUrl ? { apiKey, baseUrl } : apiKey);
|
|
1797
|
+
default:
|
|
1798
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
var ai = {
|
|
1802
|
+
openrouter: (apiKey, baseUrl) => createProvider({ provider: "openrouter", apiKey, baseUrl }),
|
|
1803
|
+
gemini: (apiKey, baseUrl) => createProvider({ provider: "gemini", apiKey, baseUrl }),
|
|
1804
|
+
groq: (apiKey, baseUrl) => createProvider({ provider: "groq", apiKey, baseUrl }),
|
|
1805
|
+
huggingface: (apiKey, baseUrl) => createProvider({ provider: "huggingface", apiKey, baseUrl }),
|
|
1806
|
+
modelscope: (apiKey, baseUrl) => createProvider({ provider: "modelscope", apiKey, baseUrl }),
|
|
1807
|
+
deepseek: (apiKey, baseUrl) => createProvider({ provider: "deepseek", apiKey, baseUrl }),
|
|
1808
|
+
poe: (apiKey, baseUrl) => createProvider({ provider: "poe", apiKey, baseUrl }),
|
|
1809
|
+
nova: (apiKey, baseUrl) => createProvider({ provider: "nova", apiKey, baseUrl })
|
|
1810
|
+
};
|
|
189
1811
|
// Annotate the CommonJS export names for ESM import in node:
|
|
190
1812
|
0 && (module.exports = {
|
|
191
1813
|
BaseProvider,
|
|
192
|
-
|
|
1814
|
+
EFFORT_TOKEN_MAP,
|
|
1815
|
+
GeminiProvider,
|
|
1816
|
+
GroqProvider,
|
|
1817
|
+
HuggingFaceProvider,
|
|
1818
|
+
ModelScopeProvider,
|
|
1819
|
+
OpenRouterProvider,
|
|
1820
|
+
ai,
|
|
1821
|
+
createProvider
|
|
193
1822
|
});
|
|
194
1823
|
//# sourceMappingURL=index.js.map
|