@weisiren000/oiiai 0.1.3 → 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/dist/index.mjs CHANGED
@@ -1,34 +1,369 @@
1
1
  // src/providers/openrouter.ts
2
2
  import { OpenRouter } from "@openrouter/sdk";
3
3
 
4
+ // src/providers/__model-detection__.ts
5
+ var THINKING_MODEL_PATTERNS = [
6
+ // 明确的思考/推理标识
7
+ /[-_]think(?:ing)?(?:[-_:]|$)/i,
8
+ // *-think, *-thinking
9
+ /[-_]reason(?:ing)?(?:[-_:]|$)/i,
10
+ // *-reason, *-reasoning
11
+ /[-_]cot(?:[-_:]|$)/i,
12
+ // chain-of-thought
13
+ /[-_]reflect(?:ion)?(?:[-_:]|$)/i,
14
+ // reflection models
15
+ // 知名推理模型系列
16
+ /\bo1[-_]?/i,
17
+ // OpenAI o1 系列
18
+ /\bo3[-_]?/i,
19
+ // OpenAI o3 系列
20
+ /\br1[-_]?/i,
21
+ // DeepSeek R1 等
22
+ /\bqwq\b/i,
23
+ // Qwen QwQ
24
+ /\bn1[-_]?/i,
25
+ // nex-n1 等
26
+ // 通用思考关键词(较低优先级)
27
+ /think/i
28
+ // 包含 think
29
+ ];
30
+ var DIRECT_ANSWER_PATTERNS = [
31
+ /[-_]chat$/i,
32
+ // *-chat (结尾)
33
+ /[-_]instruct/i,
34
+ // *-instruct
35
+ /[-_]turbo/i,
36
+ // *-turbo
37
+ /[-_]flash/i,
38
+ // gemini-flash 等快速模型
39
+ /[-_]lite[-_v]/i,
40
+ // lite 版本(但不匹配 lite 结尾,避免误判)
41
+ /[-_]fast/i
42
+ // fast 模型
43
+ ];
44
+ var PROBLEMATIC_MODEL_PATTERNS = [
45
+ /nova[-_]?\d*[-_]lite/i
46
+ // Amazon Nova Lite 系列
47
+ ];
48
+ function isProblematicModel(modelId) {
49
+ return PROBLEMATIC_MODEL_PATTERNS.some((pattern) => pattern.test(modelId));
50
+ }
51
+ function detectByModelName(modelId) {
52
+ const normalizedId = modelId.toLowerCase();
53
+ const isThinkingPattern = THINKING_MODEL_PATTERNS.some(
54
+ (pattern) => pattern.test(normalizedId)
55
+ );
56
+ const isDirectPattern = DIRECT_ANSWER_PATTERNS.some(
57
+ (pattern) => pattern.test(normalizedId)
58
+ );
59
+ const isProblematic = isProblematicModel(normalizedId);
60
+ if (isThinkingPattern) {
61
+ return {
62
+ behavior: "thinking-first",
63
+ supportsReasoningConfig: true,
64
+ recommendedMinTokens: 500,
65
+ confidence: 0.7,
66
+ detectedBy: "pattern"
67
+ };
68
+ }
69
+ if (isDirectPattern) {
70
+ return {
71
+ behavior: "direct-answer",
72
+ supportsReasoningConfig: false,
73
+ // 问题模型需要更多 token
74
+ recommendedMinTokens: isProblematic ? 300 : 100,
75
+ confidence: 0.6,
76
+ detectedBy: "pattern"
77
+ };
78
+ }
79
+ return {
80
+ behavior: "unknown",
81
+ supportsReasoningConfig: false,
82
+ recommendedMinTokens: isProblematic ? 300 : 200,
83
+ confidence: 0.3,
84
+ detectedBy: "pattern"
85
+ };
86
+ }
87
+ var modelCharacteristicsCache = /* @__PURE__ */ new Map();
88
+ function detectByResponse(modelId, result) {
89
+ const hasReasoning = !!result.reasoning && result.reasoning.length > 0;
90
+ const hasContent = !!result.content && result.content.trim().length > 0;
91
+ const reasoningLength = result.reasoning?.length ?? 0;
92
+ const contentLength = result.content?.length ?? 0;
93
+ let behavior;
94
+ let supportsReasoningConfig = false;
95
+ let recommendedMinTokens = 200;
96
+ if (hasReasoning && !hasContent) {
97
+ behavior = "thinking-first";
98
+ supportsReasoningConfig = true;
99
+ recommendedMinTokens = Math.max(500, reasoningLength + 200);
100
+ } else if (hasReasoning && hasContent) {
101
+ if (reasoningLength > contentLength * 2) {
102
+ behavior = "thinking-first";
103
+ supportsReasoningConfig = true;
104
+ recommendedMinTokens = 500;
105
+ } else {
106
+ behavior = "hybrid";
107
+ supportsReasoningConfig = true;
108
+ recommendedMinTokens = 300;
109
+ }
110
+ } else if (hasContent && !hasReasoning) {
111
+ behavior = "direct-answer";
112
+ supportsReasoningConfig = false;
113
+ recommendedMinTokens = 100;
114
+ } else {
115
+ behavior = "unknown";
116
+ recommendedMinTokens = 500;
117
+ }
118
+ const characteristics = {
119
+ behavior,
120
+ supportsReasoningConfig,
121
+ recommendedMinTokens,
122
+ confidence: 0.9,
123
+ detectedBy: "runtime"
124
+ };
125
+ modelCharacteristicsCache.set(modelId, characteristics);
126
+ return characteristics;
127
+ }
128
+ function getModelCharacteristics(modelId) {
129
+ const cached = modelCharacteristicsCache.get(modelId);
130
+ if (cached) {
131
+ return { ...cached, detectedBy: "cache" };
132
+ }
133
+ return detectByModelName(modelId);
134
+ }
135
+ var DEFAULT_FALLBACK_CONFIG = {
136
+ enabled: true,
137
+ returnReasoningAsContent: true,
138
+ extractConclusionFromReasoning: true,
139
+ autoRetryWithMoreTokens: false,
140
+ // 默认关闭自动重试,避免额外消耗
141
+ retryTokenIncrement: 300,
142
+ maxRetries: 2
143
+ };
144
+ function extractConclusionFromReasoning(reasoning) {
145
+ if (!reasoning) return null;
146
+ const conclusionPatterns = [
147
+ /(?:therefore|thus|so|hence|finally|in conclusion|the answer is|result is)[:\s]*(.+?)(?:\n|$)/i,
148
+ /(?:答案是|结论是|因此|所以|最终)[::\s]*(.+?)(?:\n|$)/,
149
+ /(?:\*\*answer\*\*|\*\*result\*\*)[:\s]*(.+?)(?:\n|$)/i,
150
+ /=\s*(.+?)(?:\n|$)/
151
+ // 数学等式结果
152
+ ];
153
+ for (const pattern of conclusionPatterns) {
154
+ const match = reasoning.match(pattern);
155
+ if (match && match[1]) {
156
+ return match[1].trim();
157
+ }
158
+ }
159
+ const paragraphs = reasoning.split(/\n\n+/).filter((p) => p.trim());
160
+ if (paragraphs.length > 0) {
161
+ const lastParagraph = paragraphs[paragraphs.length - 1].trim();
162
+ if (lastParagraph.length < 500) {
163
+ return lastParagraph;
164
+ }
165
+ }
166
+ return null;
167
+ }
168
+ function applyFallbackStrategy(result, config = {}) {
169
+ const finalConfig = { ...DEFAULT_FALLBACK_CONFIG, ...config };
170
+ if (result.content && result.content.trim().length > 0) {
171
+ return {
172
+ content: result.content,
173
+ didFallback: false,
174
+ originalReasoning: result.reasoning
175
+ };
176
+ }
177
+ if (!finalConfig.enabled) {
178
+ return {
179
+ content: "",
180
+ didFallback: false,
181
+ originalReasoning: result.reasoning
182
+ };
183
+ }
184
+ if (finalConfig.extractConclusionFromReasoning && result.reasoning) {
185
+ const conclusion = extractConclusionFromReasoning(result.reasoning);
186
+ if (conclusion) {
187
+ return {
188
+ content: conclusion,
189
+ didFallback: true,
190
+ fallbackReason: "extracted_conclusion_from_reasoning",
191
+ originalReasoning: result.reasoning
192
+ };
193
+ }
194
+ }
195
+ if (finalConfig.returnReasoningAsContent && result.reasoning) {
196
+ return {
197
+ content: result.reasoning,
198
+ didFallback: true,
199
+ fallbackReason: "returned_reasoning_as_content",
200
+ originalReasoning: result.reasoning
201
+ };
202
+ }
203
+ return {
204
+ content: "",
205
+ didFallback: false,
206
+ fallbackReason: "no_fallback_available",
207
+ originalReasoning: result.reasoning
208
+ };
209
+ }
210
+ function adjustOptionsForModel(modelId, options) {
211
+ const characteristics = getModelCharacteristics(modelId);
212
+ if (characteristics.behavior === "thinking-first" && (!options.maxTokens || options.maxTokens < characteristics.recommendedMinTokens)) {
213
+ return {
214
+ ...options,
215
+ maxTokens: Math.max(
216
+ options.maxTokens ?? 0,
217
+ characteristics.recommendedMinTokens
218
+ )
219
+ };
220
+ }
221
+ return options;
222
+ }
223
+ function getRecommendedConfig(modelId, scenario = "simple") {
224
+ const characteristics = getModelCharacteristics(modelId);
225
+ if (characteristics.behavior !== "thinking-first") {
226
+ return {};
227
+ }
228
+ const configs = {
229
+ simple: {
230
+ maxTokens: 300,
231
+ reasoning: { effort: "low" }
232
+ },
233
+ math: {
234
+ maxTokens: 600,
235
+ reasoning: { effort: "high" }
236
+ },
237
+ reasoning: {
238
+ maxTokens: 800,
239
+ reasoning: { effort: "medium" }
240
+ },
241
+ fast: {
242
+ maxTokens: 200,
243
+ reasoning: { effort: "off" }
244
+ }
245
+ };
246
+ return configs[scenario] ?? configs.simple;
247
+ }
248
+ var ModelDetection = {
249
+ detectByModelName,
250
+ detectByResponse,
251
+ getModelCharacteristics,
252
+ applyFallbackStrategy,
253
+ adjustOptionsForModel,
254
+ getRecommendedConfig,
255
+ extractConclusionFromReasoning,
256
+ isProblematicModel,
257
+ clearCache: () => modelCharacteristicsCache.clear()
258
+ };
259
+
4
260
  // src/providers/__base__.ts
5
261
  var BaseProvider = class {
262
+ /** 降级策略配置 */
263
+ fallbackConfig = DEFAULT_FALLBACK_CONFIG;
264
+ /** 是否启用自动参数调整 */
265
+ autoAdjustEnabled = true;
266
+ /**
267
+ * 配置降级策略
268
+ */
269
+ configureFallback(config) {
270
+ this.fallbackConfig = { ...this.fallbackConfig, ...config };
271
+ return this;
272
+ }
273
+ /**
274
+ * 启用/禁用自动参数调整
275
+ */
276
+ setAutoAdjust(enabled) {
277
+ this.autoAdjustEnabled = enabled;
278
+ return this;
279
+ }
280
+ /**
281
+ * 获取模型特性信息
282
+ */
283
+ getModelCharacteristics(modelId) {
284
+ return ModelDetection.getModelCharacteristics(modelId);
285
+ }
286
+ /**
287
+ * 智能聊天:自动检测模型特性并应用降级策略
288
+ */
289
+ async chatSmart(options) {
290
+ const adjustedOptions = this.autoAdjustEnabled ? ModelDetection.adjustOptionsForModel(options.model, options) : options;
291
+ const result = await this.chat(adjustedOptions);
292
+ ModelDetection.detectByResponse(options.model, result);
293
+ return result;
294
+ }
6
295
  /**
7
296
  * 简单对话:单轮问答(默认实现)
8
- * 对于思考模型,如果 content 为空则返回 reasoning
297
+ *
298
+ * 智能处理思考模型:
299
+ * 1. 自动检测模型类型
300
+ * 2. 为思考模型自动调整 maxTokens
301
+ * 3. 如果 content 为空,智能降级(提取结论或返回 reasoning)
9
302
  */
10
303
  async ask(model, question, options) {
11
- const result = await this.chat({
304
+ const { fallback, autoAdjust = this.autoAdjustEnabled, ...chatOptions } = options ?? {};
305
+ let finalOptions = {
12
306
  model,
13
307
  messages: [{ role: "user", content: question }],
14
- ...options
308
+ ...chatOptions
309
+ };
310
+ if (autoAdjust) {
311
+ finalOptions = ModelDetection.adjustOptionsForModel(model, finalOptions);
312
+ }
313
+ const result = await this.chat(finalOptions);
314
+ ModelDetection.detectByResponse(model, result);
315
+ const fallbackResult = ModelDetection.applyFallbackStrategy(result, {
316
+ ...this.fallbackConfig,
317
+ ...fallback
15
318
  });
16
- return result.content || result.reasoning || "";
319
+ return fallbackResult.content;
17
320
  }
18
321
  /**
19
322
  * 带系统提示的对话(默认实现)
20
- * 对于思考模型,如果 content 为空则返回 reasoning
323
+ *
324
+ * 智能处理思考模型:
325
+ * 1. 自动检测模型类型
326
+ * 2. 为思考模型自动调整 maxTokens
327
+ * 3. 如果 content 为空,智能降级(提取结论或返回 reasoning)
21
328
  */
22
329
  async askWithSystem(model, systemPrompt, userMessage, options) {
23
- const result = await this.chat({
330
+ const { fallback, autoAdjust = this.autoAdjustEnabled, ...chatOptions } = options ?? {};
331
+ let finalOptions = {
24
332
  model,
25
333
  messages: [
26
334
  { role: "system", content: systemPrompt },
27
335
  { role: "user", content: userMessage }
28
336
  ],
337
+ ...chatOptions
338
+ };
339
+ if (autoAdjust) {
340
+ finalOptions = ModelDetection.adjustOptionsForModel(model, finalOptions);
341
+ }
342
+ const result = await this.chat(finalOptions);
343
+ ModelDetection.detectByResponse(model, result);
344
+ const fallbackResult = ModelDetection.applyFallbackStrategy(result, {
345
+ ...this.fallbackConfig,
346
+ ...fallback
347
+ });
348
+ return fallbackResult.content;
349
+ }
350
+ /**
351
+ * 场景化问答:根据场景自动配置参数
352
+ *
353
+ * @param model 模型 ID
354
+ * @param question 问题
355
+ * @param scenario 场景类型
356
+ * - 'simple': 简单问答(默认)
357
+ * - 'math': 数学计算
358
+ * - 'reasoning': 逻辑推理
359
+ * - 'fast': 快速回答(关闭思考)
360
+ */
361
+ async askWithScenario(model, question, scenario = "simple", options) {
362
+ const recommendedConfig = ModelDetection.getRecommendedConfig(model, scenario);
363
+ return this.ask(model, question, {
364
+ ...recommendedConfig,
29
365
  ...options
30
366
  });
31
- return result.content || result.reasoning || "";
32
367
  }
33
368
  };
34
369
 
@@ -527,9 +862,13 @@ var HuggingFaceProvider = class extends BaseProvider {
527
862
  }
528
863
  /**
529
864
  * 发送聊天请求(非流式)
865
+ *
866
+ * reasoning 参数说明:
867
+ * - HuggingFace 是模型聚合平台,thinking 支持取决于具体模型
868
+ * - 如果模型支持,会返回 reasoning_content
530
869
  */
531
870
  async chat(options) {
532
- const { model, messages, temperature = 0.7, maxTokens } = options;
871
+ const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
533
872
  const body = {
534
873
  model,
535
874
  messages,
@@ -539,6 +878,9 @@ var HuggingFaceProvider = class extends BaseProvider {
539
878
  if (maxTokens) {
540
879
  body.max_tokens = maxTokens;
541
880
  }
881
+ if (reasoning?.effort && reasoning.effort !== "off") {
882
+ body.reasoning_effort = reasoning.effort;
883
+ }
542
884
  const response = await fetch(`${this.baseUrl}/chat/completions`, {
543
885
  method: "POST",
544
886
  headers: {
@@ -574,7 +916,7 @@ var HuggingFaceProvider = class extends BaseProvider {
574
916
  * 发送流式聊天请求
575
917
  */
576
918
  async *chatStream(options) {
577
- const { model, messages, temperature = 0.7, maxTokens } = options;
919
+ const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
578
920
  const body = {
579
921
  model,
580
922
  messages,
@@ -584,6 +926,9 @@ var HuggingFaceProvider = class extends BaseProvider {
584
926
  if (maxTokens) {
585
927
  body.max_tokens = maxTokens;
586
928
  }
929
+ if (reasoning?.effort && reasoning.effort !== "off") {
930
+ body.reasoning_effort = reasoning.effort;
931
+ }
587
932
  const response = await fetch(`${this.baseUrl}/chat/completions`, {
588
933
  method: "POST",
589
934
  headers: {
@@ -808,18 +1153,6 @@ var ModelScopeProvider = class extends BaseProvider {
808
1153
 
809
1154
  // src/providers/deepseek.ts
810
1155
  var BASE_URL5 = "https://api.deepseek.com";
811
- function getReasoningMaxTokens(reasoning, userMaxTokens) {
812
- if (!reasoning || reasoning.effort === "off") {
813
- return userMaxTokens;
814
- }
815
- if (reasoning.budgetTokens !== void 0) {
816
- return reasoning.budgetTokens;
817
- }
818
- if (reasoning.effort) {
819
- return EFFORT_TOKEN_MAP[reasoning.effort];
820
- }
821
- return userMaxTokens;
822
- }
823
1156
  function extractTextContent6(content) {
824
1157
  if (typeof content === "string") {
825
1158
  return content;
@@ -847,6 +1180,10 @@ var DeepSeekProvider = class extends BaseProvider {
847
1180
  }
848
1181
  /**
849
1182
  * 发送聊天请求(非流式)
1183
+ *
1184
+ * reasoning 参数说明:
1185
+ * - effort 不为 'off' 时启用 thinking 模式
1186
+ * - maxTokens 独立控制输出长度,不受 effort 影响
850
1187
  */
851
1188
  async chat(options) {
852
1189
  const {
@@ -856,15 +1193,14 @@ var DeepSeekProvider = class extends BaseProvider {
856
1193
  maxTokens,
857
1194
  reasoning
858
1195
  } = options;
859
- const effectiveMaxTokens = getReasoningMaxTokens(reasoning, maxTokens);
860
1196
  const body = {
861
1197
  model,
862
1198
  messages,
863
1199
  temperature,
864
1200
  stream: false
865
1201
  };
866
- if (effectiveMaxTokens) {
867
- body.max_tokens = effectiveMaxTokens;
1202
+ if (maxTokens) {
1203
+ body.max_tokens = maxTokens;
868
1204
  }
869
1205
  if (reasoning?.effort && reasoning.effort !== "off") {
870
1206
  body.thinking = { type: "enabled" };
@@ -911,15 +1247,14 @@ var DeepSeekProvider = class extends BaseProvider {
911
1247
  maxTokens,
912
1248
  reasoning
913
1249
  } = options;
914
- const effectiveMaxTokens = getReasoningMaxTokens(reasoning, maxTokens);
915
1250
  const body = {
916
1251
  model,
917
1252
  messages,
918
1253
  temperature,
919
1254
  stream: true
920
1255
  };
921
- if (effectiveMaxTokens) {
922
- body.max_tokens = effectiveMaxTokens;
1256
+ if (maxTokens) {
1257
+ body.max_tokens = maxTokens;
923
1258
  }
924
1259
  if (reasoning?.effort && reasoning.effort !== "off") {
925
1260
  body.thinking = { type: "enabled" };
@@ -979,6 +1314,432 @@ var DeepSeekProvider = class extends BaseProvider {
979
1314
  }
980
1315
  };
981
1316
 
1317
+ // src/providers/poe.ts
1318
+ var BASE_URL6 = "https://api.poe.com/v1";
1319
+ function extractTextContent7(content) {
1320
+ if (typeof content === "string") {
1321
+ return content;
1322
+ }
1323
+ if (Array.isArray(content)) {
1324
+ return content.filter(
1325
+ (item) => typeof item === "object" && item !== null && item.type === "text" && typeof item.text === "string"
1326
+ ).map((item) => item.text).join("");
1327
+ }
1328
+ return "";
1329
+ }
1330
+ function extractThinkingFromContent(content) {
1331
+ const thinkMatch = content.match(/<think>([\s\S]*?)<\/think>/);
1332
+ if (thinkMatch) {
1333
+ const thinking = thinkMatch[1].trim();
1334
+ const cleanContent = content.replace(/<think>[\s\S]*?<\/think>/, "").trim();
1335
+ return { thinking, content: cleanContent };
1336
+ }
1337
+ const thinkingMatch = content.match(
1338
+ /^\*Thinking\.{0,3}\*\s*\n((?:>.*(?:\n|$))+)/
1339
+ );
1340
+ if (thinkingMatch) {
1341
+ const thinking = thinkingMatch[1].split("\n").map((line) => line.replace(/^>\s?/, "")).join("\n").trim();
1342
+ const cleanContent = content.replace(thinkingMatch[0], "").trim();
1343
+ return { thinking, content: cleanContent };
1344
+ }
1345
+ return { thinking: "", content };
1346
+ }
1347
+ function buildExtraBody(reasoning) {
1348
+ if (!reasoning || reasoning.effort === "off") {
1349
+ return void 0;
1350
+ }
1351
+ const extraBody = {};
1352
+ if (reasoning.effort) {
1353
+ extraBody.reasoning_effort = reasoning.effort;
1354
+ }
1355
+ if (reasoning.budgetTokens !== void 0) {
1356
+ extraBody.thinking_budget = reasoning.budgetTokens;
1357
+ } else if (reasoning.effort && EFFORT_TOKEN_MAP[reasoning.effort]) {
1358
+ extraBody.thinking_budget = EFFORT_TOKEN_MAP[reasoning.effort];
1359
+ }
1360
+ return Object.keys(extraBody).length > 0 ? extraBody : void 0;
1361
+ }
1362
+ var PoeProvider = class extends BaseProvider {
1363
+ name = "poe";
1364
+ apiKey;
1365
+ baseUrl;
1366
+ constructor(config) {
1367
+ super();
1368
+ if (typeof config === "string") {
1369
+ this.apiKey = config;
1370
+ this.baseUrl = BASE_URL6;
1371
+ } else {
1372
+ this.apiKey = config.apiKey;
1373
+ this.baseUrl = config.baseUrl ?? BASE_URL6;
1374
+ }
1375
+ }
1376
+ /**
1377
+ * 发送聊天请求(非流式)
1378
+ */
1379
+ async chat(options) {
1380
+ const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
1381
+ const body = {
1382
+ model,
1383
+ messages,
1384
+ temperature,
1385
+ stream: false
1386
+ };
1387
+ if (maxTokens) {
1388
+ body.max_tokens = maxTokens;
1389
+ }
1390
+ const extraBody = buildExtraBody(reasoning);
1391
+ if (extraBody) {
1392
+ Object.assign(body, extraBody);
1393
+ }
1394
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
1395
+ method: "POST",
1396
+ headers: {
1397
+ "Content-Type": "application/json",
1398
+ Authorization: `Bearer ${this.apiKey}`
1399
+ },
1400
+ body: JSON.stringify(body)
1401
+ });
1402
+ if (!response.ok) {
1403
+ const error = await response.text();
1404
+ throw new Error(`Poe API error: ${response.status} ${error}`);
1405
+ }
1406
+ const result = await response.json();
1407
+ const choice = result.choices?.[0];
1408
+ if (!choice) {
1409
+ throw new Error("No response from model");
1410
+ }
1411
+ const msg = choice.message;
1412
+ let reasoningContent = msg?.reasoning_content ?? null;
1413
+ let contentText = extractTextContent7(msg?.content);
1414
+ if (!reasoningContent && contentText) {
1415
+ const extracted = extractThinkingFromContent(contentText);
1416
+ if (extracted.thinking) {
1417
+ reasoningContent = extracted.thinking;
1418
+ contentText = extracted.content;
1419
+ }
1420
+ }
1421
+ return {
1422
+ content: contentText,
1423
+ reasoning: reasoningContent ? extractTextContent7(reasoningContent) : null,
1424
+ model: result.model ?? model,
1425
+ usage: {
1426
+ promptTokens: result.usage?.prompt_tokens ?? 0,
1427
+ completionTokens: result.usage?.completion_tokens ?? 0,
1428
+ totalTokens: result.usage?.total_tokens ?? 0
1429
+ },
1430
+ finishReason: choice.finish_reason ?? null
1431
+ };
1432
+ }
1433
+ /**
1434
+ * 发送流式聊天请求
1435
+ */
1436
+ async *chatStream(options) {
1437
+ const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
1438
+ const body = {
1439
+ model,
1440
+ messages,
1441
+ temperature,
1442
+ stream: true
1443
+ };
1444
+ if (maxTokens) {
1445
+ body.max_tokens = maxTokens;
1446
+ }
1447
+ const extraBody = buildExtraBody(reasoning);
1448
+ if (extraBody) {
1449
+ Object.assign(body, extraBody);
1450
+ }
1451
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
1452
+ method: "POST",
1453
+ headers: {
1454
+ "Content-Type": "application/json",
1455
+ Authorization: `Bearer ${this.apiKey}`
1456
+ },
1457
+ body: JSON.stringify(body)
1458
+ });
1459
+ if (!response.ok) {
1460
+ const error = await response.text();
1461
+ throw new Error(`Poe API error: ${response.status} ${error}`);
1462
+ }
1463
+ const reader = response.body?.getReader();
1464
+ if (!reader) {
1465
+ throw new Error("No response body");
1466
+ }
1467
+ const decoder = new TextDecoder();
1468
+ let buffer = "";
1469
+ let thinkingMode = "none";
1470
+ let contentBuffer = "";
1471
+ try {
1472
+ while (true) {
1473
+ const { done, value } = await reader.read();
1474
+ if (done) break;
1475
+ buffer += decoder.decode(value, { stream: true });
1476
+ const lines = buffer.split("\n");
1477
+ buffer = lines.pop() ?? "";
1478
+ for (const line of lines) {
1479
+ const trimmed = line.trim();
1480
+ if (!trimmed || trimmed === "data: [DONE]") continue;
1481
+ if (!trimmed.startsWith("data: ")) continue;
1482
+ try {
1483
+ const data = JSON.parse(trimmed.slice(6));
1484
+ const delta = data.choices?.[0]?.delta;
1485
+ if (!delta) continue;
1486
+ if (delta.reasoning_content) {
1487
+ yield {
1488
+ type: "reasoning",
1489
+ text: extractTextContent7(delta.reasoning_content)
1490
+ };
1491
+ continue;
1492
+ }
1493
+ if (delta.content) {
1494
+ const text = extractTextContent7(delta.content);
1495
+ contentBuffer += text;
1496
+ while (true) {
1497
+ if (thinkingMode === "none") {
1498
+ const thinkStart = contentBuffer.indexOf("<think>");
1499
+ if (thinkStart !== -1) {
1500
+ if (thinkStart > 0) {
1501
+ yield { type: "content", text: contentBuffer.slice(0, thinkStart) };
1502
+ }
1503
+ contentBuffer = contentBuffer.slice(thinkStart + 7);
1504
+ thinkingMode = "think_tag";
1505
+ continue;
1506
+ }
1507
+ const thinkingMatch = contentBuffer.match(/^\*Thinking\.{0,3}\*\s*\n/);
1508
+ if (thinkingMatch) {
1509
+ contentBuffer = contentBuffer.slice(thinkingMatch[0].length);
1510
+ thinkingMode = "markdown_thinking";
1511
+ continue;
1512
+ }
1513
+ if (contentBuffer.length > 0) {
1514
+ const keepLen = Math.min(15, contentBuffer.length);
1515
+ const output = contentBuffer.slice(0, -keepLen) || "";
1516
+ if (output) {
1517
+ yield { type: "content", text: output };
1518
+ contentBuffer = contentBuffer.slice(-keepLen);
1519
+ }
1520
+ }
1521
+ break;
1522
+ } else if (thinkingMode === "think_tag") {
1523
+ const endIdx = contentBuffer.indexOf("</think>");
1524
+ if (endIdx !== -1) {
1525
+ yield { type: "reasoning", text: contentBuffer.slice(0, endIdx) };
1526
+ contentBuffer = contentBuffer.slice(endIdx + 8);
1527
+ thinkingMode = "none";
1528
+ continue;
1529
+ }
1530
+ if (contentBuffer.length > 8) {
1531
+ yield { type: "reasoning", text: contentBuffer.slice(0, -8) };
1532
+ contentBuffer = contentBuffer.slice(-8);
1533
+ }
1534
+ break;
1535
+ } else if (thinkingMode === "markdown_thinking") {
1536
+ if (contentBuffer.startsWith(">")) {
1537
+ thinkingMode = "markdown_quote";
1538
+ continue;
1539
+ }
1540
+ if (contentBuffer.length > 0 && !contentBuffer.startsWith(">")) {
1541
+ thinkingMode = "none";
1542
+ continue;
1543
+ }
1544
+ break;
1545
+ } else if (thinkingMode === "markdown_quote") {
1546
+ const newlineIdx = contentBuffer.indexOf("\n");
1547
+ if (newlineIdx !== -1) {
1548
+ const quoteLine = contentBuffer.slice(0, newlineIdx);
1549
+ contentBuffer = contentBuffer.slice(newlineIdx + 1);
1550
+ if (quoteLine.startsWith(">")) {
1551
+ const thinkText = quoteLine.replace(/^>\s?/, "");
1552
+ yield { type: "reasoning", text: thinkText + "\n" };
1553
+ continue;
1554
+ }
1555
+ if (quoteLine.trim() === "") {
1556
+ yield { type: "reasoning", text: "\n" };
1557
+ continue;
1558
+ }
1559
+ thinkingMode = "none";
1560
+ yield { type: "content", text: quoteLine + "\n" };
1561
+ continue;
1562
+ }
1563
+ break;
1564
+ }
1565
+ }
1566
+ }
1567
+ } catch {
1568
+ }
1569
+ }
1570
+ }
1571
+ if (contentBuffer.length > 0) {
1572
+ if (thinkingMode === "think_tag" || thinkingMode === "markdown_quote") {
1573
+ yield { type: "reasoning", text: contentBuffer };
1574
+ } else {
1575
+ yield { type: "content", text: contentBuffer };
1576
+ }
1577
+ }
1578
+ } finally {
1579
+ reader.releaseLock();
1580
+ }
1581
+ }
1582
+ };
1583
+
1584
+ // src/providers/nova.ts
1585
+ var BASE_URL7 = "https://api.nova.amazon.com/v1";
1586
+ function extractTextContent8(content) {
1587
+ if (typeof content === "string") {
1588
+ return content;
1589
+ }
1590
+ if (Array.isArray(content)) {
1591
+ return content.filter(
1592
+ (item) => typeof item === "object" && item !== null && item.type === "text" && typeof item.text === "string"
1593
+ ).map((item) => item.text).join("");
1594
+ }
1595
+ return "";
1596
+ }
1597
+ var NovaProvider = class extends BaseProvider {
1598
+ name = "nova";
1599
+ apiKey;
1600
+ baseUrl;
1601
+ constructor(config) {
1602
+ super();
1603
+ if (typeof config === "string") {
1604
+ this.apiKey = config;
1605
+ this.baseUrl = BASE_URL7;
1606
+ } else {
1607
+ this.apiKey = config.apiKey;
1608
+ this.baseUrl = config.baseUrl ?? BASE_URL7;
1609
+ }
1610
+ }
1611
+ /**
1612
+ * 发送聊天请求(非流式)
1613
+ *
1614
+ * 注意:
1615
+ * - Nova API 的 temperature 范围是 0-1(不是 0-2)
1616
+ * - Nova 2 Lite 支持 extended thinking (reasoningConfig)
1617
+ * - effort 映射为 maxReasoningEffort
1618
+ */
1619
+ async chat(options) {
1620
+ const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
1621
+ const body = {
1622
+ model,
1623
+ messages,
1624
+ temperature,
1625
+ stream: false
1626
+ };
1627
+ if (maxTokens) {
1628
+ body.max_tokens = maxTokens;
1629
+ }
1630
+ if (reasoning?.effort && reasoning.effort !== "off") {
1631
+ body.reasoningConfig = {
1632
+ type: "enabled",
1633
+ maxReasoningEffort: reasoning.effort
1634
+ // low/medium/high
1635
+ };
1636
+ }
1637
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
1638
+ method: "POST",
1639
+ headers: {
1640
+ "Content-Type": "application/json",
1641
+ Authorization: `Bearer ${this.apiKey}`
1642
+ },
1643
+ body: JSON.stringify(body)
1644
+ });
1645
+ if (!response.ok) {
1646
+ const error = await response.text();
1647
+ throw new Error(`Nova API error: ${response.status} ${error}`);
1648
+ }
1649
+ const result = await response.json();
1650
+ const choice = result.choices?.[0];
1651
+ if (!choice) {
1652
+ throw new Error("No response from model");
1653
+ }
1654
+ const msg = choice.message;
1655
+ const reasoningContent = msg?.reasoning_content ?? null;
1656
+ return {
1657
+ content: extractTextContent8(msg?.content),
1658
+ reasoning: reasoningContent ? extractTextContent8(reasoningContent) : null,
1659
+ model: result.model ?? model,
1660
+ usage: {
1661
+ promptTokens: result.usage?.prompt_tokens ?? 0,
1662
+ completionTokens: result.usage?.completion_tokens ?? 0,
1663
+ totalTokens: result.usage?.total_tokens ?? 0
1664
+ },
1665
+ finishReason: choice.finish_reason ?? null
1666
+ };
1667
+ }
1668
+ /**
1669
+ * 发送流式聊天请求
1670
+ */
1671
+ async *chatStream(options) {
1672
+ const { model, messages, temperature = 0.7, maxTokens, reasoning } = options;
1673
+ const body = {
1674
+ model,
1675
+ messages,
1676
+ temperature,
1677
+ stream: true
1678
+ };
1679
+ if (maxTokens) {
1680
+ body.max_tokens = maxTokens;
1681
+ }
1682
+ if (reasoning?.effort && reasoning.effort !== "off") {
1683
+ body.reasoningConfig = {
1684
+ type: "enabled",
1685
+ maxReasoningEffort: reasoning.effort
1686
+ };
1687
+ }
1688
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
1689
+ method: "POST",
1690
+ headers: {
1691
+ "Content-Type": "application/json",
1692
+ Authorization: `Bearer ${this.apiKey}`
1693
+ },
1694
+ body: JSON.stringify(body)
1695
+ });
1696
+ if (!response.ok) {
1697
+ const error = await response.text();
1698
+ throw new Error(`Nova API error: ${response.status} ${error}`);
1699
+ }
1700
+ const reader = response.body?.getReader();
1701
+ if (!reader) {
1702
+ throw new Error("No response body");
1703
+ }
1704
+ const decoder = new TextDecoder();
1705
+ let buffer = "";
1706
+ try {
1707
+ while (true) {
1708
+ const { done, value } = await reader.read();
1709
+ if (done) break;
1710
+ buffer += decoder.decode(value, { stream: true });
1711
+ const lines = buffer.split("\n");
1712
+ buffer = lines.pop() ?? "";
1713
+ for (const line of lines) {
1714
+ const trimmed = line.trim();
1715
+ if (!trimmed || trimmed === "data: [DONE]") continue;
1716
+ if (!trimmed.startsWith("data: ")) continue;
1717
+ try {
1718
+ const data = JSON.parse(trimmed.slice(6));
1719
+ const delta = data.choices?.[0]?.delta;
1720
+ if (!delta) continue;
1721
+ if (delta.reasoning_content) {
1722
+ yield {
1723
+ type: "reasoning",
1724
+ text: extractTextContent8(delta.reasoning_content)
1725
+ };
1726
+ }
1727
+ if (delta.content) {
1728
+ yield {
1729
+ type: "content",
1730
+ text: extractTextContent8(delta.content)
1731
+ };
1732
+ }
1733
+ } catch {
1734
+ }
1735
+ }
1736
+ }
1737
+ } finally {
1738
+ reader.releaseLock();
1739
+ }
1740
+ }
1741
+ };
1742
+
982
1743
  // src/providers/__factory__.ts
983
1744
  function createProvider(config) {
984
1745
  const { provider, apiKey, baseUrl } = config;
@@ -995,6 +1756,10 @@ function createProvider(config) {
995
1756
  return new ModelScopeProvider(baseUrl ? { apiKey, baseUrl } : apiKey);
996
1757
  case "deepseek":
997
1758
  return new DeepSeekProvider(baseUrl ? { apiKey, baseUrl } : apiKey);
1759
+ case "poe":
1760
+ return new PoeProvider(baseUrl ? { apiKey, baseUrl } : apiKey);
1761
+ case "nova":
1762
+ return new NovaProvider(baseUrl ? { apiKey, baseUrl } : apiKey);
998
1763
  default:
999
1764
  throw new Error(`Unknown provider: ${provider}`);
1000
1765
  }
@@ -1005,7 +1770,9 @@ var ai = {
1005
1770
  groq: (apiKey, baseUrl) => createProvider({ provider: "groq", apiKey, baseUrl }),
1006
1771
  huggingface: (apiKey, baseUrl) => createProvider({ provider: "huggingface", apiKey, baseUrl }),
1007
1772
  modelscope: (apiKey, baseUrl) => createProvider({ provider: "modelscope", apiKey, baseUrl }),
1008
- deepseek: (apiKey, baseUrl) => createProvider({ provider: "deepseek", apiKey, baseUrl })
1773
+ deepseek: (apiKey, baseUrl) => createProvider({ provider: "deepseek", apiKey, baseUrl }),
1774
+ poe: (apiKey, baseUrl) => createProvider({ provider: "poe", apiKey, baseUrl }),
1775
+ nova: (apiKey, baseUrl) => createProvider({ provider: "nova", apiKey, baseUrl })
1009
1776
  };
1010
1777
  export {
1011
1778
  BaseProvider,