@huyooo/ai-chat-core 0.2.13 → 0.2.14
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 +3 -3
- package/dist/index.d.ts +673 -201
- package/dist/index.js +2007 -781
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -1,126 +1,175 @@
|
|
|
1
1
|
import { GoogleGenAI } from '@google/genai';
|
|
2
|
+
import { createGateway, streamText, tool as tool$1 } from 'ai';
|
|
3
|
+
import { z } from 'zod';
|
|
2
4
|
|
|
3
5
|
// src/agent.ts
|
|
4
6
|
|
|
5
|
-
// src/
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
// src/providers/model-registry.ts
|
|
8
|
+
var DOUBAO_FAMILY = {
|
|
9
|
+
id: "doubao",
|
|
10
|
+
displayName: "\u8C46\u5305",
|
|
11
|
+
supportsVision: true,
|
|
12
|
+
supportsThinking: true,
|
|
13
|
+
thinkingFormat: "reasoning",
|
|
14
|
+
supportsNativeSearch: true,
|
|
15
|
+
searchStrategy: "native_web_search",
|
|
16
|
+
toolCallFormat: "responses",
|
|
17
|
+
defaultMaxTokens: 32768
|
|
16
18
|
};
|
|
17
|
-
var
|
|
18
|
-
id: "
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
var DEEPSEEK_FAMILY = {
|
|
20
|
+
id: "deepseek",
|
|
21
|
+
displayName: "DeepSeek",
|
|
22
|
+
supportsVision: false,
|
|
23
|
+
supportsThinking: true,
|
|
24
|
+
thinkingFormat: "reasoning",
|
|
25
|
+
supportsNativeSearch: true,
|
|
26
|
+
searchStrategy: "native_web_search",
|
|
27
|
+
toolCallFormat: "responses",
|
|
28
|
+
defaultMaxTokens: 32768
|
|
25
29
|
};
|
|
26
|
-
var
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
};
|
|
38
|
-
var GEMINI_2_5_FLASH = { id: "gemini-2.5-flash-preview-05-20"};
|
|
39
|
-
var GEMINI_2_5_PRO = { id: "gemini-2.5-pro-preview-05-06"};
|
|
40
|
-
var GEMINI_2_0_FLASH = { id: "gemini-2.0-flash"};
|
|
41
|
-
var GEMINI_2_0_FLASH_LITE = { id: "gemini-2.0-flash-lite"};
|
|
42
|
-
var GEMINI_3_PRO = {
|
|
43
|
-
id: "gemini-3-pro-preview",
|
|
44
|
-
displayName: "Gemini 3 Pro",
|
|
45
|
-
provider: "Gemini"
|
|
46
|
-
};
|
|
47
|
-
var GEMINI_IMAGE_MODEL = "gemini-2.0-flash";
|
|
48
|
-
var GEMINI_IMAGE_GEN_MODEL = "gemini-3-pro-image-preview";
|
|
49
|
-
var OR_GEMINI_2_5_FLASH = {
|
|
50
|
-
id: "google/gemini-2.5-flash-preview"};
|
|
51
|
-
var OR_GEMINI_2_5_PRO = {
|
|
52
|
-
id: "google/gemini-2.5-pro-preview"};
|
|
53
|
-
var OR_GEMINI_2_0_FLASH = {
|
|
54
|
-
id: "google/gemini-2.0-flash-001"};
|
|
55
|
-
var OR_GEMINI_3_PRO = {
|
|
56
|
-
id: "google/gemini-3-pro-preview",
|
|
57
|
-
displayName: "Gemini 3 Pro",
|
|
58
|
-
isOpenRouter: true,
|
|
59
|
-
provider: "Gemini"
|
|
30
|
+
var QWEN_FAMILY = {
|
|
31
|
+
id: "qwen",
|
|
32
|
+
displayName: "\u901A\u4E49\u5343\u95EE",
|
|
33
|
+
supportsVision: false,
|
|
34
|
+
supportsThinking: true,
|
|
35
|
+
thinkingFormat: "thinking_enabled",
|
|
36
|
+
supportsNativeSearch: false,
|
|
37
|
+
// 关闭原生搜索,使用 Tavily
|
|
38
|
+
searchStrategy: "tool_based",
|
|
39
|
+
toolCallFormat: "openai",
|
|
40
|
+
defaultMaxTokens: 32768
|
|
60
41
|
};
|
|
61
|
-
var
|
|
62
|
-
id: "
|
|
63
|
-
displayName: "
|
|
64
|
-
|
|
65
|
-
|
|
42
|
+
var GEMINI_FAMILY = {
|
|
43
|
+
id: "gemini",
|
|
44
|
+
displayName: "Gemini",
|
|
45
|
+
supportsVision: true,
|
|
46
|
+
supportsThinking: true,
|
|
47
|
+
thinkingFormat: "thought_signature",
|
|
48
|
+
supportsNativeSearch: false,
|
|
49
|
+
// googleSearch 与其他工具冲突,统一使用 Tavily
|
|
50
|
+
searchStrategy: "tool_based",
|
|
51
|
+
toolCallFormat: "gemini",
|
|
52
|
+
defaultMaxTokens: 65536,
|
|
53
|
+
requiresSpecialHandling: ["thought_signature"]
|
|
66
54
|
};
|
|
67
|
-
var
|
|
68
|
-
id: "
|
|
69
|
-
displayName: "
|
|
70
|
-
|
|
71
|
-
|
|
55
|
+
var GPT_FAMILY = {
|
|
56
|
+
id: "gpt",
|
|
57
|
+
displayName: "GPT",
|
|
58
|
+
supportsVision: true,
|
|
59
|
+
supportsThinking: true,
|
|
60
|
+
// GPT-5.x 支持 reasoning
|
|
61
|
+
thinkingFormat: "reasoning",
|
|
62
|
+
supportsNativeSearch: false,
|
|
63
|
+
searchStrategy: "tool_based",
|
|
64
|
+
toolCallFormat: "openai",
|
|
65
|
+
defaultMaxTokens: 128e3
|
|
72
66
|
};
|
|
73
|
-
var
|
|
74
|
-
id: "
|
|
75
|
-
displayName: "
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
var CLAUDE_FAMILY = {
|
|
68
|
+
id: "claude",
|
|
69
|
+
displayName: "Claude",
|
|
70
|
+
supportsVision: true,
|
|
71
|
+
supportsThinking: true,
|
|
72
|
+
// 通过 Vercel AI SDK 支持 extended thinking
|
|
73
|
+
thinkingFormat: "reasoning",
|
|
74
|
+
supportsNativeSearch: false,
|
|
75
|
+
searchStrategy: "tool_based",
|
|
76
|
+
toolCallFormat: "openai",
|
|
77
|
+
defaultMaxTokens: 2e5
|
|
78
78
|
};
|
|
79
|
-
var
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
isOpenRouter: true,
|
|
87
|
-
provider: "DeepSeek"
|
|
79
|
+
var MODEL_FAMILIES = {
|
|
80
|
+
doubao: DOUBAO_FAMILY,
|
|
81
|
+
deepseek: DEEPSEEK_FAMILY,
|
|
82
|
+
qwen: QWEN_FAMILY,
|
|
83
|
+
gemini: GEMINI_FAMILY,
|
|
84
|
+
gpt: GPT_FAMILY,
|
|
85
|
+
claude: CLAUDE_FAMILY
|
|
88
86
|
};
|
|
89
|
-
var
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
87
|
+
var MODEL_REGISTRY = [
|
|
88
|
+
// 豆包(价格为输入<=32k档,输出价格取决于输出长度)
|
|
89
|
+
{ id: "doubao-seed-1-6-250615", displayName: "\u8C46\u5305 Seed 1.6", family: "doubao", protocol: "ark", visible: true, supportsVision: true, contextWindow: "256K", pricing: ["\u8F93\u5165 0.8 \u5143/\u767E\u4E07tokens", "\u8F93\u51FA 2-8 \u5143/\u767E\u4E07tokens"] },
|
|
90
|
+
{ id: "doubao-seed-1-8-251215", displayName: "\u8C46\u5305 Seed 1.8", family: "doubao", protocol: "ark", contextWindow: "256K", pricing: ["\u8F93\u5165 0.8 \u5143/\u767E\u4E07tokens", "\u8F93\u51FA 2-8 \u5143/\u767E\u4E07tokens"] },
|
|
91
|
+
// DeepSeek(价格为输入<=32k档)
|
|
92
|
+
{ id: "deepseek-v3-2-251201", displayName: "DeepSeek V3.2", family: "deepseek", protocol: "deepseek", visible: true, supportsVision: false, contextWindow: "128K", pricing: ["\u8F93\u5165 2 \u5143/\u767E\u4E07tokens", "\u8F93\u51FA 3 \u5143/\u767E\u4E07tokens"] },
|
|
93
|
+
// 通义千问
|
|
94
|
+
{ id: "qwen3-vl-plus", displayName: "\u901A\u4E49\u5343\u95EE 3 VL", family: "qwen", protocol: "qwen", visible: true, supportsVision: true, contextWindow: "128K", pricing: ["\u8F93\u5165 1 \u5143/\u767E\u4E07tokens", "\u8F93\u51FA 10 \u5143/\u767E\u4E07tokens"] },
|
|
95
|
+
// Gemini
|
|
96
|
+
{ id: "gemini-3-pro-preview", displayName: "Gemini 3 Pro", family: "gemini", protocol: "gemini", visible: true, supportsVision: true, contextWindow: "1M", pricing: ["\u8F93\u5165 1.25 \u5143/\u767E\u4E07tokens", "\u8F93\u51FA 10 \u5143/\u767E\u4E07tokens"] },
|
|
97
|
+
{ id: "gemini-2.5-flash-preview-05-20", displayName: "Gemini 2.5 Flash", family: "gemini", protocol: "gemini", contextWindow: "1M", pricing: ["\u8F93\u5165 0.15 \u5143/\u767E\u4E07tokens", "\u8F93\u51FA 0.6 \u5143/\u767E\u4E07tokens"] },
|
|
98
|
+
{ id: "gemini-2.5-pro-preview-05-06", displayName: "Gemini 2.5 Pro", family: "gemini", protocol: "gemini", contextWindow: "1M", pricing: ["\u8F93\u5165 1.25 \u5143/\u767E\u4E07tokens", "\u8F93\u51FA 10 \u5143/\u767E\u4E07tokens"] },
|
|
99
|
+
// GPT(OpenRouter,美元价格按约7.2汇率换算)
|
|
100
|
+
{ id: "openai/gpt-5.2", displayName: "GPT-5.2", family: "gpt", protocol: "openai", visible: true, supportsVision: true, contextWindow: "400K", pricing: ["\u8F93\u5165 12.6 \u5143/\u767E\u4E07tokens", "\u8F93\u51FA 100.8 \u5143/\u767E\u4E07tokens"] },
|
|
101
|
+
// Claude(Vercel AI SDK,美元价格按约7.2汇率换算)
|
|
102
|
+
{ id: "anthropic/claude-opus-4.5", displayName: "Claude Opus 4.5", family: "claude", protocol: "anthropic", visible: true, supportsVision: true, contextWindow: "200K", pricing: ["\u8F93\u5165 36 \u5143/\u767E\u4E07tokens", "\u8F93\u51FA 180 \u5143/\u767E\u4E07tokens"] }
|
|
101
103
|
];
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
function getModelEntry(modelId) {
|
|
105
|
+
return MODEL_REGISTRY.find((m) => m.id === modelId || modelId.includes(m.id));
|
|
106
|
+
}
|
|
107
|
+
function getModelFamily(modelId) {
|
|
108
|
+
const entry = getModelEntry(modelId);
|
|
109
|
+
if (!entry) return void 0;
|
|
110
|
+
return MODEL_FAMILIES[entry.family];
|
|
111
|
+
}
|
|
112
|
+
function getModelProtocol(modelId) {
|
|
113
|
+
const entry = getModelEntry(modelId);
|
|
114
|
+
return entry?.protocol;
|
|
115
|
+
}
|
|
116
|
+
function getVisibleModels() {
|
|
117
|
+
return MODEL_REGISTRY.filter((m) => m.visible);
|
|
118
|
+
}
|
|
119
|
+
function getModelsByFamily(familyId) {
|
|
120
|
+
return MODEL_REGISTRY.filter((m) => m.family === familyId);
|
|
121
|
+
}
|
|
122
|
+
function getModelsByProtocol(protocol) {
|
|
123
|
+
return MODEL_REGISTRY.filter((m) => m.protocol === protocol);
|
|
124
|
+
}
|
|
125
|
+
function modelSupportsThinking(modelId) {
|
|
126
|
+
const family = getModelFamily(modelId);
|
|
127
|
+
return family?.supportsThinking ?? false;
|
|
128
|
+
}
|
|
129
|
+
function modelSupportsNativeSearch(modelId) {
|
|
130
|
+
const family = getModelFamily(modelId);
|
|
131
|
+
return family?.supportsNativeSearch ?? false;
|
|
132
|
+
}
|
|
133
|
+
function modelSupportsVision(modelId) {
|
|
134
|
+
const entry = getModelEntry(modelId);
|
|
135
|
+
if (!entry) return false;
|
|
136
|
+
if (typeof entry.supportsVision === "boolean") return entry.supportsVision;
|
|
137
|
+
const family = getModelFamily(modelId);
|
|
138
|
+
return family?.supportsVision ?? false;
|
|
139
|
+
}
|
|
140
|
+
function getModelSearchStrategy(modelId) {
|
|
141
|
+
const family = getModelFamily(modelId);
|
|
142
|
+
return family?.searchStrategy ?? "tool_based";
|
|
143
|
+
}
|
|
108
144
|
|
|
109
145
|
// src/types.ts
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
146
|
+
function buildModelFeatures(entry) {
|
|
147
|
+
const family = getModelFamily(entry.id);
|
|
148
|
+
const supportsVision = modelSupportsVision(entry.id);
|
|
149
|
+
const supportsThinking = family?.supportsThinking ?? false;
|
|
150
|
+
const supportsNativeSearch = family?.supportsNativeSearch ?? false;
|
|
151
|
+
const features = [];
|
|
152
|
+
if (supportsVision) features.push("\u591A\u6A21\u6001");
|
|
153
|
+
if (supportsThinking) features.push("\u6DF1\u5EA6\u601D\u8003");
|
|
154
|
+
if (entry.contextWindow) features.push(`\u957F\u4E0A\u4E0B\u6587\uFF08${entry.contextWindow}\uFF09`);
|
|
155
|
+
if (supportsNativeSearch) features.push("\u8054\u7F51\u641C\u7D22");
|
|
156
|
+
return features;
|
|
157
|
+
}
|
|
158
|
+
var MODELS = getVisibleModels().map((entry) => {
|
|
159
|
+
const family = getModelFamily(entry.id);
|
|
160
|
+
const supportsThinking = family?.supportsThinking ?? false;
|
|
161
|
+
const supportsVision = modelSupportsVision(entry.id);
|
|
162
|
+
return {
|
|
163
|
+
modelId: entry.id,
|
|
164
|
+
displayName: entry.displayName,
|
|
165
|
+
supportsThinking,
|
|
166
|
+
supportsVision,
|
|
167
|
+
tooltip: {
|
|
168
|
+
features: buildModelFeatures(entry),
|
|
169
|
+
cost: entry.pricing
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
});
|
|
124
173
|
function getModelByModelId(modelId) {
|
|
125
174
|
return MODELS.find((m) => m.modelId === modelId);
|
|
126
175
|
}
|
|
@@ -201,7 +250,7 @@ function createToolCallStart(id, name, args) {
|
|
|
201
250
|
data: { id, name, args, startedAt: Date.now() }
|
|
202
251
|
};
|
|
203
252
|
}
|
|
204
|
-
function createToolCallResult(id, name, result, success, startedAt, error, sideEffects) {
|
|
253
|
+
function createToolCallResult(id, name, result, success, startedAt, error, sideEffects, resultType) {
|
|
205
254
|
const endedAt = Date.now();
|
|
206
255
|
return {
|
|
207
256
|
type: "tool_call_result",
|
|
@@ -213,7 +262,20 @@ function createToolCallResult(id, name, result, success, startedAt, error, sideE
|
|
|
213
262
|
error,
|
|
214
263
|
endedAt,
|
|
215
264
|
duration: endedAt - startedAt,
|
|
216
|
-
sideEffects
|
|
265
|
+
sideEffects,
|
|
266
|
+
resultType
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function createToolCallOutput(id, name, stream, chunk) {
|
|
271
|
+
return {
|
|
272
|
+
type: "tool_call_output",
|
|
273
|
+
data: {
|
|
274
|
+
id,
|
|
275
|
+
name,
|
|
276
|
+
stream,
|
|
277
|
+
chunk,
|
|
278
|
+
at: Date.now()
|
|
217
279
|
}
|
|
218
280
|
};
|
|
219
281
|
}
|
|
@@ -381,7 +443,7 @@ function isDangerousCommand(command) {
|
|
|
381
443
|
}
|
|
382
444
|
function createDefaultToolExecutor(defaultCwd = process.cwd()) {
|
|
383
445
|
return {
|
|
384
|
-
async executeCommand(command, cwd, signal) {
|
|
446
|
+
async executeCommand(command, cwd, signal, hooks) {
|
|
385
447
|
if (signal?.aborted) {
|
|
386
448
|
return { success: false, error: "\u64CD\u4F5C\u5DF2\u53D6\u6D88" };
|
|
387
449
|
}
|
|
@@ -394,35 +456,70 @@ function createDefaultToolExecutor(defaultCwd = process.cwd()) {
|
|
|
394
456
|
let stdout = "";
|
|
395
457
|
let stderr = "";
|
|
396
458
|
let killed = false;
|
|
459
|
+
let killTimer = null;
|
|
397
460
|
const child = spawn("sh", ["-c", command], {
|
|
398
461
|
cwd: cwd || defaultCwd,
|
|
399
|
-
env: process.env
|
|
462
|
+
env: process.env,
|
|
463
|
+
// 关键:让 sh 及其子进程处于同一进程组,便于取消/超时时“一锅端”
|
|
464
|
+
// macOS/Linux: 可用负 PID kill 进程组;Windows 会忽略 detached 的语义
|
|
465
|
+
detached: process.platform !== "win32"
|
|
400
466
|
});
|
|
401
|
-
const
|
|
402
|
-
if (
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
467
|
+
const killProcessTree = (signalName) => {
|
|
468
|
+
if (killed) return;
|
|
469
|
+
killed = true;
|
|
470
|
+
try {
|
|
471
|
+
if (process.platform !== "win32" && typeof child.pid === "number") {
|
|
472
|
+
process.kill(-child.pid, signalName);
|
|
473
|
+
} else {
|
|
474
|
+
child.kill(signalName);
|
|
475
|
+
}
|
|
476
|
+
} catch {
|
|
477
|
+
try {
|
|
478
|
+
child.kill(signalName);
|
|
479
|
+
} catch {
|
|
480
|
+
}
|
|
406
481
|
}
|
|
482
|
+
if (killTimer) clearTimeout(killTimer);
|
|
483
|
+
killTimer = setTimeout(() => {
|
|
484
|
+
try {
|
|
485
|
+
if (process.platform !== "win32" && typeof child.pid === "number") {
|
|
486
|
+
process.kill(-child.pid, "SIGKILL");
|
|
487
|
+
} else {
|
|
488
|
+
child.kill("SIGKILL");
|
|
489
|
+
}
|
|
490
|
+
} catch {
|
|
491
|
+
}
|
|
492
|
+
}, 1500);
|
|
493
|
+
};
|
|
494
|
+
const timeout = setTimeout(() => {
|
|
495
|
+
if (killed) return;
|
|
496
|
+
killProcessTree("SIGTERM");
|
|
497
|
+
resolve({ success: false, error: "\u547D\u4EE4\u6267\u884C\u8D85\u65F6\uFF0830\u79D2\uFF09" });
|
|
407
498
|
}, 3e4);
|
|
408
499
|
const abortHandler = () => {
|
|
409
|
-
if (
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
resolve({ success: false, error: "\u64CD\u4F5C\u5DF2\u53D6\u6D88" });
|
|
414
|
-
}
|
|
500
|
+
if (killed) return;
|
|
501
|
+
killProcessTree("SIGTERM");
|
|
502
|
+
clearTimeout(timeout);
|
|
503
|
+
resolve({ success: false, error: "\u64CD\u4F5C\u5DF2\u53D6\u6D88" });
|
|
415
504
|
};
|
|
416
505
|
signal?.addEventListener("abort", abortHandler);
|
|
417
506
|
child.stdout?.on("data", (data) => {
|
|
418
|
-
|
|
507
|
+
const chunk = data.toString();
|
|
508
|
+
stdout += chunk;
|
|
509
|
+
hooks?.onStdout?.(chunk);
|
|
419
510
|
});
|
|
420
511
|
child.stderr?.on("data", (data) => {
|
|
421
|
-
|
|
512
|
+
const chunk = data.toString();
|
|
513
|
+
stderr += chunk;
|
|
514
|
+
hooks?.onStderr?.(chunk);
|
|
422
515
|
});
|
|
423
516
|
child.on("close", (code) => {
|
|
424
517
|
clearTimeout(timeout);
|
|
425
518
|
signal?.removeEventListener("abort", abortHandler);
|
|
519
|
+
if (killTimer) {
|
|
520
|
+
clearTimeout(killTimer);
|
|
521
|
+
killTimer = null;
|
|
522
|
+
}
|
|
426
523
|
if (killed) return;
|
|
427
524
|
const output = stdout.trim();
|
|
428
525
|
const errOutput = stderr.trim();
|
|
@@ -443,6 +540,10 @@ function createDefaultToolExecutor(defaultCwd = process.cwd()) {
|
|
|
443
540
|
child.on("error", (err) => {
|
|
444
541
|
clearTimeout(timeout);
|
|
445
542
|
signal?.removeEventListener("abort", abortHandler);
|
|
543
|
+
if (killTimer) {
|
|
544
|
+
clearTimeout(killTimer);
|
|
545
|
+
killTimer = null;
|
|
546
|
+
}
|
|
446
547
|
if (!killed) {
|
|
447
548
|
resolve({ success: false, error: err.message });
|
|
448
549
|
}
|
|
@@ -455,95 +556,40 @@ function createDefaultToolExecutor(defaultCwd = process.cwd()) {
|
|
|
455
556
|
};
|
|
456
557
|
}
|
|
457
558
|
|
|
559
|
+
// src/constants.ts
|
|
560
|
+
var DEFAULT_ARK_URL = "https://ark.cn-beijing.volces.com/api/v3";
|
|
561
|
+
var DEFAULT_QWEN_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
|
|
562
|
+
var DEFAULT_OPENROUTER_URL = "https://openrouter.ai/api/v1";
|
|
563
|
+
var DOUBAO_SEED_1_6 = {
|
|
564
|
+
id: "doubao-seed-1-6-250615"};
|
|
565
|
+
var GEMINI_IMAGE_MODEL = "gemini-2.0-flash";
|
|
566
|
+
var GEMINI_IMAGE_GEN_MODEL = "gemini-3-pro-image-preview";
|
|
567
|
+
var DEFAULT_MODEL = DOUBAO_SEED_1_6.id;
|
|
568
|
+
var AGENT_MODE_PROMPT = `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684 AI \u52A9\u624B\u3002\u8BF7\u9075\u5FAA\u4EE5\u4E0B\u89C4\u5219\uFF1A
|
|
569
|
+
- \u8FD4\u56DE\u7684\u5185\u5BB9\u5C3D\u91CF\u4E0D\u8981\u4F7F\u7528\u8868\u60C5\u7B26\u53F7\uFF08emoji\uFF09\uFF0C\u9664\u975E\u7528\u6237\u8981\u6C42\u4F7F\u7528
|
|
570
|
+
- \u4F7F\u7528 Markdown \u683C\u5F0F\u7EC4\u7EC7\u5185\u5BB9
|
|
571
|
+
- \u4EE3\u7801\u5757\u4F7F\u7528\u6B63\u786E\u7684\u8BED\u8A00\u6807\u8BC6`;
|
|
572
|
+
|
|
458
573
|
// src/router.ts
|
|
459
|
-
var MODEL_ROUTES = [
|
|
460
|
-
// === 高优先级:OpenRouter 格式(包含 /)===
|
|
461
|
-
// 所有包含 / 的模型名都路由到 OpenRouter
|
|
462
|
-
// 例如:qwen/qwen3-max, anthropic/claude-opus-4.5, openai/gpt-5.2
|
|
463
|
-
{
|
|
464
|
-
type: "contains",
|
|
465
|
-
pattern: "/",
|
|
466
|
-
provider: "openrouter",
|
|
467
|
-
priority: 100,
|
|
468
|
-
description: "OpenRouter \u683C\u5F0F\u6A21\u578B\uFF08vendor/model\uFF09"
|
|
469
|
-
},
|
|
470
|
-
// === 中优先级:原生 Provider 前缀匹配 ===
|
|
471
|
-
{
|
|
472
|
-
type: "prefix",
|
|
473
|
-
pattern: "qwen",
|
|
474
|
-
provider: "qwen",
|
|
475
|
-
priority: 50,
|
|
476
|
-
description: "Qwen (DashScope) \u539F\u751F\u6A21\u578B"
|
|
477
|
-
},
|
|
478
|
-
{
|
|
479
|
-
type: "prefix",
|
|
480
|
-
pattern: "gemini",
|
|
481
|
-
provider: "gemini",
|
|
482
|
-
priority: 50,
|
|
483
|
-
description: "Gemini (Google AI) \u539F\u751F\u6A21\u578B"
|
|
484
|
-
},
|
|
485
|
-
// === 低优先级:ARK 火山引擎模型 ===
|
|
486
|
-
{
|
|
487
|
-
type: "prefix",
|
|
488
|
-
pattern: "doubao",
|
|
489
|
-
provider: "ark",
|
|
490
|
-
priority: 30,
|
|
491
|
-
description: "\u8C46\u5305\u6A21\u578B\uFF08\u706B\u5C71\u5F15\u64CE\uFF09"
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
type: "prefix",
|
|
495
|
-
pattern: "deepseek",
|
|
496
|
-
provider: "ark",
|
|
497
|
-
priority: 30,
|
|
498
|
-
description: "DeepSeek \u6A21\u578B\uFF08\u706B\u5C71\u5F15\u64CE\uFF09"
|
|
499
|
-
}
|
|
500
|
-
];
|
|
501
574
|
var DEFAULT_PROVIDER = "ark";
|
|
502
|
-
var sortedRulesCache = null;
|
|
503
|
-
function getSortedRules() {
|
|
504
|
-
if (!sortedRulesCache) {
|
|
505
|
-
sortedRulesCache = [...MODEL_ROUTES].sort((a, b) => b.priority - a.priority);
|
|
506
|
-
}
|
|
507
|
-
return sortedRulesCache;
|
|
508
|
-
}
|
|
509
|
-
function matchRule(model, rule) {
|
|
510
|
-
const lowerPattern = rule.pattern.toLowerCase();
|
|
511
|
-
switch (rule.type) {
|
|
512
|
-
case "exact":
|
|
513
|
-
return model === lowerPattern;
|
|
514
|
-
case "prefix":
|
|
515
|
-
return model.startsWith(lowerPattern);
|
|
516
|
-
case "contains":
|
|
517
|
-
return model.includes(lowerPattern);
|
|
518
|
-
case "regex":
|
|
519
|
-
return new RegExp(rule.pattern, "i").test(model);
|
|
520
|
-
default:
|
|
521
|
-
return false;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
575
|
function routeModelToProvider(model) {
|
|
525
576
|
return routeModelWithDetails(model).provider;
|
|
526
577
|
}
|
|
527
578
|
function routeModelWithDetails(model) {
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
};
|
|
537
|
-
}
|
|
579
|
+
const registryEntry = getModelEntry(model);
|
|
580
|
+
if (registryEntry) {
|
|
581
|
+
return {
|
|
582
|
+
provider: registryEntry.protocol,
|
|
583
|
+
registryEntry,
|
|
584
|
+
familyConfig: getModelFamily(model),
|
|
585
|
+
isDefault: false
|
|
586
|
+
};
|
|
538
587
|
}
|
|
539
588
|
return {
|
|
540
589
|
provider: DEFAULT_PROVIDER,
|
|
541
590
|
isDefault: true
|
|
542
591
|
};
|
|
543
592
|
}
|
|
544
|
-
function getRouteRules() {
|
|
545
|
-
return MODEL_ROUTES;
|
|
546
|
-
}
|
|
547
593
|
function getDefaultProvider() {
|
|
548
594
|
return DEFAULT_PROVIDER;
|
|
549
595
|
}
|
|
@@ -551,7 +597,199 @@ function isModelForProvider(model, provider) {
|
|
|
551
597
|
return routeModelToProvider(model) === provider;
|
|
552
598
|
}
|
|
553
599
|
|
|
600
|
+
// src/utils/debug-logger.ts
|
|
601
|
+
var debugEnabled = false;
|
|
602
|
+
var logFilePath = null;
|
|
603
|
+
var logBuffer = [];
|
|
604
|
+
var flushTimer = null;
|
|
605
|
+
var fsModule = null;
|
|
606
|
+
var fsLoadAttempted = false;
|
|
607
|
+
function getFs() {
|
|
608
|
+
if (fsModule !== null) return fsModule;
|
|
609
|
+
if (fsLoadAttempted) return null;
|
|
610
|
+
fsLoadAttempted = true;
|
|
611
|
+
try {
|
|
612
|
+
if (typeof globalThis.require === "function") {
|
|
613
|
+
fsModule = globalThis.require("fs");
|
|
614
|
+
return fsModule;
|
|
615
|
+
}
|
|
616
|
+
const proc = process;
|
|
617
|
+
if (typeof proc !== "undefined" && proc.mainModule?.require) {
|
|
618
|
+
fsModule = proc.mainModule.require("fs");
|
|
619
|
+
return fsModule;
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
const dynamicRequire = new Function("moduleName", "return require(moduleName)");
|
|
623
|
+
fsModule = dynamicRequire("fs");
|
|
624
|
+
return fsModule;
|
|
625
|
+
} catch {
|
|
626
|
+
}
|
|
627
|
+
return null;
|
|
628
|
+
} catch {
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function flushToFile() {
|
|
633
|
+
if (!logFilePath || logBuffer.length === 0) return;
|
|
634
|
+
const fs = getFs();
|
|
635
|
+
if (!fs) return;
|
|
636
|
+
try {
|
|
637
|
+
const lines = logBuffer.map((entry) => JSON.stringify(entry)).join("\n") + "\n";
|
|
638
|
+
fs.appendFileSync(logFilePath, lines, "utf-8");
|
|
639
|
+
logBuffer = [];
|
|
640
|
+
} catch (err) {
|
|
641
|
+
console.error("[DebugLogger] \u5199\u5165\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25:", err);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function scheduleFlush() {
|
|
645
|
+
if (flushTimer) return;
|
|
646
|
+
flushTimer = setTimeout(() => {
|
|
647
|
+
flushTimer = null;
|
|
648
|
+
flushToFile();
|
|
649
|
+
}, 100);
|
|
650
|
+
}
|
|
651
|
+
function createEntry(level, module, message, data) {
|
|
652
|
+
return {
|
|
653
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
654
|
+
level,
|
|
655
|
+
module,
|
|
656
|
+
message,
|
|
657
|
+
...data !== void 0 ? { data } : {}
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function log(level, module, message, data) {
|
|
661
|
+
if (!debugEnabled) return;
|
|
662
|
+
const entry = createEntry(level, module, message, data);
|
|
663
|
+
const prefix = `[${entry.timestamp.slice(11, 23)}] [${module}]`;
|
|
664
|
+
const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
|
|
665
|
+
if (data !== void 0) {
|
|
666
|
+
consoleMethod(`${prefix} ${message}`, typeof data === "object" ? JSON.stringify(data) : data);
|
|
667
|
+
} else {
|
|
668
|
+
consoleMethod(`${prefix} ${message}`);
|
|
669
|
+
}
|
|
670
|
+
if (logFilePath) {
|
|
671
|
+
logBuffer.push(entry);
|
|
672
|
+
scheduleFlush();
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
var DebugLogger = {
|
|
676
|
+
/**
|
|
677
|
+
* 启用/禁用调试日志
|
|
678
|
+
*/
|
|
679
|
+
enable(enabled = true) {
|
|
680
|
+
debugEnabled = enabled;
|
|
681
|
+
},
|
|
682
|
+
/**
|
|
683
|
+
* 检查是否启用
|
|
684
|
+
*/
|
|
685
|
+
isEnabled() {
|
|
686
|
+
return debugEnabled;
|
|
687
|
+
},
|
|
688
|
+
/**
|
|
689
|
+
* 直接设置 fs 模块(用于 Electron 主进程等打包环境)
|
|
690
|
+
*/
|
|
691
|
+
setFsModule(fs) {
|
|
692
|
+
fsModule = fs;
|
|
693
|
+
},
|
|
694
|
+
/**
|
|
695
|
+
* 设置日志文件路径(仅 Node.js 环境有效)
|
|
696
|
+
* @param path 日志文件路径,null 表示禁用文件输出
|
|
697
|
+
*/
|
|
698
|
+
setLogFile(path) {
|
|
699
|
+
flushToFile();
|
|
700
|
+
logFilePath = path;
|
|
701
|
+
if (path) {
|
|
702
|
+
const fs = getFs();
|
|
703
|
+
if (fs) {
|
|
704
|
+
try {
|
|
705
|
+
const header = {
|
|
706
|
+
type: "session_start",
|
|
707
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
708
|
+
version: "1.0"
|
|
709
|
+
};
|
|
710
|
+
fs.writeFileSync(path, JSON.stringify(header) + "\n", "utf-8");
|
|
711
|
+
console.log("[DebugLogger] \u65E5\u5FD7\u6587\u4EF6\u521B\u5EFA\u6210\u529F:", path);
|
|
712
|
+
} catch (err) {
|
|
713
|
+
console.error("[DebugLogger] \u521B\u5EFA\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25:", err);
|
|
714
|
+
logFilePath = null;
|
|
715
|
+
}
|
|
716
|
+
} else {
|
|
717
|
+
console.warn("[DebugLogger] \u65E0\u6CD5\u52A0\u8F7D fs \u6A21\u5757\uFF0C\u6587\u4EF6\u65E5\u5FD7\u4E0D\u53EF\u7528");
|
|
718
|
+
logFilePath = null;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
/**
|
|
723
|
+
* 获取日志文件路径
|
|
724
|
+
*/
|
|
725
|
+
getLogFile() {
|
|
726
|
+
return logFilePath;
|
|
727
|
+
},
|
|
728
|
+
/**
|
|
729
|
+
* 立即刷新日志到文件
|
|
730
|
+
*/
|
|
731
|
+
flush() {
|
|
732
|
+
if (flushTimer) {
|
|
733
|
+
clearTimeout(flushTimer);
|
|
734
|
+
flushTimer = null;
|
|
735
|
+
}
|
|
736
|
+
flushToFile();
|
|
737
|
+
},
|
|
738
|
+
/**
|
|
739
|
+
* 创建模块专用的 logger
|
|
740
|
+
*/
|
|
741
|
+
module(moduleName) {
|
|
742
|
+
return {
|
|
743
|
+
debug: (message, data) => log("debug", moduleName, message, data),
|
|
744
|
+
info: (message, data) => log("info", moduleName, message, data),
|
|
745
|
+
warn: (message, data) => log("warn", moduleName, message, data),
|
|
746
|
+
error: (message, data) => log("error", moduleName, message, data)
|
|
747
|
+
};
|
|
748
|
+
},
|
|
749
|
+
// 快捷方法
|
|
750
|
+
debug: (module, message, data) => log("debug", module, message, data),
|
|
751
|
+
info: (module, message, data) => log("info", module, message, data),
|
|
752
|
+
warn: (module, message, data) => log("warn", module, message, data),
|
|
753
|
+
error: (module, message, data) => log("error", module, message, data)
|
|
754
|
+
};
|
|
755
|
+
try {
|
|
756
|
+
if (typeof process !== "undefined" && process.env) {
|
|
757
|
+
if (process.env.AI_CHAT_DEBUG === "true") {
|
|
758
|
+
debugEnabled = true;
|
|
759
|
+
}
|
|
760
|
+
if (process.env.AI_CHAT_DEBUG_FILE) {
|
|
761
|
+
DebugLogger.setLogFile(process.env.AI_CHAT_DEBUG_FILE);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
} catch {
|
|
765
|
+
}
|
|
766
|
+
|
|
554
767
|
// src/providers/orchestrator.ts
|
|
768
|
+
var logger = DebugLogger.module("Orchestrator");
|
|
769
|
+
function normalizeSearchResults(items) {
|
|
770
|
+
const byUrl = /* @__PURE__ */ new Map();
|
|
771
|
+
for (const it of items) {
|
|
772
|
+
const url = String(it?.url ?? "").trim();
|
|
773
|
+
if (!url) continue;
|
|
774
|
+
const snippet = String(it?.snippet ?? "").trim();
|
|
775
|
+
let title = String(it?.title ?? "").trim();
|
|
776
|
+
if (!title) {
|
|
777
|
+
try {
|
|
778
|
+
title = new URL(url).hostname;
|
|
779
|
+
} catch {
|
|
780
|
+
title = url;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
const prev = byUrl.get(url);
|
|
784
|
+
if (!prev) {
|
|
785
|
+
byUrl.set(url, { title, url, snippet });
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
if (!prev.snippet && snippet) prev.snippet = snippet;
|
|
789
|
+
if (!prev.title && title) prev.title = title;
|
|
790
|
+
}
|
|
791
|
+
return Array.from(byUrl.values());
|
|
792
|
+
}
|
|
555
793
|
var DEFAULT_MAX_ITERATIONS = 10;
|
|
556
794
|
var ChatOrchestrator = class {
|
|
557
795
|
config;
|
|
@@ -575,37 +813,49 @@ var ChatOrchestrator = class {
|
|
|
575
813
|
const messages = this.buildMessages(context, message);
|
|
576
814
|
let iterations = 0;
|
|
577
815
|
let finalText = "";
|
|
578
|
-
let thinkingText = "";
|
|
579
|
-
let thinkingStartedAt = 0;
|
|
580
|
-
let thinkingComplete = !options.enableThinking;
|
|
581
816
|
let searchResults = [];
|
|
582
817
|
let searchStartedAt = 0;
|
|
583
818
|
let searchStarted = false;
|
|
584
|
-
|
|
585
|
-
thinkingStartedAt = Date.now();
|
|
586
|
-
yield createThinkingStart();
|
|
587
|
-
}
|
|
819
|
+
let searchEnded = false;
|
|
588
820
|
while (iterations < maxIterations) {
|
|
589
821
|
if (context.signal.aborted) {
|
|
590
822
|
yield createAbort("\u8BF7\u6C42\u5DF2\u53D6\u6D88");
|
|
591
823
|
return;
|
|
592
824
|
}
|
|
593
825
|
iterations++;
|
|
826
|
+
logger.info(`======= \u7B2C ${iterations} \u8F6E\u5F00\u59CB =======`);
|
|
827
|
+
let thinkingText = "";
|
|
828
|
+
let thinkingStartedAt = 0;
|
|
829
|
+
let thinkingStarted = false;
|
|
830
|
+
let thinkingComplete = false;
|
|
831
|
+
const chunkCounts = {};
|
|
832
|
+
let textStartedInOrchestrator = false;
|
|
833
|
+
const logChunk = (type) => {
|
|
834
|
+
chunkCounts[type] = (chunkCounts[type] || 0) + 1;
|
|
835
|
+
};
|
|
594
836
|
try {
|
|
595
837
|
const pendingToolCalls = [];
|
|
596
838
|
let hasToolCalls = false;
|
|
839
|
+
const enableThinkingThisRound = options.enableThinking === true;
|
|
840
|
+
const enableSearchThisRound = options.enableSearch === true;
|
|
841
|
+
logger.debug("\u8C03\u7528 adapter.streamOnce", { enableThinking: enableThinkingThisRound, enableSearch: enableSearchThisRound });
|
|
597
842
|
const stream = adapter.streamOnce(messages, context.tools, {
|
|
598
843
|
model: options.model,
|
|
599
|
-
enableThinking:
|
|
600
|
-
|
|
601
|
-
enableSearch: options.enableSearch && iterations === 1,
|
|
844
|
+
enableThinking: enableThinkingThisRound,
|
|
845
|
+
enableSearch: enableSearchThisRound,
|
|
602
846
|
signal: context.signal
|
|
603
847
|
});
|
|
604
848
|
for await (const chunk of stream) {
|
|
849
|
+
logChunk(chunk.type);
|
|
605
850
|
switch (chunk.type) {
|
|
606
851
|
case "text":
|
|
607
852
|
if (chunk.text) {
|
|
608
|
-
if (!
|
|
853
|
+
if (!textStartedInOrchestrator) {
|
|
854
|
+
textStartedInOrchestrator = true;
|
|
855
|
+
logger.debug("\u9996\u6B21\u6536\u5230 text", { thinkingStarted, thinkingComplete });
|
|
856
|
+
}
|
|
857
|
+
if (enableThinkingThisRound && thinkingStarted && !thinkingComplete) {
|
|
858
|
+
logger.debug("text \u89E6\u53D1 thinking_end");
|
|
609
859
|
thinkingComplete = true;
|
|
610
860
|
yield createThinkingEnd(thinkingStartedAt);
|
|
611
861
|
}
|
|
@@ -614,45 +864,80 @@ var ChatOrchestrator = class {
|
|
|
614
864
|
}
|
|
615
865
|
break;
|
|
616
866
|
case "thinking":
|
|
867
|
+
if (!enableThinkingThisRound) break;
|
|
617
868
|
if (chunk.thinking) {
|
|
618
|
-
|
|
619
|
-
|
|
869
|
+
if (thinkingComplete) {
|
|
870
|
+
logger.warn("\u26A0\uFE0F thinkingComplete=true \u4F46\u6536\u5230 thinking", chunk.thinking.slice(0, 30));
|
|
871
|
+
logger.warn("\u5F53\u524D\u72B6\u6001", { textStarted: textStartedInOrchestrator, chunkCounts });
|
|
872
|
+
} else if (textStartedInOrchestrator) {
|
|
873
|
+
logger.warn("\u26A0\uFE0F text \u5F00\u59CB\u540E\u6536\u5230 thinking", chunk.thinking.slice(0, 30));
|
|
874
|
+
} else {
|
|
875
|
+
let delta = chunk.thinking;
|
|
876
|
+
if (!thinkingStarted) {
|
|
877
|
+
delta = delta.replace(/^(?:\r?\n)+/, "");
|
|
878
|
+
if (!delta) break;
|
|
879
|
+
}
|
|
880
|
+
if (!thinkingStarted) {
|
|
881
|
+
thinkingStarted = true;
|
|
882
|
+
thinkingStartedAt = Date.now();
|
|
883
|
+
logger.debug("\u53D1\u9001 thinking_start");
|
|
884
|
+
yield createThinkingStart();
|
|
885
|
+
}
|
|
886
|
+
thinkingText += delta;
|
|
887
|
+
yield createThinkingDelta(delta);
|
|
888
|
+
}
|
|
620
889
|
}
|
|
621
890
|
break;
|
|
622
891
|
case "thinking_done":
|
|
623
|
-
if (!
|
|
892
|
+
if (!enableThinkingThisRound) break;
|
|
893
|
+
logger.info("\u6536\u5230 thinking_done", { thinkingStarted, thinkingComplete });
|
|
894
|
+
if (thinkingStarted && !thinkingComplete) {
|
|
624
895
|
thinkingComplete = true;
|
|
896
|
+
logger.debug("\u53D1\u9001 thinking_end");
|
|
625
897
|
yield createThinkingEnd(thinkingStartedAt);
|
|
898
|
+
} else if (!thinkingStarted) {
|
|
899
|
+
logger.warn("\u26A0\uFE0F \u6536\u5230 thinking_done \u4F46 thinkingStarted=false");
|
|
626
900
|
}
|
|
627
901
|
break;
|
|
628
902
|
case "tool_call":
|
|
629
903
|
if (chunk.toolCall) {
|
|
904
|
+
if (!context.tools || context.tools.length === 0) {
|
|
905
|
+
logger.warn("\u6536\u5230 tool_call \u4F46\u5F53\u524D\u672A\u6CE8\u5165\u5DE5\u5177\uFF0C\u5DF2\u5FFD\u7565", { toolName: chunk.toolCall.name });
|
|
906
|
+
break;
|
|
907
|
+
}
|
|
630
908
|
pendingToolCalls.push(chunk.toolCall);
|
|
631
909
|
hasToolCalls = true;
|
|
632
910
|
}
|
|
633
911
|
break;
|
|
634
912
|
case "search_result":
|
|
913
|
+
if (!enableSearchThisRound) break;
|
|
635
914
|
if (chunk.searchResults) {
|
|
636
915
|
if (!searchStarted) {
|
|
637
916
|
searchStarted = true;
|
|
638
917
|
searchStartedAt = Date.now();
|
|
639
918
|
yield createSearchStart(message);
|
|
640
919
|
}
|
|
641
|
-
searchResults = chunk.searchResults;
|
|
920
|
+
searchResults = normalizeSearchResults(chunk.searchResults);
|
|
642
921
|
yield createSearchResult(searchResults, searchStartedAt);
|
|
643
922
|
}
|
|
644
923
|
break;
|
|
645
924
|
case "done":
|
|
925
|
+
logger.info("\u6536\u5230 done", { finishReason: chunk.finishReason });
|
|
926
|
+
logger.info(`\u7B2C ${iterations} \u8F6E chunk \u7EDF\u8BA1`, chunkCounts);
|
|
646
927
|
if (chunk.finishReason === "tool_calls") {
|
|
647
928
|
hasToolCalls = true;
|
|
648
929
|
}
|
|
649
930
|
break;
|
|
650
931
|
case "error":
|
|
932
|
+
logger.error("\u6536\u5230 error", chunk.error);
|
|
651
933
|
yield createApiError(chunk.error ?? "\u672A\u77E5\u9519\u8BEF");
|
|
652
934
|
return;
|
|
653
935
|
}
|
|
654
936
|
}
|
|
655
|
-
|
|
937
|
+
logger.info(`\u7B2C ${iterations} \u8F6E for-await \u5FAA\u73AF\u7ED3\u675F`);
|
|
938
|
+
logger.debug("\u72B6\u6001", { thinkingStarted, thinkingComplete });
|
|
939
|
+
if (enableThinkingThisRound && thinkingStarted && !thinkingComplete) {
|
|
940
|
+
logger.debug("\u8865\u53D1 thinking_end");
|
|
656
941
|
thinkingComplete = true;
|
|
657
942
|
yield createThinkingEnd(thinkingStartedAt);
|
|
658
943
|
}
|
|
@@ -676,14 +961,20 @@ var ChatOrchestrator = class {
|
|
|
676
961
|
});
|
|
677
962
|
continue;
|
|
678
963
|
}
|
|
964
|
+
if (toolCall.name === "web_search") {
|
|
965
|
+
const q = typeof args.query === "string" ? args.query : "";
|
|
966
|
+
if (!q.trim()) {
|
|
967
|
+
args.query = message;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
679
970
|
const autoRunConfig = this.config.getAutoRunConfig ? await this.config.getAutoRunConfig() : options.autoRunConfig || this.config.autoRunConfig;
|
|
680
|
-
|
|
971
|
+
logger.debug("\u68C0\u67E5\u5DE5\u5177\u6279\u51C6", {
|
|
681
972
|
toolName: toolCall.name,
|
|
682
973
|
autoRunConfigMode: autoRunConfig?.mode,
|
|
683
974
|
hasCallback: !!this.config.onToolApprovalRequest
|
|
684
975
|
});
|
|
685
976
|
if (autoRunConfig?.mode === "manual" && this.config.onToolApprovalRequest) {
|
|
686
|
-
|
|
977
|
+
logger.info("\u53D1\u9001\u5DE5\u5177\u6279\u51C6\u8BF7\u6C42", toolCall.name);
|
|
687
978
|
const approvalRequest = {
|
|
688
979
|
type: "tool_approval_request",
|
|
689
980
|
data: {
|
|
@@ -701,7 +992,18 @@ var ChatOrchestrator = class {
|
|
|
701
992
|
});
|
|
702
993
|
if (!approved) {
|
|
703
994
|
const result2 = JSON.stringify({ skipped: true, message: "\u7528\u6237\u8DF3\u8FC7\u4E86\u6B64\u5DE5\u5177" });
|
|
704
|
-
|
|
995
|
+
if (toolCall.name === "web_search" && enableSearchThisRound) {
|
|
996
|
+
if (!searchStarted) {
|
|
997
|
+
searchStarted = true;
|
|
998
|
+
searchStartedAt = Date.now();
|
|
999
|
+
const q = typeof args.query === "string" ? args.query : message;
|
|
1000
|
+
yield createSearchStart(q);
|
|
1001
|
+
}
|
|
1002
|
+
searchEnded = true;
|
|
1003
|
+
yield createSearchEnd(false, searchStartedAt, "\u7528\u6237\u8DF3\u8FC7\u4E86 web_search");
|
|
1004
|
+
} else {
|
|
1005
|
+
yield createToolCallResult(toolCall.id, toolCall.name, result2, false, toolStartedAt);
|
|
1006
|
+
}
|
|
705
1007
|
messages.push({
|
|
706
1008
|
role: "tool",
|
|
707
1009
|
content: result2,
|
|
@@ -711,12 +1013,61 @@ var ChatOrchestrator = class {
|
|
|
711
1013
|
continue;
|
|
712
1014
|
}
|
|
713
1015
|
}
|
|
714
|
-
|
|
1016
|
+
const isWebSearchTool = toolCall.name === "web_search";
|
|
1017
|
+
if (!isWebSearchTool) {
|
|
1018
|
+
yield createToolCallStart(toolCall.id, toolCall.name, args);
|
|
1019
|
+
} else if (enableSearchThisRound) {
|
|
1020
|
+
if (!searchStarted) {
|
|
1021
|
+
searchStarted = true;
|
|
1022
|
+
searchStartedAt = Date.now();
|
|
1023
|
+
const q = typeof args.query === "string" ? args.query : message;
|
|
1024
|
+
yield createSearchStart(q);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
715
1027
|
let result;
|
|
716
1028
|
let dynamicSideEffects;
|
|
717
1029
|
let success = true;
|
|
718
1030
|
try {
|
|
719
|
-
const
|
|
1031
|
+
const eventQueue = [];
|
|
1032
|
+
let wake = null;
|
|
1033
|
+
const notify = () => wake?.();
|
|
1034
|
+
const pushEvent = (ev) => {
|
|
1035
|
+
eventQueue.push(ev);
|
|
1036
|
+
notify();
|
|
1037
|
+
};
|
|
1038
|
+
let toolDone = false;
|
|
1039
|
+
let toolValue;
|
|
1040
|
+
let toolError;
|
|
1041
|
+
const hooks = {
|
|
1042
|
+
toolCallId: toolCall.id,
|
|
1043
|
+
toolName: toolCall.name,
|
|
1044
|
+
onStdout: (chunk) => {
|
|
1045
|
+
if (chunk) pushEvent(createToolCallOutput(toolCall.id, toolCall.name, "stdout", chunk));
|
|
1046
|
+
},
|
|
1047
|
+
onStderr: (chunk) => {
|
|
1048
|
+
if (chunk) pushEvent(createToolCallOutput(toolCall.id, toolCall.name, "stderr", chunk));
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
this.config.executeTool(toolCall.name, args, context.signal, hooks).then((v) => {
|
|
1052
|
+
toolValue = v;
|
|
1053
|
+
toolDone = true;
|
|
1054
|
+
notify();
|
|
1055
|
+
}).catch((e) => {
|
|
1056
|
+
toolError = e;
|
|
1057
|
+
toolDone = true;
|
|
1058
|
+
notify();
|
|
1059
|
+
});
|
|
1060
|
+
while (!toolDone || eventQueue.length > 0) {
|
|
1061
|
+
while (eventQueue.length > 0) {
|
|
1062
|
+
const ev = eventQueue.shift();
|
|
1063
|
+
if (ev) yield ev;
|
|
1064
|
+
}
|
|
1065
|
+
if (toolDone) break;
|
|
1066
|
+
await new Promise((r) => wake = r);
|
|
1067
|
+
wake = null;
|
|
1068
|
+
}
|
|
1069
|
+
if (toolError) throw toolError;
|
|
1070
|
+
const executeResult = toolValue;
|
|
720
1071
|
if (typeof executeResult === "string") {
|
|
721
1072
|
result = executeResult;
|
|
722
1073
|
} else {
|
|
@@ -728,9 +1079,37 @@ var ChatOrchestrator = class {
|
|
|
728
1079
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
729
1080
|
result = `\u9519\u8BEF: ${errorMessage}`;
|
|
730
1081
|
}
|
|
731
|
-
const
|
|
732
|
-
const sideEffects = dynamicSideEffects ??
|
|
733
|
-
|
|
1082
|
+
const tool3 = this.config.tools?.get(toolCall.name);
|
|
1083
|
+
const sideEffects = dynamicSideEffects ?? tool3?.sideEffects;
|
|
1084
|
+
const resultType = success ? tool3?.resultType : void 0;
|
|
1085
|
+
if (!isWebSearchTool) {
|
|
1086
|
+
yield createToolCallResult(toolCall.id, toolCall.name, result, success, toolStartedAt, void 0, sideEffects, resultType);
|
|
1087
|
+
} else if (enableSearchThisRound) {
|
|
1088
|
+
let parsedResults = [];
|
|
1089
|
+
let parseError;
|
|
1090
|
+
try {
|
|
1091
|
+
const obj = JSON.parse(result || "{}");
|
|
1092
|
+
const arr = Array.isArray(obj?.results) ? obj.results : [];
|
|
1093
|
+
parsedResults = arr.map((r) => ({
|
|
1094
|
+
title: typeof r?.title === "string" ? r.title : "",
|
|
1095
|
+
url: typeof r?.url === "string" ? r.url : "",
|
|
1096
|
+
snippet: typeof r?.snippet === "string" ? r.snippet : ""
|
|
1097
|
+
})).filter((r) => !!r.url);
|
|
1098
|
+
if (typeof obj?.error === "string" && obj.error) {
|
|
1099
|
+
parseError = obj.error;
|
|
1100
|
+
success = false;
|
|
1101
|
+
}
|
|
1102
|
+
} catch {
|
|
1103
|
+
parseError = "web_search \u8FD4\u56DE\u7ED3\u679C\u89E3\u6790\u5931\u8D25";
|
|
1104
|
+
success = false;
|
|
1105
|
+
}
|
|
1106
|
+
if (parsedResults.length > 0) {
|
|
1107
|
+
searchResults = normalizeSearchResults(parsedResults);
|
|
1108
|
+
yield createSearchResult(searchResults, searchStartedAt);
|
|
1109
|
+
}
|
|
1110
|
+
searchEnded = true;
|
|
1111
|
+
yield createSearchEnd(success, searchStartedAt, parseError);
|
|
1112
|
+
}
|
|
734
1113
|
messages.push({
|
|
735
1114
|
role: "tool",
|
|
736
1115
|
content: result,
|
|
@@ -756,7 +1135,7 @@ var ChatOrchestrator = class {
|
|
|
756
1135
|
return;
|
|
757
1136
|
}
|
|
758
1137
|
}
|
|
759
|
-
if (searchStarted) {
|
|
1138
|
+
if (searchStarted && !searchEnded) {
|
|
760
1139
|
yield createSearchEnd(true, searchStartedAt);
|
|
761
1140
|
}
|
|
762
1141
|
const duration = Date.now() - startedAt;
|
|
@@ -782,7 +1161,9 @@ var ChatOrchestrator = class {
|
|
|
782
1161
|
standardMsg.toolCalls = msg.tool_calls.map((tc) => ({
|
|
783
1162
|
id: tc.id,
|
|
784
1163
|
name: tc.function.name,
|
|
785
|
-
arguments: tc.function.arguments
|
|
1164
|
+
arguments: tc.function.arguments,
|
|
1165
|
+
// 保留 thought_signature(Gemini 模型需要)
|
|
1166
|
+
thought_signature: tc.thought_signature
|
|
786
1167
|
}));
|
|
787
1168
|
}
|
|
788
1169
|
messages.push(standardMsg);
|
|
@@ -799,59 +1180,28 @@ function createOrchestrator(config) {
|
|
|
799
1180
|
return new ChatOrchestrator(config);
|
|
800
1181
|
}
|
|
801
1182
|
|
|
802
|
-
// src/providers/
|
|
803
|
-
var
|
|
804
|
-
|
|
805
|
-
DOUBAO_SEED_1_6.id,
|
|
806
|
-
DOUBAO_SEED_1_6_LATEST.id,
|
|
807
|
-
DOUBAO_SEED_1_6_FLASH.id,
|
|
808
|
-
"doubao-1-5-pro-32k-250115",
|
|
809
|
-
"doubao-1-5-pro-256k-250115",
|
|
810
|
-
"doubao-1-5-lite-32k-250115"
|
|
811
|
-
];
|
|
812
|
-
var ArkAdapter = class {
|
|
1183
|
+
// src/providers/protocols/ark.ts
|
|
1184
|
+
var logger2 = DebugLogger.module("ArkProtocol");
|
|
1185
|
+
var ArkProtocol = class {
|
|
813
1186
|
name = "ark";
|
|
814
|
-
supportedModels = ARK_MODELS;
|
|
815
1187
|
apiKey;
|
|
816
1188
|
apiUrl;
|
|
817
1189
|
constructor(config) {
|
|
818
1190
|
this.apiKey = config.apiKey;
|
|
819
1191
|
this.apiUrl = config.apiUrl ?? DEFAULT_ARK_URL;
|
|
820
1192
|
}
|
|
821
|
-
supportsModel(model) {
|
|
822
|
-
return this.supportedModels.some((m) => model.includes(m) || m.includes(model));
|
|
823
|
-
}
|
|
824
1193
|
/**
|
|
825
|
-
*
|
|
1194
|
+
* 发送请求并返回原始事件流
|
|
826
1195
|
*/
|
|
827
|
-
async *
|
|
828
|
-
const
|
|
829
|
-
|
|
1196
|
+
async *stream(messages, tools2, options) {
|
|
1197
|
+
const requestBody = this.buildRequestBody(messages, tools2, options);
|
|
1198
|
+
logger2.debug("\u53D1\u9001 ARK \u8BF7\u6C42", {
|
|
1199
|
+
url: `${this.apiUrl}/responses`,
|
|
830
1200
|
model: options.model,
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
};
|
|
835
|
-
const apiTools = [];
|
|
836
|
-
if (options.enableSearch) {
|
|
837
|
-
apiTools.push({ type: "web_search", max_keyword: 5, limit: 20 });
|
|
838
|
-
}
|
|
839
|
-
if (tools2.length > 0) {
|
|
840
|
-
for (const t of tools2) {
|
|
841
|
-
apiTools.push({
|
|
842
|
-
type: "function",
|
|
843
|
-
name: t.name,
|
|
844
|
-
description: t.description,
|
|
845
|
-
parameters: t.parameters
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
if (apiTools.length > 0) {
|
|
850
|
-
requestBody.tools = apiTools;
|
|
851
|
-
}
|
|
852
|
-
if (options.enableThinking) {
|
|
853
|
-
requestBody.thinking = { type: "enabled" };
|
|
854
|
-
}
|
|
1201
|
+
enableSearch: options.enableSearch,
|
|
1202
|
+
enableThinking: options.enableThinking,
|
|
1203
|
+
toolsCount: tools2.length
|
|
1204
|
+
});
|
|
855
1205
|
const response = await fetch(`${this.apiUrl}/responses`, {
|
|
856
1206
|
method: "POST",
|
|
857
1207
|
headers: {
|
|
@@ -863,6 +1213,7 @@ var ArkAdapter = class {
|
|
|
863
1213
|
});
|
|
864
1214
|
if (!response.ok) {
|
|
865
1215
|
const errorText = await response.text();
|
|
1216
|
+
logger2.error("ARK API \u9519\u8BEF", { status: response.status, body: errorText.slice(0, 500) });
|
|
866
1217
|
yield { type: "error", error: `ARK API \u9519\u8BEF: ${response.status} - ${errorText}` };
|
|
867
1218
|
return;
|
|
868
1219
|
}
|
|
@@ -871,10 +1222,41 @@ var ArkAdapter = class {
|
|
|
871
1222
|
yield { type: "error", error: "\u65E0\u6CD5\u83B7\u53D6\u54CD\u5E94\u6D41" };
|
|
872
1223
|
return;
|
|
873
1224
|
}
|
|
874
|
-
yield* this.
|
|
1225
|
+
yield* this.parseSSE(reader);
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* 构建请求体
|
|
1229
|
+
*/
|
|
1230
|
+
buildRequestBody(messages, tools2, options) {
|
|
1231
|
+
const input = this.convertMessages(messages);
|
|
1232
|
+
const body = {
|
|
1233
|
+
model: options.model,
|
|
1234
|
+
stream: true,
|
|
1235
|
+
max_output_tokens: options.familyConfig.defaultMaxTokens ?? 32768,
|
|
1236
|
+
input
|
|
1237
|
+
};
|
|
1238
|
+
const apiTools = [];
|
|
1239
|
+
if (options.enableSearch) {
|
|
1240
|
+
apiTools.push({ type: "web_search", max_keyword: 5, limit: 20 });
|
|
1241
|
+
}
|
|
1242
|
+
for (const t of tools2) {
|
|
1243
|
+
apiTools.push({
|
|
1244
|
+
type: "function",
|
|
1245
|
+
name: t.name,
|
|
1246
|
+
description: t.description,
|
|
1247
|
+
parameters: t.parameters
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
if (apiTools.length > 0) {
|
|
1251
|
+
body.tools = apiTools;
|
|
1252
|
+
}
|
|
1253
|
+
if (options.enableThinking) {
|
|
1254
|
+
body.thinking = { type: "enabled" };
|
|
1255
|
+
}
|
|
1256
|
+
return body;
|
|
875
1257
|
}
|
|
876
1258
|
/**
|
|
877
|
-
*
|
|
1259
|
+
* 转换消息格式
|
|
878
1260
|
*/
|
|
879
1261
|
convertMessages(messages) {
|
|
880
1262
|
const input = [];
|
|
@@ -884,7 +1266,8 @@ var ArkAdapter = class {
|
|
|
884
1266
|
input.push({ role: "system", content: msg.content });
|
|
885
1267
|
break;
|
|
886
1268
|
case "user": {
|
|
887
|
-
const
|
|
1269
|
+
const textContent = msg.content || (msg.images?.length ? "\u8BF7\u5206\u6790\u8FD9\u5F20\u56FE\u7247" : "");
|
|
1270
|
+
const content = [{ type: "input_text", text: textContent }];
|
|
888
1271
|
if (msg.images?.length) {
|
|
889
1272
|
for (const img of msg.images) {
|
|
890
1273
|
content.push({
|
|
@@ -925,14 +1308,17 @@ var ArkAdapter = class {
|
|
|
925
1308
|
return input;
|
|
926
1309
|
}
|
|
927
1310
|
/**
|
|
928
|
-
* 解析
|
|
1311
|
+
* 解析 SSE 流
|
|
929
1312
|
*/
|
|
930
|
-
async *
|
|
1313
|
+
async *parseSSE(reader) {
|
|
931
1314
|
const decoder = new TextDecoder();
|
|
932
1315
|
let buffer = "";
|
|
933
1316
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
934
1317
|
let currentFunctionCallId = null;
|
|
935
1318
|
const searchResults = [];
|
|
1319
|
+
let streamDone = false;
|
|
1320
|
+
let thinkingDone = false;
|
|
1321
|
+
let textStarted = false;
|
|
936
1322
|
while (true) {
|
|
937
1323
|
const { done, value } = await reader.read();
|
|
938
1324
|
if (done) break;
|
|
@@ -940,12 +1326,14 @@ var ArkAdapter = class {
|
|
|
940
1326
|
const lines = buffer.split("\n");
|
|
941
1327
|
buffer = lines.pop() || "";
|
|
942
1328
|
for (const line of lines) {
|
|
1329
|
+
if (streamDone) continue;
|
|
943
1330
|
if (!line.startsWith("data:")) continue;
|
|
944
1331
|
const data = line.slice(5).trim();
|
|
945
1332
|
if (data === "[DONE]") {
|
|
1333
|
+
streamDone = true;
|
|
946
1334
|
if (pendingToolCalls.size > 0) {
|
|
947
1335
|
for (const tc of pendingToolCalls.values()) {
|
|
948
|
-
yield { type: "
|
|
1336
|
+
yield { type: "tool_call_done", toolCall: tc };
|
|
949
1337
|
}
|
|
950
1338
|
yield { type: "done", finishReason: "tool_calls" };
|
|
951
1339
|
} else {
|
|
@@ -958,18 +1346,14 @@ var ArkAdapter = class {
|
|
|
958
1346
|
switch (event.type) {
|
|
959
1347
|
case "response.output_item.added": {
|
|
960
1348
|
const item = event.item;
|
|
961
|
-
if (item?.type === "function_call") {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
break;
|
|
966
|
-
}
|
|
967
|
-
currentFunctionCallId = callId;
|
|
968
|
-
pendingToolCalls.set(callId, {
|
|
969
|
-
id: callId,
|
|
1349
|
+
if (item?.type === "function_call" && item.call_id) {
|
|
1350
|
+
currentFunctionCallId = item.call_id;
|
|
1351
|
+
pendingToolCalls.set(item.call_id, {
|
|
1352
|
+
id: item.call_id,
|
|
970
1353
|
name: item.name || "",
|
|
971
1354
|
arguments: item.arguments || ""
|
|
972
1355
|
});
|
|
1356
|
+
yield { type: "tool_call_start", toolCall: { id: item.call_id, name: item.name || "" } };
|
|
973
1357
|
}
|
|
974
1358
|
break;
|
|
975
1359
|
}
|
|
@@ -978,6 +1362,7 @@ var ArkAdapter = class {
|
|
|
978
1362
|
const call = pendingToolCalls.get(currentFunctionCallId);
|
|
979
1363
|
if (call) {
|
|
980
1364
|
call.arguments += event.delta || "";
|
|
1365
|
+
yield { type: "tool_call_delta", toolCall: { id: currentFunctionCallId, arguments: event.delta || "" } };
|
|
981
1366
|
}
|
|
982
1367
|
}
|
|
983
1368
|
break;
|
|
@@ -986,10 +1371,9 @@ var ArkAdapter = class {
|
|
|
986
1371
|
case "response.output_item.done": {
|
|
987
1372
|
const item = event.item;
|
|
988
1373
|
if (item?.type === "function_call" && item.call_id) {
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
id: callId,
|
|
1374
|
+
const existing = pendingToolCalls.get(item.call_id);
|
|
1375
|
+
pendingToolCalls.set(item.call_id, {
|
|
1376
|
+
id: item.call_id,
|
|
993
1377
|
name: item.name || existing?.name || "",
|
|
994
1378
|
arguments: item.arguments || existing?.arguments || "{}"
|
|
995
1379
|
});
|
|
@@ -998,8 +1382,7 @@ var ArkAdapter = class {
|
|
|
998
1382
|
}
|
|
999
1383
|
case "response.output_text.annotation.added": {
|
|
1000
1384
|
const annotation = event.annotation;
|
|
1001
|
-
|
|
1002
|
-
if (isUrlCitation && annotation?.url) {
|
|
1385
|
+
if (annotation?.url) {
|
|
1003
1386
|
const exists = searchResults.some((r) => r.url === annotation.url);
|
|
1004
1387
|
if (!exists) {
|
|
1005
1388
|
searchResults.push({
|
|
@@ -1014,181 +1397,176 @@ var ArkAdapter = class {
|
|
|
1014
1397
|
}
|
|
1015
1398
|
case "response.output_text.delta":
|
|
1016
1399
|
if (event.delta) {
|
|
1017
|
-
|
|
1400
|
+
if (!textStarted) {
|
|
1401
|
+
textStarted = true;
|
|
1402
|
+
if (!thinkingDone) {
|
|
1403
|
+
thinkingDone = true;
|
|
1404
|
+
yield { type: "thinking_done" };
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
yield { type: "text_delta", delta: event.delta };
|
|
1018
1408
|
}
|
|
1019
1409
|
break;
|
|
1020
1410
|
case "response.reasoning_summary_text.delta":
|
|
1021
|
-
if (event.delta) {
|
|
1022
|
-
yield { type: "
|
|
1411
|
+
if (event.delta && !thinkingDone) {
|
|
1412
|
+
yield { type: "thinking_delta", delta: event.delta };
|
|
1023
1413
|
}
|
|
1024
1414
|
break;
|
|
1025
|
-
case "response.reasoning_summary_text.done":
|
|
1026
|
-
yield { type: "thinking_done" };
|
|
1027
|
-
break;
|
|
1028
1415
|
}
|
|
1029
1416
|
} catch {
|
|
1030
1417
|
}
|
|
1031
1418
|
}
|
|
1032
1419
|
}
|
|
1033
|
-
if (
|
|
1034
|
-
|
|
1035
|
-
|
|
1420
|
+
if (!streamDone) {
|
|
1421
|
+
if (pendingToolCalls.size > 0) {
|
|
1422
|
+
for (const tc of pendingToolCalls.values()) {
|
|
1423
|
+
yield { type: "tool_call_done", toolCall: tc };
|
|
1424
|
+
}
|
|
1425
|
+
yield { type: "done", finishReason: "tool_calls" };
|
|
1426
|
+
} else {
|
|
1427
|
+
yield { type: "done", finishReason: "stop" };
|
|
1036
1428
|
}
|
|
1037
|
-
yield { type: "done", finishReason: "tool_calls" };
|
|
1038
|
-
} else {
|
|
1039
|
-
yield { type: "done", finishReason: "stop" };
|
|
1040
1429
|
}
|
|
1041
1430
|
}
|
|
1042
1431
|
};
|
|
1043
|
-
function
|
|
1044
|
-
return new
|
|
1432
|
+
function createArkProtocol(config) {
|
|
1433
|
+
return new ArkProtocol(config);
|
|
1045
1434
|
}
|
|
1046
1435
|
|
|
1047
|
-
// src/providers/
|
|
1048
|
-
var
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
OR_GEMINI_2_0_FLASH.id,
|
|
1052
|
-
"anthropic/claude-sonnet-4",
|
|
1053
|
-
"anthropic/claude-3.5-sonnet",
|
|
1054
|
-
"openai/gpt-4o",
|
|
1055
|
-
"openai/gpt-4o-mini",
|
|
1056
|
-
OR_DEEPSEEK_R1.id,
|
|
1057
|
-
OR_DEEPSEEK_CHAT_V3.id
|
|
1058
|
-
];
|
|
1059
|
-
var OpenRouterAdapter = class {
|
|
1060
|
-
name = "openrouter";
|
|
1061
|
-
supportedModels = OPENROUTER_MODELS;
|
|
1436
|
+
// src/providers/protocols/deepseek.ts
|
|
1437
|
+
var logger3 = DebugLogger.module("DeepSeekProtocol");
|
|
1438
|
+
var DeepSeekProtocol = class {
|
|
1439
|
+
name = "deepseek";
|
|
1062
1440
|
apiKey;
|
|
1063
1441
|
apiUrl;
|
|
1064
1442
|
constructor(config) {
|
|
1065
1443
|
this.apiKey = config.apiKey;
|
|
1066
|
-
this.apiUrl = config.apiUrl ??
|
|
1444
|
+
this.apiUrl = config.apiUrl ?? DEFAULT_ARK_URL;
|
|
1067
1445
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1446
|
+
async *stream(messages, tools2, options) {
|
|
1447
|
+
const requestBody = this.buildRequestBody(messages, tools2, options);
|
|
1448
|
+
const url = `${this.apiUrl}/responses`;
|
|
1449
|
+
logger3.debug("\u53D1\u9001 DeepSeek \u8BF7\u6C42", {
|
|
1450
|
+
url,
|
|
1451
|
+
model: options.model,
|
|
1452
|
+
enableSearch: options.enableSearch,
|
|
1453
|
+
enableThinking: options.enableThinking,
|
|
1454
|
+
toolsCount: tools2.length
|
|
1455
|
+
});
|
|
1456
|
+
const response = await fetch(url, {
|
|
1457
|
+
method: "POST",
|
|
1458
|
+
headers: {
|
|
1459
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
1460
|
+
"Content-Type": "application/json"
|
|
1461
|
+
},
|
|
1462
|
+
body: JSON.stringify(requestBody),
|
|
1463
|
+
signal: options.signal
|
|
1464
|
+
});
|
|
1465
|
+
if (!response.ok) {
|
|
1466
|
+
const errorText = await response.text();
|
|
1467
|
+
logger3.error("DeepSeek API \u9519\u8BEF", { status: response.status, body: errorText.slice(0, 500) });
|
|
1468
|
+
yield { type: "error", error: `DeepSeek API \u9519\u8BEF: ${response.status} - ${errorText}` };
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
const reader = response.body?.getReader();
|
|
1472
|
+
if (!reader) {
|
|
1473
|
+
yield { type: "error", error: "\u65E0\u6CD5\u83B7\u53D6\u54CD\u5E94\u6D41" };
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
yield* this.parseSSE(reader, options.enableThinking);
|
|
1070
1477
|
}
|
|
1071
1478
|
/**
|
|
1072
|
-
*
|
|
1479
|
+
* 构建请求体(DeepSeek 专用)
|
|
1073
1480
|
*/
|
|
1074
|
-
|
|
1075
|
-
const
|
|
1076
|
-
const
|
|
1481
|
+
buildRequestBody(messages, tools2, options) {
|
|
1482
|
+
const input = this.convertMessages(messages);
|
|
1483
|
+
const body = {
|
|
1077
1484
|
model: options.model,
|
|
1078
|
-
messages: apiMessages,
|
|
1079
1485
|
stream: true,
|
|
1080
|
-
|
|
1486
|
+
max_output_tokens: options.familyConfig.defaultMaxTokens ?? 32768,
|
|
1487
|
+
input
|
|
1081
1488
|
};
|
|
1082
|
-
|
|
1083
|
-
requestBody.tools = tools2.map((t) => ({
|
|
1084
|
-
type: "function",
|
|
1085
|
-
function: {
|
|
1086
|
-
name: t.name,
|
|
1087
|
-
description: t.description,
|
|
1088
|
-
parameters: t.parameters
|
|
1089
|
-
}
|
|
1090
|
-
}));
|
|
1091
|
-
}
|
|
1092
|
-
if (options.model.includes("gemini") && options.enableThinking) {
|
|
1093
|
-
requestBody.reasoning = { effort: "high" };
|
|
1094
|
-
}
|
|
1095
|
-
if (options.model.includes("claude") && options.enableThinking) {
|
|
1096
|
-
requestBody.reasoning = { effort: "high", exclude: false };
|
|
1097
|
-
}
|
|
1489
|
+
const apiTools = [];
|
|
1098
1490
|
if (options.enableSearch) {
|
|
1099
|
-
|
|
1100
|
-
const maxResults = isSmallContext ? 2 : 3;
|
|
1101
|
-
requestBody.plugins = [{ id: "web", max_results: maxResults }];
|
|
1491
|
+
apiTools.push({ type: "web_search", max_keyword: 5, limit: 20 });
|
|
1102
1492
|
}
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
},
|
|
1111
|
-
body: JSON.stringify(requestBody),
|
|
1112
|
-
signal: options.signal
|
|
1113
|
-
});
|
|
1114
|
-
if (!response.ok) {
|
|
1115
|
-
const errorText = await response.text();
|
|
1116
|
-
yield { type: "error", error: `OpenRouter API \u9519\u8BEF: ${response.status} - ${errorText}` };
|
|
1117
|
-
return;
|
|
1493
|
+
for (const t of tools2) {
|
|
1494
|
+
apiTools.push({
|
|
1495
|
+
type: "function",
|
|
1496
|
+
name: t.name,
|
|
1497
|
+
description: t.description,
|
|
1498
|
+
parameters: t.parameters
|
|
1499
|
+
});
|
|
1118
1500
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1501
|
+
if (apiTools.length > 0) {
|
|
1502
|
+
body.tools = apiTools;
|
|
1503
|
+
}
|
|
1504
|
+
if (options.enableThinking) {
|
|
1505
|
+
body.thinking = { type: "enabled" };
|
|
1123
1506
|
}
|
|
1124
|
-
|
|
1507
|
+
return body;
|
|
1125
1508
|
}
|
|
1126
|
-
/**
|
|
1127
|
-
* 转换标准消息为 OpenAI 格式
|
|
1128
|
-
*/
|
|
1129
1509
|
convertMessages(messages) {
|
|
1130
|
-
const
|
|
1510
|
+
const input = [];
|
|
1131
1511
|
for (const msg of messages) {
|
|
1132
1512
|
switch (msg.role) {
|
|
1133
1513
|
case "system":
|
|
1134
|
-
|
|
1514
|
+
input.push({ role: "system", content: msg.content });
|
|
1135
1515
|
break;
|
|
1136
1516
|
case "user": {
|
|
1517
|
+
const textContent = msg.content || (msg.images?.length ? "\u8BF7\u5206\u6790\u8FD9\u5F20\u56FE\u7247" : "");
|
|
1518
|
+
const content = [{ type: "input_text", text: textContent }];
|
|
1137
1519
|
if (msg.images?.length) {
|
|
1138
|
-
const content = [{ type: "text", text: msg.content }];
|
|
1139
1520
|
for (const img of msg.images) {
|
|
1140
1521
|
content.push({
|
|
1141
|
-
type: "
|
|
1142
|
-
image_url:
|
|
1522
|
+
type: "input_image",
|
|
1523
|
+
image_url: img.startsWith("data:") ? img : `data:image/jpeg;base64,${img}`
|
|
1143
1524
|
});
|
|
1144
1525
|
}
|
|
1145
|
-
apiMessages.push({ role: "user", content });
|
|
1146
|
-
} else {
|
|
1147
|
-
apiMessages.push({ role: "user", content: msg.content });
|
|
1148
1526
|
}
|
|
1527
|
+
input.push({ role: "user", content });
|
|
1149
1528
|
break;
|
|
1150
1529
|
}
|
|
1151
1530
|
case "assistant":
|
|
1152
1531
|
if (msg.toolCalls?.length) {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
arguments: tc.arguments
|
|
1162
|
-
}
|
|
1163
|
-
}))
|
|
1164
|
-
});
|
|
1532
|
+
for (const tc of msg.toolCalls) {
|
|
1533
|
+
input.push({
|
|
1534
|
+
type: "function_call",
|
|
1535
|
+
call_id: tc.id,
|
|
1536
|
+
name: tc.name,
|
|
1537
|
+
arguments: tc.arguments
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1165
1540
|
} else {
|
|
1166
|
-
|
|
1541
|
+
input.push({
|
|
1542
|
+
role: "developer",
|
|
1543
|
+
content: `[\u4E0A\u4E00\u8F6EAI\u56DE\u590D]: ${msg.content}`
|
|
1544
|
+
});
|
|
1167
1545
|
}
|
|
1168
1546
|
break;
|
|
1169
1547
|
case "tool":
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1548
|
+
input.push({
|
|
1549
|
+
type: "function_call_output",
|
|
1550
|
+
call_id: msg.toolCallId,
|
|
1551
|
+
output: msg.content
|
|
1174
1552
|
});
|
|
1175
1553
|
break;
|
|
1176
1554
|
}
|
|
1177
1555
|
}
|
|
1178
|
-
return
|
|
1556
|
+
return input;
|
|
1179
1557
|
}
|
|
1180
1558
|
/**
|
|
1181
|
-
* 解析
|
|
1559
|
+
* 解析 SSE 流(DeepSeek 专用)
|
|
1182
1560
|
*/
|
|
1183
|
-
async *
|
|
1561
|
+
async *parseSSE(reader, enableThinking) {
|
|
1184
1562
|
const decoder = new TextDecoder();
|
|
1185
1563
|
let buffer = "";
|
|
1186
|
-
const
|
|
1187
|
-
let
|
|
1188
|
-
let finishReason = null;
|
|
1564
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
1565
|
+
let currentFunctionCallId = null;
|
|
1189
1566
|
const searchResults = [];
|
|
1190
|
-
let
|
|
1567
|
+
let streamDone = false;
|
|
1191
1568
|
let thinkingDone = false;
|
|
1569
|
+
let textStarted = false;
|
|
1192
1570
|
while (true) {
|
|
1193
1571
|
const { done, value } = await reader.read();
|
|
1194
1572
|
if (done) break;
|
|
@@ -1196,17 +1574,14 @@ var OpenRouterAdapter = class {
|
|
|
1196
1574
|
const lines = buffer.split("\n");
|
|
1197
1575
|
buffer = lines.pop() || "";
|
|
1198
1576
|
for (const line of lines) {
|
|
1577
|
+
if (streamDone) continue;
|
|
1199
1578
|
if (!line.startsWith("data:")) continue;
|
|
1200
1579
|
const data = line.slice(5).trim();
|
|
1201
1580
|
if (data === "[DONE]") {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
for (const tc of toolCallsMap.values()) {
|
|
1207
|
-
if (tc.name) {
|
|
1208
|
-
yield { type: "tool_call", toolCall: tc };
|
|
1209
|
-
}
|
|
1581
|
+
streamDone = true;
|
|
1582
|
+
if (pendingToolCalls.size > 0) {
|
|
1583
|
+
for (const tc of pendingToolCalls.values()) {
|
|
1584
|
+
yield { type: "tool_call_done", toolCall: tc };
|
|
1210
1585
|
}
|
|
1211
1586
|
yield { type: "done", finishReason: "tool_calls" };
|
|
1212
1587
|
} else {
|
|
@@ -1215,159 +1590,133 @@ var OpenRouterAdapter = class {
|
|
|
1215
1590
|
return;
|
|
1216
1591
|
}
|
|
1217
1592
|
try {
|
|
1218
|
-
const
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1593
|
+
const event = JSON.parse(data);
|
|
1594
|
+
switch (event.type) {
|
|
1595
|
+
case "response.output_item.added": {
|
|
1596
|
+
const item = event.item;
|
|
1597
|
+
if (item?.type === "function_call" && item.call_id) {
|
|
1598
|
+
currentFunctionCallId = item.call_id;
|
|
1599
|
+
pendingToolCalls.set(item.call_id, {
|
|
1600
|
+
id: item.call_id,
|
|
1601
|
+
name: item.name || "",
|
|
1602
|
+
arguments: item.arguments || ""
|
|
1603
|
+
});
|
|
1604
|
+
yield { type: "tool_call_start", toolCall: { id: item.call_id, name: item.name || "" } };
|
|
1605
|
+
}
|
|
1606
|
+
break;
|
|
1607
|
+
}
|
|
1608
|
+
case "response.function_call_arguments.delta": {
|
|
1609
|
+
if (currentFunctionCallId) {
|
|
1610
|
+
const call = pendingToolCalls.get(currentFunctionCallId);
|
|
1611
|
+
if (call) {
|
|
1612
|
+
call.arguments += event.delta || "";
|
|
1613
|
+
yield { type: "tool_call_delta", toolCall: { id: currentFunctionCallId, arguments: event.delta || "" } };
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
break;
|
|
1617
|
+
}
|
|
1618
|
+
case "response.function_call_arguments.done":
|
|
1619
|
+
case "response.output_item.done": {
|
|
1620
|
+
const item = event.item;
|
|
1621
|
+
if (item?.type === "function_call" && item.call_id) {
|
|
1622
|
+
const existing = pendingToolCalls.get(item.call_id);
|
|
1623
|
+
pendingToolCalls.set(item.call_id, {
|
|
1624
|
+
id: item.call_id,
|
|
1625
|
+
name: item.name || existing?.name || "",
|
|
1626
|
+
arguments: item.arguments || existing?.arguments || "{}"
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
break;
|
|
1630
|
+
}
|
|
1631
|
+
case "response.output_text.annotation.added": {
|
|
1632
|
+
const annotation = event.annotation;
|
|
1633
|
+
if (annotation?.url) {
|
|
1634
|
+
const exists = searchResults.some((r) => r.url === annotation.url);
|
|
1225
1635
|
if (!exists) {
|
|
1226
1636
|
searchResults.push({
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
snippet: annotation.
|
|
1637
|
+
title: annotation.title || annotation.text || "",
|
|
1638
|
+
url: annotation.url,
|
|
1639
|
+
snippet: annotation.summary || annotation.snippet || ""
|
|
1230
1640
|
});
|
|
1231
1641
|
yield { type: "search_result", searchResults: [...searchResults] };
|
|
1232
1642
|
}
|
|
1233
1643
|
}
|
|
1644
|
+
break;
|
|
1234
1645
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
if (delta.content) {
|
|
1244
|
-
if (hasThinking && !thinkingDone) {
|
|
1245
|
-
thinkingDone = true;
|
|
1246
|
-
yield { type: "thinking_done" };
|
|
1247
|
-
}
|
|
1248
|
-
yield { type: "text", text: delta.content };
|
|
1249
|
-
}
|
|
1250
|
-
if (delta.tool_calls?.length) {
|
|
1251
|
-
for (const tc of delta.tool_calls) {
|
|
1252
|
-
const index = tc.index;
|
|
1253
|
-
const existing = toolCallsMap.get(index);
|
|
1254
|
-
if (existing) {
|
|
1255
|
-
if (tc.function?.arguments) {
|
|
1256
|
-
existing.arguments += tc.function.arguments;
|
|
1257
|
-
}
|
|
1258
|
-
if (tc.function?.name) {
|
|
1259
|
-
existing.name = tc.function.name;
|
|
1260
|
-
}
|
|
1261
|
-
if (tc.id) {
|
|
1262
|
-
existing.id = tc.id;
|
|
1646
|
+
case "response.output_text.delta":
|
|
1647
|
+
if (event.delta) {
|
|
1648
|
+
if (!textStarted) {
|
|
1649
|
+
textStarted = true;
|
|
1650
|
+
if (enableThinking && !thinkingDone) {
|
|
1651
|
+
thinkingDone = true;
|
|
1652
|
+
yield { type: "thinking_done" };
|
|
1653
|
+
}
|
|
1263
1654
|
}
|
|
1264
|
-
|
|
1265
|
-
toolCallsMap.set(index, {
|
|
1266
|
-
id: tc.id || `call_${Date.now()}_${toolCallCounter++}`,
|
|
1267
|
-
name: tc.function?.name || "",
|
|
1268
|
-
arguments: tc.function?.arguments || ""
|
|
1269
|
-
});
|
|
1655
|
+
yield { type: "text_delta", delta: event.delta };
|
|
1270
1656
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
yield { type: "tool_call", toolCall: tc };
|
|
1657
|
+
break;
|
|
1658
|
+
// DeepSeek 支持 reasoning(深度思考)
|
|
1659
|
+
case "response.reasoning_summary_text.delta":
|
|
1660
|
+
if (enableThinking && event.delta && !thinkingDone) {
|
|
1661
|
+
yield { type: "thinking_delta", delta: event.delta };
|
|
1277
1662
|
}
|
|
1278
|
-
|
|
1279
|
-
yield { type: "done", finishReason: "tool_calls" };
|
|
1280
|
-
return;
|
|
1663
|
+
break;
|
|
1281
1664
|
}
|
|
1282
1665
|
} catch {
|
|
1283
1666
|
}
|
|
1284
1667
|
}
|
|
1285
1668
|
}
|
|
1286
|
-
if (
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
for (const tc of toolCallsMap.values()) {
|
|
1291
|
-
if (tc.name) {
|
|
1292
|
-
yield { type: "tool_call", toolCall: tc };
|
|
1669
|
+
if (!streamDone) {
|
|
1670
|
+
if (pendingToolCalls.size > 0) {
|
|
1671
|
+
for (const tc of pendingToolCalls.values()) {
|
|
1672
|
+
yield { type: "tool_call_done", toolCall: tc };
|
|
1293
1673
|
}
|
|
1674
|
+
yield { type: "done", finishReason: "tool_calls" };
|
|
1675
|
+
} else {
|
|
1676
|
+
yield { type: "done", finishReason: "stop" };
|
|
1294
1677
|
}
|
|
1295
|
-
yield { type: "done", finishReason: "tool_calls" };
|
|
1296
|
-
} else {
|
|
1297
|
-
yield { type: "done", finishReason: "stop" };
|
|
1298
1678
|
}
|
|
1299
1679
|
}
|
|
1300
1680
|
};
|
|
1301
|
-
function
|
|
1302
|
-
return new
|
|
1681
|
+
function createDeepSeekProtocol(config) {
|
|
1682
|
+
return new DeepSeekProtocol(config);
|
|
1303
1683
|
}
|
|
1304
1684
|
|
|
1305
|
-
// src/providers/
|
|
1306
|
-
var
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
QWEN_PLUS.id,
|
|
1310
|
-
QWEN_PLUS_LATEST.id,
|
|
1311
|
-
QWEN_TURBO.id,
|
|
1312
|
-
QWEN_TURBO_LATEST.id,
|
|
1313
|
-
QWEN3_235B.id
|
|
1314
|
-
];
|
|
1315
|
-
var QwenAdapter = class {
|
|
1685
|
+
// src/providers/protocols/qwen.ts
|
|
1686
|
+
var logger4 = DebugLogger.module("QwenProtocol");
|
|
1687
|
+
var DEFAULT_QWEN_COMPATIBLE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
|
|
1688
|
+
var QwenProtocol = class {
|
|
1316
1689
|
name = "qwen";
|
|
1317
|
-
supportedModels = QWEN_MODELS;
|
|
1318
1690
|
apiKey;
|
|
1319
1691
|
apiUrl;
|
|
1320
1692
|
constructor(config) {
|
|
1321
1693
|
this.apiKey = config.apiKey;
|
|
1322
|
-
this.apiUrl = config.apiUrl ??
|
|
1323
|
-
}
|
|
1324
|
-
supportsModel(model) {
|
|
1325
|
-
return this.supportedModels.some((m) => model.includes(m) || m.includes(model));
|
|
1694
|
+
this.apiUrl = config.apiUrl ?? DEFAULT_QWEN_COMPATIBLE_URL;
|
|
1326
1695
|
}
|
|
1327
1696
|
/**
|
|
1328
|
-
*
|
|
1697
|
+
* 发送请求并返回原始事件流
|
|
1329
1698
|
*/
|
|
1330
|
-
async *
|
|
1331
|
-
const
|
|
1332
|
-
const
|
|
1699
|
+
async *stream(messages, tools2, options) {
|
|
1700
|
+
const requestBody = this.buildRequestBody(messages, tools2, options);
|
|
1701
|
+
const url = `${this.apiUrl}/chat/completions`;
|
|
1702
|
+
logger4.debug("\u53D1\u9001 Qwen \u8BF7\u6C42\uFF08\u517C\u5BB9\u6A21\u5F0F\uFF09", {
|
|
1703
|
+
url,
|
|
1333
1704
|
model: options.model,
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
enable_search: options.enableSearch,
|
|
1339
|
-
// 深度思考支持
|
|
1340
|
-
...options.enableThinking && {
|
|
1341
|
-
enable_thinking: true,
|
|
1342
|
-
thinking_budget: 38400
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
};
|
|
1346
|
-
if (tools2.length > 0) {
|
|
1347
|
-
requestBody.parameters = {
|
|
1348
|
-
...requestBody.parameters,
|
|
1349
|
-
tools: tools2.map((t) => ({
|
|
1350
|
-
type: "function",
|
|
1351
|
-
function: {
|
|
1352
|
-
name: t.name,
|
|
1353
|
-
description: t.description,
|
|
1354
|
-
parameters: t.parameters
|
|
1355
|
-
}
|
|
1356
|
-
}))
|
|
1357
|
-
};
|
|
1358
|
-
}
|
|
1359
|
-
const response = await fetch(`${this.apiUrl}/services/aigc/text-generation/generation`, {
|
|
1705
|
+
enableThinking: options.enableThinking,
|
|
1706
|
+
toolsCount: tools2.length
|
|
1707
|
+
});
|
|
1708
|
+
const response = await fetch(url, {
|
|
1360
1709
|
method: "POST",
|
|
1361
1710
|
headers: {
|
|
1362
1711
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
1363
|
-
"Content-Type": "application/json"
|
|
1364
|
-
"X-DashScope-SSE": "enable"
|
|
1712
|
+
"Content-Type": "application/json"
|
|
1365
1713
|
},
|
|
1366
1714
|
body: JSON.stringify(requestBody),
|
|
1367
1715
|
signal: options.signal
|
|
1368
1716
|
});
|
|
1369
1717
|
if (!response.ok) {
|
|
1370
1718
|
const errorText = await response.text();
|
|
1719
|
+
logger4.error("Qwen API \u9519\u8BEF", { status: response.status, body: errorText.slice(0, 500) });
|
|
1371
1720
|
yield { type: "error", error: `Qwen API \u9519\u8BEF: ${response.status} - ${errorText}` };
|
|
1372
1721
|
return;
|
|
1373
1722
|
}
|
|
@@ -1376,53 +1725,77 @@ var QwenAdapter = class {
|
|
|
1376
1725
|
yield { type: "error", error: "\u65E0\u6CD5\u83B7\u53D6\u54CD\u5E94\u6D41" };
|
|
1377
1726
|
return;
|
|
1378
1727
|
}
|
|
1379
|
-
yield* this.
|
|
1728
|
+
yield* this.parseSSE(reader, options.enableThinking);
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* 构建请求体(OpenAI 格式)
|
|
1732
|
+
*/
|
|
1733
|
+
buildRequestBody(messages, tools2, options) {
|
|
1734
|
+
const convertedMessages = this.convertMessages(messages);
|
|
1735
|
+
const body = {
|
|
1736
|
+
model: options.model,
|
|
1737
|
+
messages: convertedMessages,
|
|
1738
|
+
stream: true
|
|
1739
|
+
};
|
|
1740
|
+
if (options.enableThinking) {
|
|
1741
|
+
body.enable_thinking = true;
|
|
1742
|
+
body.thinking_budget = 38400;
|
|
1743
|
+
}
|
|
1744
|
+
if (tools2.length > 0) {
|
|
1745
|
+
body.tools = tools2.map((t) => ({
|
|
1746
|
+
type: "function",
|
|
1747
|
+
function: {
|
|
1748
|
+
name: t.name,
|
|
1749
|
+
description: t.description,
|
|
1750
|
+
parameters: t.parameters
|
|
1751
|
+
}
|
|
1752
|
+
}));
|
|
1753
|
+
}
|
|
1754
|
+
return body;
|
|
1380
1755
|
}
|
|
1381
1756
|
/**
|
|
1382
|
-
*
|
|
1757
|
+
* 转换消息格式(OpenAI 标准格式)
|
|
1383
1758
|
*/
|
|
1384
1759
|
convertMessages(messages) {
|
|
1385
|
-
const
|
|
1760
|
+
const result = [];
|
|
1386
1761
|
for (const msg of messages) {
|
|
1387
1762
|
switch (msg.role) {
|
|
1388
1763
|
case "system":
|
|
1389
|
-
|
|
1764
|
+
result.push({ role: "system", content: msg.content });
|
|
1390
1765
|
break;
|
|
1391
1766
|
case "user": {
|
|
1767
|
+
const textContent = msg.content || (msg.images?.length ? "\u8BF7\u5206\u6790\u8FD9\u5F20\u56FE\u7247" : "");
|
|
1392
1768
|
if (msg.images?.length) {
|
|
1393
|
-
const content = [{ type: "text", text:
|
|
1769
|
+
const content = [{ type: "text", text: textContent }];
|
|
1394
1770
|
for (const img of msg.images) {
|
|
1395
1771
|
content.push({
|
|
1396
1772
|
type: "image_url",
|
|
1397
1773
|
image_url: { url: img.startsWith("data:") ? img : `data:image/jpeg;base64,${img}` }
|
|
1398
1774
|
});
|
|
1399
1775
|
}
|
|
1400
|
-
|
|
1776
|
+
result.push({ role: "user", content });
|
|
1401
1777
|
} else {
|
|
1402
|
-
|
|
1778
|
+
result.push({ role: "user", content: textContent });
|
|
1403
1779
|
}
|
|
1404
1780
|
break;
|
|
1405
1781
|
}
|
|
1406
1782
|
case "assistant":
|
|
1407
1783
|
if (msg.toolCalls?.length) {
|
|
1408
|
-
|
|
1784
|
+
result.push({
|
|
1409
1785
|
role: "assistant",
|
|
1410
|
-
content: msg.content ||
|
|
1786
|
+
content: msg.content || null,
|
|
1411
1787
|
tool_calls: msg.toolCalls.map((tc) => ({
|
|
1412
1788
|
id: tc.id,
|
|
1413
1789
|
type: "function",
|
|
1414
|
-
function: {
|
|
1415
|
-
name: tc.name,
|
|
1416
|
-
arguments: tc.arguments
|
|
1417
|
-
}
|
|
1790
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
1418
1791
|
}))
|
|
1419
1792
|
});
|
|
1420
1793
|
} else {
|
|
1421
|
-
|
|
1794
|
+
result.push({ role: "assistant", content: msg.content });
|
|
1422
1795
|
}
|
|
1423
1796
|
break;
|
|
1424
1797
|
case "tool":
|
|
1425
|
-
|
|
1798
|
+
result.push({
|
|
1426
1799
|
role: "tool",
|
|
1427
1800
|
tool_call_id: msg.toolCallId,
|
|
1428
1801
|
content: msg.content
|
|
@@ -1430,18 +1803,16 @@ var QwenAdapter = class {
|
|
|
1430
1803
|
break;
|
|
1431
1804
|
}
|
|
1432
1805
|
}
|
|
1433
|
-
return
|
|
1806
|
+
return result;
|
|
1434
1807
|
}
|
|
1435
1808
|
/**
|
|
1436
|
-
* 解析
|
|
1809
|
+
* 解析 SSE 流(OpenAI 格式)
|
|
1437
1810
|
*/
|
|
1438
|
-
async *
|
|
1811
|
+
async *parseSSE(reader, enableThinking) {
|
|
1439
1812
|
const decoder = new TextDecoder();
|
|
1440
1813
|
let buffer = "";
|
|
1441
1814
|
const toolCallsMap = /* @__PURE__ */ new Map();
|
|
1442
|
-
let
|
|
1443
|
-
const searchResults = [];
|
|
1444
|
-
let hasThinking = false;
|
|
1815
|
+
let textStarted = false;
|
|
1445
1816
|
let thinkingDone = false;
|
|
1446
1817
|
while (true) {
|
|
1447
1818
|
const { done, value } = await reader.read();
|
|
@@ -1452,42 +1823,41 @@ var QwenAdapter = class {
|
|
|
1452
1823
|
for (const line of lines) {
|
|
1453
1824
|
if (!line.startsWith("data:")) continue;
|
|
1454
1825
|
const data = line.slice(5).trim();
|
|
1455
|
-
if (!data || data === "[DONE]")
|
|
1826
|
+
if (!data || data === "[DONE]") {
|
|
1827
|
+
if (data === "[DONE]") {
|
|
1828
|
+
const toolCalls2 = Array.from(toolCallsMap.values());
|
|
1829
|
+
if (toolCalls2.length > 0) {
|
|
1830
|
+
for (const tc of toolCalls2) {
|
|
1831
|
+
yield { type: "tool_call_done", toolCall: tc };
|
|
1832
|
+
}
|
|
1833
|
+
yield { type: "done", finishReason: "tool_calls" };
|
|
1834
|
+
} else {
|
|
1835
|
+
yield { type: "done", finishReason: "stop" };
|
|
1836
|
+
}
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1456
1841
|
try {
|
|
1457
1842
|
const json = JSON.parse(data);
|
|
1458
|
-
const
|
|
1459
|
-
if (!
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
hasThinking = true;
|
|
1465
|
-
yield { type: "thinking", thinking: message.reasoning_content };
|
|
1843
|
+
const choice = json.choices?.[0];
|
|
1844
|
+
if (!choice) continue;
|
|
1845
|
+
const delta = choice.delta;
|
|
1846
|
+
if (!delta) continue;
|
|
1847
|
+
if (enableThinking && delta.reasoning_content && !thinkingDone) {
|
|
1848
|
+
yield { type: "thinking_delta", delta: delta.reasoning_content };
|
|
1466
1849
|
}
|
|
1467
|
-
if (
|
|
1468
|
-
if (!
|
|
1850
|
+
if (delta.content) {
|
|
1851
|
+
if (enableThinking && !textStarted && !thinkingDone) {
|
|
1469
1852
|
thinkingDone = true;
|
|
1470
1853
|
yield { type: "thinking_done" };
|
|
1471
1854
|
}
|
|
1472
|
-
|
|
1855
|
+
textStarted = true;
|
|
1856
|
+
yield { type: "text_delta", delta: delta.content };
|
|
1473
1857
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
const exists = searchResults.some((r) => r.url === result.url);
|
|
1478
|
-
if (!exists) {
|
|
1479
|
-
searchResults.push({
|
|
1480
|
-
title: result.title || "",
|
|
1481
|
-
url: result.url || "",
|
|
1482
|
-
snippet: result.snippet || ""
|
|
1483
|
-
});
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
yield { type: "search_result", searchResults: [...searchResults] };
|
|
1487
|
-
}
|
|
1488
|
-
if (message.tool_calls?.length) {
|
|
1489
|
-
for (const tc of message.tool_calls) {
|
|
1490
|
-
const index = toolCallsMap.size;
|
|
1858
|
+
if (delta.tool_calls?.length) {
|
|
1859
|
+
for (const tc of delta.tool_calls) {
|
|
1860
|
+
const index = tc.index ?? 0;
|
|
1491
1861
|
const existing = toolCallsMap.get(index);
|
|
1492
1862
|
if (existing) {
|
|
1493
1863
|
if (tc.function?.arguments) {
|
|
@@ -1495,21 +1865,26 @@ var QwenAdapter = class {
|
|
|
1495
1865
|
}
|
|
1496
1866
|
} else {
|
|
1497
1867
|
toolCallsMap.set(index, {
|
|
1498
|
-
id: tc.id || `call_${
|
|
1499
|
-
name: tc.function
|
|
1500
|
-
arguments: tc.function
|
|
1868
|
+
id: tc.id || `call_${index}`,
|
|
1869
|
+
name: tc.function?.name || "",
|
|
1870
|
+
arguments: tc.function?.arguments || ""
|
|
1501
1871
|
});
|
|
1872
|
+
yield {
|
|
1873
|
+
type: "tool_call_start",
|
|
1874
|
+
toolCall: { id: tc.id || `call_${index}`, name: tc.function?.name || "" }
|
|
1875
|
+
};
|
|
1502
1876
|
}
|
|
1503
1877
|
}
|
|
1504
1878
|
}
|
|
1505
|
-
if (
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1879
|
+
if (choice.finish_reason) {
|
|
1880
|
+
const toolCalls2 = Array.from(toolCallsMap.values());
|
|
1881
|
+
if (toolCalls2.length > 0) {
|
|
1882
|
+
for (const tc of toolCalls2) {
|
|
1883
|
+
yield { type: "tool_call_done", toolCall: tc };
|
|
1509
1884
|
}
|
|
1510
1885
|
yield { type: "done", finishReason: "tool_calls" };
|
|
1511
1886
|
} else {
|
|
1512
|
-
yield { type: "done", finishReason:
|
|
1887
|
+
yield { type: "done", finishReason: choice.finish_reason };
|
|
1513
1888
|
}
|
|
1514
1889
|
return;
|
|
1515
1890
|
}
|
|
@@ -1517,9 +1892,10 @@ var QwenAdapter = class {
|
|
|
1517
1892
|
}
|
|
1518
1893
|
}
|
|
1519
1894
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1895
|
+
const toolCalls = Array.from(toolCallsMap.values());
|
|
1896
|
+
if (toolCalls.length > 0) {
|
|
1897
|
+
for (const tc of toolCalls) {
|
|
1898
|
+
yield { type: "tool_call_done", toolCall: tc };
|
|
1523
1899
|
}
|
|
1524
1900
|
yield { type: "done", finishReason: "tool_calls" };
|
|
1525
1901
|
} else {
|
|
@@ -1527,201 +1903,1034 @@ var QwenAdapter = class {
|
|
|
1527
1903
|
}
|
|
1528
1904
|
}
|
|
1529
1905
|
};
|
|
1530
|
-
function
|
|
1531
|
-
return new
|
|
1906
|
+
function createQwenProtocol(config) {
|
|
1907
|
+
return new QwenProtocol(config);
|
|
1532
1908
|
}
|
|
1533
1909
|
|
|
1534
|
-
// src/providers/
|
|
1535
|
-
var
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
GEMINI_2_0_FLASH.id,
|
|
1539
|
-
GEMINI_2_0_FLASH_LITE.id,
|
|
1540
|
-
GEMINI_3_PRO.id
|
|
1541
|
-
];
|
|
1542
|
-
var GeminiAdapter = class {
|
|
1910
|
+
// src/providers/protocols/gemini.ts
|
|
1911
|
+
var logger5 = DebugLogger.module("GeminiProtocol");
|
|
1912
|
+
var DEFAULT_GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
1913
|
+
var GeminiProtocol = class {
|
|
1543
1914
|
name = "gemini";
|
|
1544
|
-
supportedModels = GEMINI_MODELS;
|
|
1545
|
-
client = null;
|
|
1546
1915
|
apiKey;
|
|
1916
|
+
apiUrl;
|
|
1547
1917
|
constructor(config) {
|
|
1548
1918
|
this.apiKey = config.apiKey;
|
|
1919
|
+
this.apiUrl = config.apiUrl ?? DEFAULT_GEMINI_URL;
|
|
1549
1920
|
}
|
|
1550
|
-
/**
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1921
|
+
/**
|
|
1922
|
+
* 发送请求并返回原始事件流
|
|
1923
|
+
*/
|
|
1924
|
+
async *stream(messages, tools2, options) {
|
|
1925
|
+
const requestBody = this.buildRequestBody(messages, tools2, options);
|
|
1926
|
+
const url = `${this.apiUrl}/models/${options.model}:streamGenerateContent?key=${this.apiKey}&alt=sse`;
|
|
1927
|
+
logger5.debug("\u53D1\u9001 Gemini \u8BF7\u6C42", {
|
|
1928
|
+
url: url.replace(this.apiKey, "***"),
|
|
1929
|
+
model: options.model,
|
|
1930
|
+
enableSearch: options.enableSearch,
|
|
1931
|
+
enableThinking: options.enableThinking,
|
|
1932
|
+
toolsCount: tools2.length
|
|
1933
|
+
});
|
|
1934
|
+
const response = await fetch(url, {
|
|
1935
|
+
method: "POST",
|
|
1936
|
+
headers: { "Content-Type": "application/json" },
|
|
1937
|
+
body: JSON.stringify(requestBody),
|
|
1938
|
+
signal: options.signal
|
|
1939
|
+
});
|
|
1940
|
+
if (!response.ok) {
|
|
1941
|
+
const errorText = await response.text();
|
|
1942
|
+
logger5.error("Gemini API \u9519\u8BEF", { status: response.status, body: errorText.slice(0, 500) });
|
|
1943
|
+
yield { type: "error", error: `Gemini API \u9519\u8BEF: ${response.status} - ${errorText}` };
|
|
1944
|
+
return;
|
|
1555
1945
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1946
|
+
const reader = response.body?.getReader();
|
|
1947
|
+
if (!reader) {
|
|
1948
|
+
yield { type: "error", error: "\u65E0\u6CD5\u83B7\u53D6\u54CD\u5E94\u6D41" };
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
yield* this.parseSSE(reader);
|
|
1560
1952
|
}
|
|
1561
1953
|
/**
|
|
1562
|
-
*
|
|
1954
|
+
* 构建请求体
|
|
1563
1955
|
*/
|
|
1564
|
-
|
|
1565
|
-
const
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1568
|
-
|
|
1956
|
+
buildRequestBody(messages, tools2, options) {
|
|
1957
|
+
const { systemInstruction, contents } = this.convertMessages(messages);
|
|
1958
|
+
const body = {
|
|
1959
|
+
contents,
|
|
1960
|
+
generationConfig: {
|
|
1961
|
+
maxOutputTokens: options.familyConfig.defaultMaxTokens ?? 65536
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
if (systemInstruction) {
|
|
1965
|
+
body.systemInstruction = systemInstruction;
|
|
1966
|
+
}
|
|
1967
|
+
if (options.enableThinking) {
|
|
1968
|
+
body.generationConfig.thinkingConfig = {
|
|
1969
|
+
thinkingBudget: 24576,
|
|
1970
|
+
includeThoughts: true
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
const geminiTools = [];
|
|
1569
1974
|
if (tools2.length > 0) {
|
|
1570
|
-
|
|
1975
|
+
geminiTools.push({
|
|
1571
1976
|
functionDeclarations: tools2.map((t) => ({
|
|
1572
1977
|
name: t.name,
|
|
1573
1978
|
description: t.description,
|
|
1574
1979
|
parameters: t.parameters
|
|
1575
1980
|
}))
|
|
1576
1981
|
});
|
|
1982
|
+
} else if (options.enableSearch) {
|
|
1983
|
+
geminiTools.push({ googleSearch: {} });
|
|
1577
1984
|
}
|
|
1578
|
-
if (
|
|
1579
|
-
|
|
1580
|
-
}
|
|
1581
|
-
if (toolsConfig.length > 0) {
|
|
1582
|
-
generateConfig.tools = toolsConfig;
|
|
1985
|
+
if (geminiTools.length > 0) {
|
|
1986
|
+
body.tools = geminiTools;
|
|
1583
1987
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1988
|
+
return body;
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* 转换消息格式
|
|
1992
|
+
*/
|
|
1993
|
+
convertMessages(messages) {
|
|
1994
|
+
let systemInstruction;
|
|
1995
|
+
const contents = [];
|
|
1996
|
+
for (const msg of messages) {
|
|
1997
|
+
switch (msg.role) {
|
|
1998
|
+
case "system":
|
|
1999
|
+
systemInstruction = { parts: [{ text: msg.content }] };
|
|
2000
|
+
break;
|
|
2001
|
+
case "user": {
|
|
2002
|
+
const textContent = msg.content || (msg.images?.length ? "\u8BF7\u5206\u6790\u8FD9\u5F20\u56FE\u7247" : "");
|
|
2003
|
+
const parts = [{ text: textContent }];
|
|
2004
|
+
if (msg.images?.length) {
|
|
2005
|
+
for (const img of msg.images) {
|
|
2006
|
+
if (img.startsWith("data:")) {
|
|
2007
|
+
const match = img.match(/^data:([^;]+);base64,(.+)$/);
|
|
2008
|
+
if (match) {
|
|
2009
|
+
parts.push({
|
|
2010
|
+
inlineData: { mimeType: match[1], data: match[2] }
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
} else {
|
|
2014
|
+
parts.push({
|
|
2015
|
+
inlineData: { mimeType: "image/jpeg", data: img }
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
contents.push({ role: "user", parts });
|
|
2021
|
+
break;
|
|
1604
2022
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
2023
|
+
case "assistant":
|
|
2024
|
+
if (msg.toolCalls?.length) {
|
|
2025
|
+
const parts = [];
|
|
2026
|
+
for (const tc of msg.toolCalls) {
|
|
2027
|
+
const funcPart = {
|
|
2028
|
+
functionCall: {
|
|
2029
|
+
name: tc.name,
|
|
2030
|
+
args: JSON.parse(tc.arguments || "{}")
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
if (tc.thoughtSignature) {
|
|
2034
|
+
funcPart.thoughtSignature = tc.thoughtSignature;
|
|
2035
|
+
}
|
|
2036
|
+
parts.push(funcPart);
|
|
2037
|
+
}
|
|
2038
|
+
contents.push({ role: "model", parts });
|
|
2039
|
+
} else {
|
|
2040
|
+
contents.push({ role: "model", parts: [{ text: msg.content }] });
|
|
1613
2041
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
2042
|
+
break;
|
|
2043
|
+
case "tool":
|
|
2044
|
+
contents.push({
|
|
2045
|
+
role: "user",
|
|
2046
|
+
parts: [{
|
|
2047
|
+
functionResponse: {
|
|
2048
|
+
name: msg.toolName || "unknown",
|
|
2049
|
+
response: { result: msg.content }
|
|
2050
|
+
}
|
|
2051
|
+
}]
|
|
2052
|
+
});
|
|
2053
|
+
break;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
return { systemInstruction, contents };
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* 解析 SSE 流
|
|
2060
|
+
*/
|
|
2061
|
+
async *parseSSE(reader) {
|
|
2062
|
+
const decoder = new TextDecoder();
|
|
2063
|
+
let buffer = "";
|
|
2064
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
2065
|
+
let textStarted = false;
|
|
2066
|
+
let toolCallIndex = 0;
|
|
2067
|
+
while (true) {
|
|
2068
|
+
const { done, value } = await reader.read();
|
|
2069
|
+
if (done) break;
|
|
2070
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2071
|
+
const lines = buffer.split("\n");
|
|
2072
|
+
buffer = lines.pop() || "";
|
|
2073
|
+
for (const line of lines) {
|
|
2074
|
+
if (!line.startsWith("data:")) continue;
|
|
2075
|
+
const data = line.slice(5).trim();
|
|
2076
|
+
if (!data) continue;
|
|
2077
|
+
try {
|
|
2078
|
+
const json = JSON.parse(data);
|
|
2079
|
+
const candidate = json.candidates?.[0];
|
|
2080
|
+
if (!candidate?.content?.parts) continue;
|
|
2081
|
+
for (const part of candidate.content.parts) {
|
|
2082
|
+
if (part.text) {
|
|
2083
|
+
if (part.thought === true) {
|
|
2084
|
+
yield { type: "thinking_delta", delta: part.text };
|
|
2085
|
+
} else {
|
|
2086
|
+
if (!textStarted) {
|
|
2087
|
+
textStarted = true;
|
|
2088
|
+
yield { type: "thinking_done" };
|
|
2089
|
+
}
|
|
2090
|
+
yield { type: "text_delta", delta: part.text };
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
if (part.functionCall) {
|
|
2094
|
+
const callId = `gemini-${toolCallIndex++}`;
|
|
2095
|
+
const toolCall = {
|
|
2096
|
+
id: callId,
|
|
2097
|
+
name: part.functionCall.name,
|
|
2098
|
+
arguments: JSON.stringify(part.functionCall.args || {})
|
|
2099
|
+
};
|
|
2100
|
+
if (part.thoughtSignature) {
|
|
2101
|
+
toolCall.thoughtSignature = part.thoughtSignature;
|
|
2102
|
+
}
|
|
2103
|
+
pendingToolCalls.set(callId, toolCall);
|
|
2104
|
+
yield { type: "tool_call_start", toolCall: { id: callId, name: toolCall.name } };
|
|
2105
|
+
yield { type: "tool_call_done", toolCall };
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
const groundingMeta = candidate.groundingMetadata;
|
|
2109
|
+
if (groundingMeta?.groundingChunks?.length) {
|
|
2110
|
+
const searchResults = [];
|
|
2111
|
+
for (const chunk of groundingMeta.groundingChunks) {
|
|
2112
|
+
if (chunk.web?.uri) {
|
|
2113
|
+
searchResults.push({
|
|
2114
|
+
title: chunk.web.title || "",
|
|
2115
|
+
url: chunk.web.uri,
|
|
2116
|
+
snippet: ""
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
if (searchResults.length > 0) {
|
|
2121
|
+
yield { type: "search_result", searchResults };
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
if (candidate.finishReason) {
|
|
2125
|
+
if (pendingToolCalls.size > 0) {
|
|
2126
|
+
yield { type: "done", finishReason: "tool_calls" };
|
|
2127
|
+
} else {
|
|
2128
|
+
yield { type: "done", finishReason: "stop" };
|
|
2129
|
+
}
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
} catch {
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
if (pendingToolCalls.size > 0) {
|
|
2137
|
+
yield { type: "done", finishReason: "tool_calls" };
|
|
2138
|
+
} else {
|
|
2139
|
+
yield { type: "done", finishReason: "stop" };
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
function createGeminiProtocol(config) {
|
|
2144
|
+
return new GeminiProtocol(config);
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
// src/providers/protocols/openai.ts
|
|
2148
|
+
var logger6 = DebugLogger.module("OpenAIProtocol");
|
|
2149
|
+
var OpenAIProtocol = class {
|
|
2150
|
+
name = "openai";
|
|
2151
|
+
apiKey;
|
|
2152
|
+
apiUrl;
|
|
2153
|
+
constructor(config) {
|
|
2154
|
+
this.apiKey = config.apiKey;
|
|
2155
|
+
this.apiUrl = config.apiUrl ?? DEFAULT_OPENROUTER_URL;
|
|
2156
|
+
}
|
|
2157
|
+
async *stream(messages, tools2, options) {
|
|
2158
|
+
const requestBody = this.buildRequestBody(messages, tools2, options);
|
|
2159
|
+
const url = `${this.apiUrl}/responses`;
|
|
2160
|
+
logger6.debug("\u53D1\u9001 OpenAI \u8BF7\u6C42", {
|
|
2161
|
+
url,
|
|
2162
|
+
model: options.model,
|
|
2163
|
+
toolsCount: tools2.length
|
|
2164
|
+
});
|
|
2165
|
+
const response = await fetch(url, {
|
|
2166
|
+
method: "POST",
|
|
2167
|
+
headers: {
|
|
2168
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
2169
|
+
"Content-Type": "application/json",
|
|
2170
|
+
"HTTP-Referer": "https://ai-chat.local",
|
|
2171
|
+
"X-Title": "AI Chat"
|
|
2172
|
+
},
|
|
2173
|
+
body: JSON.stringify(requestBody),
|
|
2174
|
+
signal: options.signal
|
|
2175
|
+
});
|
|
2176
|
+
if (!response.ok) {
|
|
2177
|
+
const errorText = await response.text();
|
|
2178
|
+
logger6.error("OpenAI API \u9519\u8BEF", { status: response.status, body: errorText.slice(0, 500) });
|
|
2179
|
+
yield { type: "error", error: `OpenAI API \u9519\u8BEF: ${response.status} - ${errorText}` };
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
const reader = response.body?.getReader();
|
|
2183
|
+
if (!reader) {
|
|
2184
|
+
yield { type: "error", error: "\u65E0\u6CD5\u83B7\u53D6\u54CD\u5E94\u6D41" };
|
|
2185
|
+
return;
|
|
2186
|
+
}
|
|
2187
|
+
yield* this.parseSSE(reader, options.enableThinking);
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* 构建请求体(OpenAI/GPT 专用)
|
|
2191
|
+
*/
|
|
2192
|
+
buildRequestBody(messages, tools2, options) {
|
|
2193
|
+
const input = this.convertMessages(messages);
|
|
2194
|
+
const body = {
|
|
2195
|
+
model: options.model,
|
|
2196
|
+
stream: true,
|
|
2197
|
+
input
|
|
2198
|
+
};
|
|
2199
|
+
if (options.enableThinking) {
|
|
2200
|
+
body.reasoning = { effort: "high" };
|
|
2201
|
+
}
|
|
2202
|
+
if (tools2.length > 0) {
|
|
2203
|
+
body.tools = tools2.map((t) => ({
|
|
2204
|
+
type: "function",
|
|
2205
|
+
name: t.name,
|
|
2206
|
+
description: t.description,
|
|
2207
|
+
parameters: t.parameters
|
|
2208
|
+
}));
|
|
2209
|
+
}
|
|
2210
|
+
return body;
|
|
2211
|
+
}
|
|
2212
|
+
convertMessages(messages) {
|
|
2213
|
+
const input = [];
|
|
2214
|
+
for (const msg of messages) {
|
|
2215
|
+
switch (msg.role) {
|
|
2216
|
+
case "system":
|
|
2217
|
+
input.push({ role: "system", content: msg.content });
|
|
2218
|
+
break;
|
|
2219
|
+
case "user": {
|
|
2220
|
+
const textContent = msg.content || (msg.images?.length ? "\u8BF7\u5206\u6790\u8FD9\u5F20\u56FE\u7247" : "");
|
|
2221
|
+
const content = [{ type: "input_text", text: textContent }];
|
|
2222
|
+
if (msg.images?.length) {
|
|
2223
|
+
for (const img of msg.images) {
|
|
2224
|
+
content.push({
|
|
2225
|
+
type: "input_image",
|
|
2226
|
+
image_url: img.startsWith("data:") ? img : `data:image/jpeg;base64,${img}`,
|
|
2227
|
+
detail: "auto"
|
|
2228
|
+
});
|
|
1618
2229
|
}
|
|
1619
|
-
yield { type: "text", text: part.text };
|
|
1620
2230
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
2231
|
+
input.push({ role: "user", content });
|
|
2232
|
+
break;
|
|
2233
|
+
}
|
|
2234
|
+
case "assistant":
|
|
2235
|
+
if (msg.toolCalls?.length) {
|
|
2236
|
+
for (const tc of msg.toolCalls) {
|
|
2237
|
+
input.push({
|
|
2238
|
+
type: "function_call",
|
|
2239
|
+
call_id: tc.id,
|
|
2240
|
+
name: tc.name,
|
|
2241
|
+
arguments: tc.arguments
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
} else {
|
|
2245
|
+
input.push({
|
|
2246
|
+
role: "developer",
|
|
2247
|
+
content: `[\u4E0A\u4E00\u8F6EAI\u56DE\u590D]: ${msg.content}`
|
|
1626
2248
|
});
|
|
1627
2249
|
}
|
|
2250
|
+
break;
|
|
2251
|
+
case "tool":
|
|
2252
|
+
input.push({
|
|
2253
|
+
type: "function_call_output",
|
|
2254
|
+
call_id: msg.toolCallId,
|
|
2255
|
+
output: msg.content
|
|
2256
|
+
});
|
|
2257
|
+
break;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
return input;
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* 解析 SSE 流(OpenAI/GPT 专用)
|
|
2264
|
+
*/
|
|
2265
|
+
async *parseSSE(reader, enableThinking) {
|
|
2266
|
+
const decoder = new TextDecoder();
|
|
2267
|
+
let buffer = "";
|
|
2268
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
2269
|
+
let currentFunctionCallId = null;
|
|
2270
|
+
const searchResults = [];
|
|
2271
|
+
let streamDone = false;
|
|
2272
|
+
let thinkingDone = false;
|
|
2273
|
+
let textStarted = false;
|
|
2274
|
+
while (true) {
|
|
2275
|
+
const { done, value } = await reader.read();
|
|
2276
|
+
if (done) break;
|
|
2277
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2278
|
+
const lines = buffer.split("\n");
|
|
2279
|
+
buffer = lines.pop() || "";
|
|
2280
|
+
for (const line of lines) {
|
|
2281
|
+
if (streamDone) continue;
|
|
2282
|
+
if (!line.startsWith("data:")) continue;
|
|
2283
|
+
const data = line.slice(5).trim();
|
|
2284
|
+
if (data === "[DONE]") {
|
|
2285
|
+
streamDone = true;
|
|
2286
|
+
if (pendingToolCalls.size > 0) {
|
|
2287
|
+
for (const tc of pendingToolCalls.values()) {
|
|
2288
|
+
yield { type: "tool_call_done", toolCall: tc };
|
|
2289
|
+
}
|
|
2290
|
+
yield { type: "done", finishReason: "tool_calls" };
|
|
2291
|
+
} else {
|
|
2292
|
+
yield { type: "done", finishReason: "stop" };
|
|
2293
|
+
}
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
try {
|
|
2297
|
+
const event = JSON.parse(data);
|
|
2298
|
+
logger6.debug("SSE \u4E8B\u4EF6", { type: event.type, event: JSON.stringify(event).slice(0, 200) });
|
|
2299
|
+
switch (event.type) {
|
|
2300
|
+
// ========== Responses API 格式 ==========
|
|
2301
|
+
case "response.output_item.added": {
|
|
2302
|
+
const item = event.item;
|
|
2303
|
+
const callId = item?.call_id || item?.id;
|
|
2304
|
+
if (item?.type === "function_call" && callId) {
|
|
2305
|
+
currentFunctionCallId = callId;
|
|
2306
|
+
let args = item.arguments || item.input || "";
|
|
2307
|
+
if (typeof args === "object") {
|
|
2308
|
+
args = JSON.stringify(args);
|
|
2309
|
+
}
|
|
2310
|
+
pendingToolCalls.set(callId, {
|
|
2311
|
+
id: callId,
|
|
2312
|
+
name: item.name || item.function?.name || "",
|
|
2313
|
+
arguments: args
|
|
2314
|
+
});
|
|
2315
|
+
logger6.debug("\u5DE5\u5177\u8C03\u7528\u5F00\u59CB", { id: callId, name: item.name, args: args.slice(0, 100) });
|
|
2316
|
+
yield { type: "tool_call_start", toolCall: { id: callId, name: item.name || item.function?.name || "" } };
|
|
2317
|
+
}
|
|
2318
|
+
break;
|
|
2319
|
+
}
|
|
2320
|
+
case "response.function_call_arguments.delta": {
|
|
2321
|
+
const delta = event.delta;
|
|
2322
|
+
if (currentFunctionCallId && delta) {
|
|
2323
|
+
const call = pendingToolCalls.get(currentFunctionCallId);
|
|
2324
|
+
if (call) {
|
|
2325
|
+
call.arguments += delta;
|
|
2326
|
+
yield { type: "tool_call_delta", toolCall: { id: currentFunctionCallId, arguments: delta } };
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
break;
|
|
2330
|
+
}
|
|
2331
|
+
case "response.function_call_arguments.done": {
|
|
2332
|
+
const callId = event.item_id || event.call_id || currentFunctionCallId;
|
|
2333
|
+
if (callId) {
|
|
2334
|
+
const existing = pendingToolCalls.get(callId);
|
|
2335
|
+
if (existing) {
|
|
2336
|
+
if (event.arguments) {
|
|
2337
|
+
existing.arguments = event.arguments;
|
|
2338
|
+
}
|
|
2339
|
+
try {
|
|
2340
|
+
JSON.parse(existing.arguments);
|
|
2341
|
+
} catch {
|
|
2342
|
+
logger6.warn("\u5DE5\u5177\u53C2\u6570\u89E3\u6790\u5931\u8D25\uFF0C\u4F7F\u7528\u7A7A\u5BF9\u8C61", { args: existing.arguments.slice(0, 100) });
|
|
2343
|
+
existing.arguments = "{}";
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
break;
|
|
2348
|
+
}
|
|
2349
|
+
case "response.output_item.done": {
|
|
2350
|
+
const item = event.item;
|
|
2351
|
+
const callId = item?.call_id || item?.id;
|
|
2352
|
+
if (item?.type === "function_call" && callId) {
|
|
2353
|
+
const existing = pendingToolCalls.get(callId);
|
|
2354
|
+
let args = item.arguments || item.input || existing?.arguments || "{}";
|
|
2355
|
+
if (typeof args === "object") {
|
|
2356
|
+
args = JSON.stringify(args);
|
|
2357
|
+
}
|
|
2358
|
+
pendingToolCalls.set(callId, {
|
|
2359
|
+
id: callId,
|
|
2360
|
+
name: item.name || item.function?.name || existing?.name || "",
|
|
2361
|
+
arguments: args
|
|
2362
|
+
});
|
|
2363
|
+
logger6.debug("\u5DE5\u5177\u8C03\u7528\u5B8C\u6210", { id: callId, name: item.name, args: args.slice(0, 100) });
|
|
2364
|
+
}
|
|
2365
|
+
break;
|
|
2366
|
+
}
|
|
2367
|
+
case "response.output_text.annotation.added": {
|
|
2368
|
+
const annotation = event.annotation;
|
|
2369
|
+
if (annotation?.url) {
|
|
2370
|
+
const exists = searchResults.some((r) => r.url === annotation.url);
|
|
2371
|
+
if (!exists) {
|
|
2372
|
+
searchResults.push({
|
|
2373
|
+
title: annotation.title || annotation.text || "",
|
|
2374
|
+
url: annotation.url,
|
|
2375
|
+
snippet: annotation.summary || annotation.snippet || ""
|
|
2376
|
+
});
|
|
2377
|
+
yield { type: "search_result", searchResults: [...searchResults] };
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
break;
|
|
2381
|
+
}
|
|
2382
|
+
case "response.output_text.delta":
|
|
2383
|
+
if (event.delta) {
|
|
2384
|
+
if (!textStarted) {
|
|
2385
|
+
textStarted = true;
|
|
2386
|
+
if (enableThinking && !thinkingDone) {
|
|
2387
|
+
thinkingDone = true;
|
|
2388
|
+
yield { type: "thinking_done" };
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
yield { type: "text_delta", delta: event.delta };
|
|
2392
|
+
}
|
|
2393
|
+
break;
|
|
2394
|
+
// GPT-5.x 支持 reasoning
|
|
2395
|
+
case "response.reasoning_summary_text.delta":
|
|
2396
|
+
if (enableThinking && event.delta && !thinkingDone) {
|
|
2397
|
+
yield { type: "thinking_delta", delta: event.delta };
|
|
2398
|
+
}
|
|
2399
|
+
break;
|
|
2400
|
+
// response.completed 事件(包含完整的工具调用参数)
|
|
2401
|
+
case "response.completed": {
|
|
2402
|
+
const response = event.response;
|
|
2403
|
+
if (response?.output?.length) {
|
|
2404
|
+
for (const item of response.output) {
|
|
2405
|
+
if (item.type === "function_call" && item.call_id) {
|
|
2406
|
+
const existing = pendingToolCalls.get(item.call_id);
|
|
2407
|
+
let args = item.arguments || existing?.arguments || "{}";
|
|
2408
|
+
if (typeof args === "object") {
|
|
2409
|
+
args = JSON.stringify(args);
|
|
2410
|
+
}
|
|
2411
|
+
pendingToolCalls.set(item.call_id, {
|
|
2412
|
+
id: item.call_id,
|
|
2413
|
+
name: item.name || existing?.name || "",
|
|
2414
|
+
arguments: args
|
|
2415
|
+
});
|
|
2416
|
+
logger6.debug("response.completed: \u66F4\u65B0\u5DE5\u5177\u53C2\u6570", { id: item.call_id, args: args.slice(0, 100) });
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
streamDone = true;
|
|
2421
|
+
if (pendingToolCalls.size > 0) {
|
|
2422
|
+
for (const tc of pendingToolCalls.values()) {
|
|
2423
|
+
logger6.debug("response.completed: \u53D1\u51FA tool_call_done", tc);
|
|
2424
|
+
yield { type: "tool_call_done", toolCall: tc };
|
|
2425
|
+
}
|
|
2426
|
+
yield { type: "done", finishReason: "tool_calls" };
|
|
2427
|
+
} else {
|
|
2428
|
+
yield { type: "done", finishReason: "stop" };
|
|
2429
|
+
}
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
} catch (e) {
|
|
2434
|
+
logger6.warn("SSE \u89E3\u6790\u9519\u8BEF", { line: line.slice(0, 100), error: String(e) });
|
|
1628
2435
|
}
|
|
1629
2436
|
}
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
2437
|
+
}
|
|
2438
|
+
if (!streamDone) {
|
|
2439
|
+
logger6.debug("\u6D41\u7ED3\u675F\uFF0C\u6267\u884C\u515C\u5E95\u903B\u8F91", { pendingToolCalls: pendingToolCalls.size });
|
|
2440
|
+
if (pendingToolCalls.size > 0) {
|
|
2441
|
+
for (const tc of pendingToolCalls.values()) {
|
|
2442
|
+
yield { type: "tool_call_done", toolCall: tc };
|
|
1633
2443
|
}
|
|
1634
2444
|
yield { type: "done", finishReason: "tool_calls" };
|
|
1635
2445
|
} else {
|
|
1636
2446
|
yield { type: "done", finishReason: "stop" };
|
|
1637
2447
|
}
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
function createOpenAIProtocol(config) {
|
|
2452
|
+
return new OpenAIProtocol(config);
|
|
2453
|
+
}
|
|
2454
|
+
var logger7 = DebugLogger.module("AnthropicProtocol");
|
|
2455
|
+
var AnthropicProtocol = class {
|
|
2456
|
+
name = "anthropic";
|
|
2457
|
+
gateway;
|
|
2458
|
+
constructor(config) {
|
|
2459
|
+
this.gateway = createGateway({
|
|
2460
|
+
apiKey: config.apiKey
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
async *stream(messages, tools2, options) {
|
|
2464
|
+
logger7.debug("\u4F7F\u7528 Vercel AI Gateway \u8C03\u7528 Claude", {
|
|
2465
|
+
model: options.model,
|
|
2466
|
+
enableThinking: options.enableThinking,
|
|
2467
|
+
toolsCount: tools2.length
|
|
2468
|
+
});
|
|
2469
|
+
try {
|
|
2470
|
+
const sdkMessages = this.convertMessages(messages);
|
|
2471
|
+
const streamParams = {
|
|
2472
|
+
model: this.gateway(options.model),
|
|
2473
|
+
messages: sdkMessages,
|
|
2474
|
+
maxOutputTokens: options.familyConfig.defaultMaxTokens,
|
|
2475
|
+
abortSignal: options.signal
|
|
2476
|
+
};
|
|
2477
|
+
if (tools2.length > 0) {
|
|
2478
|
+
streamParams.tools = this.convertTools(tools2);
|
|
2479
|
+
}
|
|
2480
|
+
if (options.enableThinking) {
|
|
2481
|
+
streamParams.providerOptions = {
|
|
2482
|
+
anthropic: {
|
|
2483
|
+
thinking: {
|
|
2484
|
+
type: "enabled",
|
|
2485
|
+
budgetTokens: 1e4
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
1644
2488
|
};
|
|
1645
|
-
} else {
|
|
1646
|
-
yield { type: "error", error: errorStr };
|
|
1647
2489
|
}
|
|
2490
|
+
logger7.debug("\u53D1\u9001\u8BF7\u6C42\u5230 Vercel AI Gateway", {
|
|
2491
|
+
model: options.model,
|
|
2492
|
+
messagesCount: sdkMessages.length,
|
|
2493
|
+
toolsCount: tools2.length,
|
|
2494
|
+
enableThinking: options.enableThinking
|
|
2495
|
+
});
|
|
2496
|
+
const result = streamText(streamParams);
|
|
2497
|
+
yield* this.parseStream(result);
|
|
2498
|
+
} catch (error) {
|
|
2499
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
2500
|
+
logger7.error("Vercel AI Gateway \u9519\u8BEF", { error: errorMessage });
|
|
2501
|
+
yield { type: "error", error: errorMessage };
|
|
1648
2502
|
}
|
|
1649
2503
|
}
|
|
1650
2504
|
/**
|
|
1651
|
-
*
|
|
2505
|
+
* 转换消息格式(ProtocolMessage → AI SDK CoreMessage)
|
|
2506
|
+
* 简化版:主要处理文本消息,工具调用通过历史上下文传递
|
|
1652
2507
|
*/
|
|
1653
2508
|
convertMessages(messages) {
|
|
1654
|
-
const
|
|
2509
|
+
const result = [];
|
|
1655
2510
|
for (const msg of messages) {
|
|
1656
2511
|
switch (msg.role) {
|
|
1657
2512
|
case "system":
|
|
1658
|
-
|
|
1659
|
-
{ role: "user", parts: [{ text: msg.content }] },
|
|
1660
|
-
{ role: "model", parts: [{ text: "OK" }] }
|
|
1661
|
-
);
|
|
2513
|
+
result.push({ role: "system", content: msg.content });
|
|
1662
2514
|
break;
|
|
1663
2515
|
case "user": {
|
|
1664
|
-
const
|
|
2516
|
+
const textContent = msg.content || (msg.images?.length ? "\u8BF7\u5206\u6790\u8FD9\u5F20\u56FE\u7247" : "");
|
|
1665
2517
|
if (msg.images?.length) {
|
|
2518
|
+
const content = [
|
|
2519
|
+
{ type: "text", text: textContent }
|
|
2520
|
+
];
|
|
1666
2521
|
for (const img of msg.images) {
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
mimeType: "image/jpeg",
|
|
1671
|
-
data: base64
|
|
1672
|
-
}
|
|
2522
|
+
content.push({
|
|
2523
|
+
type: "image",
|
|
2524
|
+
image: img.startsWith("data:") ? img : `data:image/jpeg;base64,${img}`
|
|
1673
2525
|
});
|
|
1674
2526
|
}
|
|
2527
|
+
result.push({ role: "user", content });
|
|
2528
|
+
} else {
|
|
2529
|
+
result.push({ role: "user", content: textContent });
|
|
1675
2530
|
}
|
|
1676
|
-
geminiMessages.push({ role: "user", parts });
|
|
1677
2531
|
break;
|
|
1678
2532
|
}
|
|
1679
2533
|
case "assistant":
|
|
1680
|
-
if (msg.
|
|
1681
|
-
|
|
1682
|
-
if (msg.content) {
|
|
1683
|
-
parts.push({ text: msg.content });
|
|
1684
|
-
}
|
|
1685
|
-
for (const tc of msg.toolCalls) {
|
|
1686
|
-
parts.push({
|
|
1687
|
-
functionCall: {
|
|
1688
|
-
name: tc.name,
|
|
1689
|
-
args: JSON.parse(tc.arguments || "{}")
|
|
1690
|
-
}
|
|
1691
|
-
});
|
|
1692
|
-
}
|
|
1693
|
-
geminiMessages.push({ role: "model", parts });
|
|
1694
|
-
} else {
|
|
1695
|
-
geminiMessages.push({
|
|
1696
|
-
role: "model",
|
|
1697
|
-
parts: [{ text: msg.content }]
|
|
1698
|
-
});
|
|
2534
|
+
if (msg.content) {
|
|
2535
|
+
result.push({ role: "assistant", content: msg.content });
|
|
1699
2536
|
}
|
|
1700
2537
|
break;
|
|
1701
2538
|
case "tool":
|
|
1702
|
-
|
|
2539
|
+
result.push({
|
|
1703
2540
|
role: "user",
|
|
1704
|
-
|
|
1705
|
-
functionResponse: {
|
|
1706
|
-
name: msg.toolName || "unknown_tool",
|
|
1707
|
-
response: { result: msg.content }
|
|
1708
|
-
}
|
|
1709
|
-
}]
|
|
2541
|
+
content: `[\u5DE5\u5177 ${msg.toolName || "unknown"} \u8FD4\u56DE\u7ED3\u679C]: ${msg.content}`
|
|
1710
2542
|
});
|
|
1711
2543
|
break;
|
|
1712
2544
|
}
|
|
1713
2545
|
}
|
|
1714
|
-
return
|
|
2546
|
+
return result;
|
|
2547
|
+
}
|
|
2548
|
+
/**
|
|
2549
|
+
* 转换工具格式(ProtocolToolDefinition → AI SDK tools)
|
|
2550
|
+
* 注意:Vercel Gateway 对工具格式有特殊要求,这里使用标准格式
|
|
2551
|
+
*/
|
|
2552
|
+
convertTools(tools2) {
|
|
2553
|
+
const result = {};
|
|
2554
|
+
for (const t of tools2) {
|
|
2555
|
+
const zodSchema = this.jsonSchemaToZod(t.parameters);
|
|
2556
|
+
result[t.name] = tool$1({
|
|
2557
|
+
description: t.description,
|
|
2558
|
+
inputSchema: zodSchema
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
return result;
|
|
2562
|
+
}
|
|
2563
|
+
/**
|
|
2564
|
+
* 将 JSON Schema 转换为 Zod Schema
|
|
2565
|
+
*/
|
|
2566
|
+
jsonSchemaToZod(schema) {
|
|
2567
|
+
const shape = {};
|
|
2568
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
2569
|
+
let zodType;
|
|
2570
|
+
switch (prop.type) {
|
|
2571
|
+
case "string":
|
|
2572
|
+
zodType = prop.enum ? z.enum(prop.enum) : z.string();
|
|
2573
|
+
break;
|
|
2574
|
+
case "number":
|
|
2575
|
+
case "integer":
|
|
2576
|
+
zodType = z.number();
|
|
2577
|
+
break;
|
|
2578
|
+
case "boolean":
|
|
2579
|
+
zodType = z.boolean();
|
|
2580
|
+
break;
|
|
2581
|
+
case "array":
|
|
2582
|
+
zodType = z.array(z.string());
|
|
2583
|
+
break;
|
|
2584
|
+
default:
|
|
2585
|
+
zodType = z.unknown();
|
|
2586
|
+
}
|
|
2587
|
+
if (prop.description) {
|
|
2588
|
+
zodType = zodType.describe(prop.description);
|
|
2589
|
+
}
|
|
2590
|
+
if (!schema.required.includes(key)) {
|
|
2591
|
+
zodType = zodType.optional();
|
|
2592
|
+
}
|
|
2593
|
+
shape[key] = zodType;
|
|
2594
|
+
}
|
|
2595
|
+
return z.object(shape);
|
|
2596
|
+
}
|
|
2597
|
+
/**
|
|
2598
|
+
* 解析 AI SDK 流式响应
|
|
2599
|
+
*/
|
|
2600
|
+
async *parseStream(result) {
|
|
2601
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
2602
|
+
let hasThinking = false;
|
|
2603
|
+
let thinkingDone = false;
|
|
2604
|
+
try {
|
|
2605
|
+
for await (const part of result.fullStream) {
|
|
2606
|
+
switch (part.type) {
|
|
2607
|
+
case "reasoning-delta":
|
|
2608
|
+
if ("text" in part && part.text) {
|
|
2609
|
+
hasThinking = true;
|
|
2610
|
+
yield { type: "thinking_delta", delta: part.text };
|
|
2611
|
+
}
|
|
2612
|
+
break;
|
|
2613
|
+
case "reasoning-end":
|
|
2614
|
+
if (hasThinking && !thinkingDone) {
|
|
2615
|
+
thinkingDone = true;
|
|
2616
|
+
yield { type: "thinking_done" };
|
|
2617
|
+
}
|
|
2618
|
+
break;
|
|
2619
|
+
case "text-delta":
|
|
2620
|
+
if (hasThinking && !thinkingDone) {
|
|
2621
|
+
thinkingDone = true;
|
|
2622
|
+
yield { type: "thinking_done" };
|
|
2623
|
+
}
|
|
2624
|
+
if ("text" in part && part.text) {
|
|
2625
|
+
yield { type: "text_delta", delta: part.text };
|
|
2626
|
+
}
|
|
2627
|
+
break;
|
|
2628
|
+
case "tool-call":
|
|
2629
|
+
if ("toolCallId" in part && "toolName" in part) {
|
|
2630
|
+
const toolCallId = part.toolCallId;
|
|
2631
|
+
const toolName = part.toolName;
|
|
2632
|
+
const input = "input" in part ? part.input : null;
|
|
2633
|
+
const args = input ? typeof input === "string" ? input : JSON.stringify(input) : "{}";
|
|
2634
|
+
yield {
|
|
2635
|
+
type: "tool_call_start",
|
|
2636
|
+
toolCall: { id: toolCallId, name: toolName }
|
|
2637
|
+
};
|
|
2638
|
+
toolCalls.set(toolCallId, {
|
|
2639
|
+
id: toolCallId,
|
|
2640
|
+
name: toolName,
|
|
2641
|
+
arguments: args
|
|
2642
|
+
});
|
|
2643
|
+
yield {
|
|
2644
|
+
type: "tool_call_done",
|
|
2645
|
+
toolCall: {
|
|
2646
|
+
id: toolCallId,
|
|
2647
|
+
name: toolName,
|
|
2648
|
+
arguments: args
|
|
2649
|
+
}
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
break;
|
|
2653
|
+
case "finish":
|
|
2654
|
+
const finishReason = toolCalls.size > 0 ? "tool_calls" : "finishReason" in part && part.finishReason === "stop" ? "stop" : "finishReason" in part && part.finishReason === "length" ? "length" : "stop";
|
|
2655
|
+
yield { type: "done", finishReason };
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
yield { type: "done", finishReason: "stop" };
|
|
2660
|
+
} catch (error) {
|
|
2661
|
+
const errorMessage = error instanceof Error ? error.message : "Stream error";
|
|
2662
|
+
logger7.error("\u6D41\u5F0F\u5904\u7406\u9519\u8BEF", { error: errorMessage });
|
|
2663
|
+
yield { type: "error", error: errorMessage };
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
};
|
|
2667
|
+
function createAnthropicProtocol(config) {
|
|
2668
|
+
return new AnthropicProtocol(config);
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
// src/providers/unified-adapter.ts
|
|
2672
|
+
var logger8 = DebugLogger.module("UnifiedAdapter");
|
|
2673
|
+
var UnifiedAdapter = class {
|
|
2674
|
+
name = "unified";
|
|
2675
|
+
/** 支持的模型列表(从 Registry 获取) */
|
|
2676
|
+
get supportedModels() {
|
|
2677
|
+
return getVisibleModels().map((m) => m.id);
|
|
2678
|
+
}
|
|
2679
|
+
protocols = /* @__PURE__ */ new Map();
|
|
2680
|
+
constructor(config) {
|
|
2681
|
+
if (config.arkApiKey) {
|
|
2682
|
+
this.protocols.set("ark", createArkProtocol({
|
|
2683
|
+
apiKey: config.arkApiKey,
|
|
2684
|
+
apiUrl: config.arkApiUrl
|
|
2685
|
+
}));
|
|
2686
|
+
this.protocols.set("deepseek", createDeepSeekProtocol({
|
|
2687
|
+
apiKey: config.arkApiKey,
|
|
2688
|
+
apiUrl: config.arkApiUrl
|
|
2689
|
+
}));
|
|
2690
|
+
}
|
|
2691
|
+
if (config.qwenApiKey) {
|
|
2692
|
+
this.protocols.set("qwen", createQwenProtocol({
|
|
2693
|
+
apiKey: config.qwenApiKey,
|
|
2694
|
+
apiUrl: config.qwenApiUrl
|
|
2695
|
+
}));
|
|
2696
|
+
}
|
|
2697
|
+
if (config.geminiApiKey) {
|
|
2698
|
+
this.protocols.set("gemini", createGeminiProtocol({
|
|
2699
|
+
apiKey: config.geminiApiKey,
|
|
2700
|
+
apiUrl: config.geminiApiUrl
|
|
2701
|
+
}));
|
|
2702
|
+
}
|
|
2703
|
+
if (config.openrouterApiKey) {
|
|
2704
|
+
this.protocols.set("openai", createOpenAIProtocol({
|
|
2705
|
+
apiKey: config.openrouterApiKey,
|
|
2706
|
+
apiUrl: config.openrouterApiUrl
|
|
2707
|
+
}));
|
|
2708
|
+
}
|
|
2709
|
+
if (config.vercelApiKey) {
|
|
2710
|
+
this.protocols.set("anthropic", createAnthropicProtocol({
|
|
2711
|
+
apiKey: config.vercelApiKey
|
|
2712
|
+
}));
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
/**
|
|
2716
|
+
* 检查是否支持指定模型
|
|
2717
|
+
*/
|
|
2718
|
+
supportsModel(model) {
|
|
2719
|
+
const protocol = getModelProtocol(model);
|
|
2720
|
+
return protocol ? this.protocols.has(protocol) : false;
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* 获取模型的家族配置
|
|
2724
|
+
*/
|
|
2725
|
+
getModelFamilyConfig(model) {
|
|
2726
|
+
return getModelFamily(model);
|
|
2727
|
+
}
|
|
2728
|
+
/**
|
|
2729
|
+
* 实现 ProviderAdapter.streamOnce 接口
|
|
2730
|
+
*
|
|
2731
|
+
* @param messages 标准消息列表
|
|
2732
|
+
* @param tools 工具定义
|
|
2733
|
+
* @param options 调用选项
|
|
2734
|
+
* @returns 标准 StreamChunk 流
|
|
2735
|
+
*/
|
|
2736
|
+
async *streamOnce(messages, tools2, options) {
|
|
2737
|
+
const { model, enableThinking = false, enableSearch = false, signal } = options;
|
|
2738
|
+
const protocolMessages = messages.map((m) => ({
|
|
2739
|
+
role: m.role,
|
|
2740
|
+
content: m.content,
|
|
2741
|
+
images: m.images,
|
|
2742
|
+
toolCalls: m.toolCalls?.map((tc) => ({
|
|
2743
|
+
id: tc.id,
|
|
2744
|
+
name: tc.name,
|
|
2745
|
+
arguments: tc.arguments,
|
|
2746
|
+
thoughtSignature: tc.thought_signature
|
|
2747
|
+
})),
|
|
2748
|
+
toolCallId: m.toolCallId,
|
|
2749
|
+
toolName: m.toolName
|
|
2750
|
+
}));
|
|
2751
|
+
const protocolTools = tools2;
|
|
2752
|
+
yield* this.stream(protocolMessages, protocolTools, {
|
|
2753
|
+
model,
|
|
2754
|
+
enableThinking,
|
|
2755
|
+
enableSearch,
|
|
2756
|
+
signal
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
/**
|
|
2760
|
+
* 流式调用(内部方法)
|
|
2761
|
+
*/
|
|
2762
|
+
async *stream(messages, tools2, options) {
|
|
2763
|
+
const { model, enableThinking = false, enableSearch = false, signal } = options;
|
|
2764
|
+
const protocolId = getModelProtocol(model);
|
|
2765
|
+
const familyConfig = getModelFamily(model);
|
|
2766
|
+
if (!protocolId) {
|
|
2767
|
+
yield { type: "error", error: `\u672A\u77E5\u6A21\u578B: ${model}` };
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
if (!familyConfig) {
|
|
2771
|
+
yield { type: "error", error: `\u6A21\u578B ${model} \u7F3A\u5C11\u5BB6\u65CF\u914D\u7F6E` };
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
const protocol = this.protocols.get(protocolId);
|
|
2775
|
+
if (!protocol) {
|
|
2776
|
+
yield { type: "error", error: `Protocol ${protocolId} \u672A\u914D\u7F6E` };
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
logger8.debug("\u5F00\u59CB\u6D41\u5F0F\u8C03\u7528", {
|
|
2780
|
+
model,
|
|
2781
|
+
protocol: protocolId,
|
|
2782
|
+
family: familyConfig.id,
|
|
2783
|
+
enableThinking,
|
|
2784
|
+
enableSearch
|
|
2785
|
+
});
|
|
2786
|
+
const rawStream = protocol.stream(messages, tools2, {
|
|
2787
|
+
model,
|
|
2788
|
+
familyConfig,
|
|
2789
|
+
enableThinking,
|
|
2790
|
+
enableSearch,
|
|
2791
|
+
signal
|
|
2792
|
+
});
|
|
2793
|
+
yield* this.transformEvents(rawStream, familyConfig, enableThinking, enableSearch);
|
|
2794
|
+
}
|
|
2795
|
+
/**
|
|
2796
|
+
* 将 RawEvent 转换为 StreamChunk
|
|
2797
|
+
*
|
|
2798
|
+
* 根据 FamilyConfig 处理行为差异
|
|
2799
|
+
*/
|
|
2800
|
+
async *transformEvents(rawStream, familyConfig, enableThinking, enableSearch) {
|
|
2801
|
+
let thinkingStarted = false;
|
|
2802
|
+
for await (const event of rawStream) {
|
|
2803
|
+
switch (event.type) {
|
|
2804
|
+
// ========== Thinking 处理 ==========
|
|
2805
|
+
case "thinking_delta":
|
|
2806
|
+
if (enableThinking && familyConfig.supportsThinking && event.delta) {
|
|
2807
|
+
let delta = event.delta;
|
|
2808
|
+
if (!thinkingStarted) {
|
|
2809
|
+
delta = delta.replace(/^\n+/, "");
|
|
2810
|
+
if (delta) thinkingStarted = true;
|
|
2811
|
+
}
|
|
2812
|
+
if (delta) {
|
|
2813
|
+
yield { type: "thinking", thinking: delta };
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
break;
|
|
2817
|
+
case "thinking_done":
|
|
2818
|
+
if (enableThinking && familyConfig.supportsThinking) {
|
|
2819
|
+
yield { type: "thinking_done" };
|
|
2820
|
+
}
|
|
2821
|
+
break;
|
|
2822
|
+
// ========== 文本处理 ==========
|
|
2823
|
+
case "text_delta":
|
|
2824
|
+
if (event.delta) {
|
|
2825
|
+
yield { type: "text", text: event.delta };
|
|
2826
|
+
}
|
|
2827
|
+
break;
|
|
2828
|
+
// ========== 工具调用处理 ==========
|
|
2829
|
+
case "tool_call_done":
|
|
2830
|
+
if (event.toolCall) {
|
|
2831
|
+
const toolCall = {
|
|
2832
|
+
id: event.toolCall.id || "",
|
|
2833
|
+
name: event.toolCall.name || "",
|
|
2834
|
+
arguments: event.toolCall.arguments || "{}"
|
|
2835
|
+
};
|
|
2836
|
+
if (event.toolCall.thoughtSignature) {
|
|
2837
|
+
toolCall.thought_signature = event.toolCall.thoughtSignature;
|
|
2838
|
+
}
|
|
2839
|
+
yield { type: "tool_call", toolCall };
|
|
2840
|
+
}
|
|
2841
|
+
break;
|
|
2842
|
+
// ========== 搜索结果处理 ==========
|
|
2843
|
+
case "search_result":
|
|
2844
|
+
if (enableSearch && event.searchResults) {
|
|
2845
|
+
const searchResults = event.searchResults.map((r) => ({
|
|
2846
|
+
title: r.title,
|
|
2847
|
+
url: r.url,
|
|
2848
|
+
snippet: r.snippet
|
|
2849
|
+
}));
|
|
2850
|
+
yield { type: "search_result", searchResults };
|
|
2851
|
+
}
|
|
2852
|
+
break;
|
|
2853
|
+
// ========== 完成处理 ==========
|
|
2854
|
+
case "done":
|
|
2855
|
+
yield {
|
|
2856
|
+
type: "done",
|
|
2857
|
+
finishReason: event.finishReason || "stop"
|
|
2858
|
+
};
|
|
2859
|
+
break;
|
|
2860
|
+
// ========== 错误处理 ==========
|
|
2861
|
+
case "error":
|
|
2862
|
+
yield { type: "error", error: event.error || "\u672A\u77E5\u9519\u8BEF" };
|
|
2863
|
+
break;
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
1715
2866
|
}
|
|
1716
2867
|
};
|
|
1717
|
-
function
|
|
1718
|
-
return new
|
|
2868
|
+
function createUnifiedAdapter(config) {
|
|
2869
|
+
return new UnifiedAdapter(config);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
// src/builtin-tools/web-search.ts
|
|
2873
|
+
function createWebSearchTool(tavilyApiKey) {
|
|
2874
|
+
return {
|
|
2875
|
+
name: "web_search",
|
|
2876
|
+
description: "\u8054\u7F51\u641C\u7D22\u5DE5\u5177\u3002\u8F93\u5165 query\uFF08\u641C\u7D22\u5173\u952E\u8BCD/\u95EE\u9898\uFF09\uFF0C\u8FD4\u56DE\u641C\u7D22\u7ED3\u679C\u5217\u8868\uFF08title/url/snippet\uFF09\u3002\u7528\u4E8E\u83B7\u53D6\u5B9E\u65F6\u4FE1\u606F\u4E0E\u53EF\u5F15\u7528\u6765\u6E90\u3002",
|
|
2877
|
+
parameters: {
|
|
2878
|
+
type: "object",
|
|
2879
|
+
properties: {
|
|
2880
|
+
query: { type: "string", description: "\u641C\u7D22\u5173\u952E\u8BCD\u6216\u95EE\u9898\uFF08\u5FC5\u586B\uFF09" },
|
|
2881
|
+
max_results: { type: "number", description: "\u6700\u5927\u8FD4\u56DE\u7ED3\u679C\u6570\uFF08\u53EF\u9009\uFF0C\u9ED8\u8BA4 5\uFF09" }
|
|
2882
|
+
},
|
|
2883
|
+
required: ["query"]
|
|
2884
|
+
},
|
|
2885
|
+
// 结果类型:让前端/调试可识别(不强依赖)
|
|
2886
|
+
resultType: "search_results",
|
|
2887
|
+
execute: async (args, ctx) => {
|
|
2888
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
2889
|
+
const maxResults = typeof args.max_results === "number" && Number.isFinite(args.max_results) ? Math.max(1, Math.min(10, Math.floor(args.max_results))) : 5;
|
|
2890
|
+
if (!query.trim()) {
|
|
2891
|
+
return JSON.stringify({ query: "", results: [], error: "\u7F3A\u5C11 query" });
|
|
2892
|
+
}
|
|
2893
|
+
if (!tavilyApiKey) {
|
|
2894
|
+
return JSON.stringify({ query, results: [], error: "\u7F3A\u5C11 Tavily API Key" });
|
|
2895
|
+
}
|
|
2896
|
+
const resp = await fetch("https://api.tavily.com/search", {
|
|
2897
|
+
method: "POST",
|
|
2898
|
+
headers: {
|
|
2899
|
+
Authorization: `Bearer ${tavilyApiKey}`,
|
|
2900
|
+
"Content-Type": "application/json"
|
|
2901
|
+
},
|
|
2902
|
+
body: JSON.stringify({
|
|
2903
|
+
query,
|
|
2904
|
+
max_results: maxResults,
|
|
2905
|
+
search_depth: "basic",
|
|
2906
|
+
include_answer: false,
|
|
2907
|
+
include_raw_content: false
|
|
2908
|
+
}),
|
|
2909
|
+
signal: ctx.signal
|
|
2910
|
+
});
|
|
2911
|
+
if (!resp.ok) {
|
|
2912
|
+
const t = await resp.text().catch(() => "");
|
|
2913
|
+
return JSON.stringify({ query, results: [], error: `Tavily /search \u9519\u8BEF: ${resp.status} ${t}`.trim() });
|
|
2914
|
+
}
|
|
2915
|
+
const data = await resp.json().catch(() => null);
|
|
2916
|
+
const results = [];
|
|
2917
|
+
const arr = data && typeof data === "object" && Array.isArray(data.results) ? data.results : [];
|
|
2918
|
+
for (const r of arr) {
|
|
2919
|
+
const url = typeof r?.url === "string" ? r.url : "";
|
|
2920
|
+
if (!url) continue;
|
|
2921
|
+
const title = typeof r?.title === "string" ? r.title : "";
|
|
2922
|
+
const snippet = typeof r?.content === "string" ? r.content : "";
|
|
2923
|
+
results.push({ title, url, snippet });
|
|
2924
|
+
}
|
|
2925
|
+
return JSON.stringify({ query, results });
|
|
2926
|
+
}
|
|
2927
|
+
};
|
|
1719
2928
|
}
|
|
1720
2929
|
|
|
1721
2930
|
// src/agent.ts
|
|
1722
2931
|
var HybridAgent = class {
|
|
1723
2932
|
config;
|
|
1724
|
-
|
|
2933
|
+
adapter;
|
|
1725
2934
|
orchestrator;
|
|
1726
2935
|
geminiClient;
|
|
1727
2936
|
toolExecutor;
|
|
@@ -1735,76 +2944,58 @@ var HybridAgent = class {
|
|
|
1735
2944
|
arkApiKey: config.arkApiKey,
|
|
1736
2945
|
arkApiUrl: config.arkApiUrl || DEFAULT_ARK_URL,
|
|
1737
2946
|
qwenApiKey: config.qwenApiKey || "",
|
|
1738
|
-
qwenApiUrl: config.qwenApiUrl ||
|
|
2947
|
+
qwenApiUrl: config.qwenApiUrl || DEFAULT_QWEN_URL,
|
|
1739
2948
|
openrouterApiKey: config.openrouterApiKey || "",
|
|
1740
2949
|
openrouterApiUrl: config.openrouterApiUrl || DEFAULT_OPENROUTER_URL,
|
|
2950
|
+
vercelApiKey: config.vercelApiKey || "",
|
|
2951
|
+
tavilyApiKey: config.tavilyApiKey || "",
|
|
1741
2952
|
geminiApiKey: config.geminiApiKey,
|
|
1742
|
-
cwd: config.cwd || process.cwd()
|
|
1743
|
-
planPrompt: config.planPrompt || PLAN_MODE_PROMPT
|
|
2953
|
+
cwd: config.cwd || process.cwd()
|
|
1744
2954
|
};
|
|
1745
2955
|
this.geminiClient = new GoogleGenAI({ apiKey: this.config.geminiApiKey });
|
|
1746
2956
|
this.toolExecutor = toolExecutor || createDefaultToolExecutor(this.config.cwd);
|
|
1747
2957
|
this.toolConfig = config.tools;
|
|
1748
2958
|
this.tools = /* @__PURE__ */ new Map();
|
|
1749
|
-
this.
|
|
2959
|
+
this.adapter = new UnifiedAdapter({
|
|
2960
|
+
arkApiKey: this.config.arkApiKey,
|
|
2961
|
+
arkApiUrl: this.config.arkApiUrl,
|
|
2962
|
+
qwenApiKey: this.config.qwenApiKey,
|
|
2963
|
+
qwenApiUrl: this.config.qwenApiUrl,
|
|
2964
|
+
geminiApiKey: this.config.geminiApiKey,
|
|
2965
|
+
openrouterApiKey: this.config.openrouterApiKey,
|
|
2966
|
+
openrouterApiUrl: this.config.openrouterApiUrl,
|
|
2967
|
+
vercelApiKey: this.config.vercelApiKey
|
|
2968
|
+
});
|
|
1750
2969
|
this.orchestrator = new ChatOrchestrator({
|
|
1751
2970
|
maxIterations: 10,
|
|
1752
2971
|
executeTool: this.executeTool.bind(this),
|
|
1753
2972
|
tools: this.tools,
|
|
1754
|
-
// 传入工具列表,用于获取 sideEffects
|
|
1755
|
-
// autoRunConfig 在每次 chat 调用时通过 options 传递
|
|
1756
2973
|
onToolApprovalRequest: config.onToolApprovalRequest,
|
|
1757
|
-
// 传递工具批准回调
|
|
1758
2974
|
getAutoRunConfig: config.getAutoRunConfig
|
|
1759
|
-
// 传递动态获取配置回调
|
|
1760
2975
|
});
|
|
1761
2976
|
}
|
|
1762
2977
|
/** 异步初始化工具(在第一次 chat 前调用) */
|
|
1763
2978
|
async asyncInit() {
|
|
1764
2979
|
if (this.toolConfig && this.tools.size === 0) {
|
|
1765
2980
|
const resolvedTools = await resolveTools(this.toolConfig);
|
|
1766
|
-
for (const
|
|
1767
|
-
this.tools.set(
|
|
2981
|
+
for (const tool3 of resolvedTools) {
|
|
2982
|
+
this.tools.set(tool3.name, tool3);
|
|
1768
2983
|
}
|
|
1769
2984
|
}
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
initializeAdapters() {
|
|
1773
|
-
if (this.config.arkApiKey) {
|
|
1774
|
-
this.adapters.set("ark", new ArkAdapter({
|
|
1775
|
-
apiKey: this.config.arkApiKey,
|
|
1776
|
-
apiUrl: this.config.arkApiUrl
|
|
1777
|
-
}));
|
|
1778
|
-
}
|
|
1779
|
-
if (this.config.qwenApiKey) {
|
|
1780
|
-
this.adapters.set("qwen", new QwenAdapter({
|
|
1781
|
-
apiKey: this.config.qwenApiKey,
|
|
1782
|
-
apiUrl: this.config.qwenApiUrl
|
|
1783
|
-
}));
|
|
1784
|
-
}
|
|
1785
|
-
if (this.config.geminiApiKey) {
|
|
1786
|
-
this.adapters.set("gemini", new GeminiAdapter({
|
|
1787
|
-
apiKey: this.config.geminiApiKey
|
|
1788
|
-
}));
|
|
1789
|
-
}
|
|
1790
|
-
if (this.config.openrouterApiKey) {
|
|
1791
|
-
this.adapters.set("openrouter", new OpenRouterAdapter({
|
|
1792
|
-
apiKey: this.config.openrouterApiKey,
|
|
1793
|
-
apiUrl: this.config.openrouterApiUrl
|
|
1794
|
-
}));
|
|
2985
|
+
if (this.config.tavilyApiKey && !this.tools.has("web_search")) {
|
|
2986
|
+
this.tools.set("web_search", createWebSearchTool(this.config.tavilyApiKey));
|
|
1795
2987
|
}
|
|
1796
2988
|
}
|
|
2989
|
+
/** 获取模型的家族配置 */
|
|
2990
|
+
getModelFamilyConfig(model) {
|
|
2991
|
+
return getModelFamily(model);
|
|
2992
|
+
}
|
|
1797
2993
|
/**
|
|
1798
|
-
*
|
|
2994
|
+
* 判断模型提供商(兼容旧接口)
|
|
1799
2995
|
*/
|
|
1800
2996
|
getModelProvider(model) {
|
|
1801
2997
|
return routeModelToProvider(model);
|
|
1802
2998
|
}
|
|
1803
|
-
/** 获取 Adapter */
|
|
1804
|
-
getAdapter(model) {
|
|
1805
|
-
const providerName = this.getModelProvider(model);
|
|
1806
|
-
return this.adapters.get(providerName);
|
|
1807
|
-
}
|
|
1808
2999
|
/**
|
|
1809
3000
|
* 调试:获取模型路由信息
|
|
1810
3001
|
*/
|
|
@@ -1812,43 +3003,67 @@ var HybridAgent = class {
|
|
|
1812
3003
|
const result = routeModelWithDetails(model);
|
|
1813
3004
|
return {
|
|
1814
3005
|
...result,
|
|
1815
|
-
available: this.
|
|
3006
|
+
available: this.adapter.supportsModel(model),
|
|
3007
|
+
familyConfig: getModelFamily(model)
|
|
1816
3008
|
};
|
|
1817
3009
|
}
|
|
1818
3010
|
/** 构建系统提示词 */
|
|
1819
3011
|
buildSystemPrompt(options) {
|
|
1820
|
-
|
|
1821
|
-
|
|
3012
|
+
const model = options.model || DEFAULT_MODEL;
|
|
3013
|
+
const familyConfig = getModelFamily(model);
|
|
3014
|
+
let prompt = AGENT_MODE_PROMPT;
|
|
3015
|
+
if (options.enableWebSearch && familyConfig && !modelSupportsNativeSearch(model)) {
|
|
3016
|
+
prompt += "\n\n\u3010\u8054\u7F51\u641C\u7D22\u3011\u5F53\u7528\u6237\u95EE\u9898\u9700\u8981\u5B9E\u65F6\u4FE1\u606F/\u6700\u65B0\u4E8B\u5B9E/\u53EF\u5F15\u7528\u6765\u6E90\u65F6\uFF0C\u8BF7\u5148\u8C03\u7528 web_search \u5DE5\u5177\u83B7\u53D6\u7ED3\u679C\uFF0C\u7136\u540E\u57FA\u4E8E\u8FD4\u56DE\u7684 title/url/snippet \u4F5C\u7B54\uFF0C\u5E76\u5728\u56DE\u7B54\u4E2D\u7ED9\u51FA\u6765\u6E90\u94FE\u63A5\u3002";
|
|
1822
3017
|
}
|
|
1823
|
-
return
|
|
3018
|
+
return prompt;
|
|
1824
3019
|
}
|
|
1825
3020
|
/** 获取默认深度思考模式 */
|
|
1826
3021
|
getDefaultThinkingMode() {
|
|
1827
3022
|
return "disabled";
|
|
1828
3023
|
}
|
|
1829
3024
|
/** 创建工具执行上下文 */
|
|
1830
|
-
createToolContext(signal) {
|
|
3025
|
+
createToolContext(signal, hooks) {
|
|
1831
3026
|
return {
|
|
1832
3027
|
cwd: this.config.cwd,
|
|
1833
3028
|
geminiClient: this.geminiClient,
|
|
1834
|
-
executeCommand: (command, cwd) => this.toolExecutor.executeCommand(command, cwd || this.config.cwd, signal
|
|
3029
|
+
executeCommand: (command, cwd) => this.toolExecutor.executeCommand(command, cwd || this.config.cwd, signal, {
|
|
3030
|
+
onStdout: hooks?.onStdout,
|
|
3031
|
+
onStderr: hooks?.onStderr
|
|
3032
|
+
}),
|
|
1835
3033
|
signal
|
|
1836
3034
|
};
|
|
1837
3035
|
}
|
|
1838
3036
|
/** 执行工具 */
|
|
1839
|
-
async executeTool(name, args, signal) {
|
|
1840
|
-
const
|
|
1841
|
-
if (!
|
|
3037
|
+
async executeTool(name, args, signal, hooks) {
|
|
3038
|
+
const tool3 = this.tools.get(name);
|
|
3039
|
+
if (!tool3) {
|
|
1842
3040
|
return `\u672A\u77E5\u5DE5\u5177: ${name}`;
|
|
1843
3041
|
}
|
|
1844
|
-
return await
|
|
3042
|
+
return await tool3.execute(args, this.createToolContext(signal, hooks));
|
|
1845
3043
|
}
|
|
1846
3044
|
/** 获取工具定义列表 */
|
|
1847
|
-
getToolDefinitions() {
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
3045
|
+
getToolDefinitions(enabledTools, forceInclude) {
|
|
3046
|
+
const allTools = Array.from(this.tools.values());
|
|
3047
|
+
let selected = enabledTools !== void 0 ? allTools.filter((t) => enabledTools.includes(t.name)) : allTools;
|
|
3048
|
+
if (forceInclude?.length) {
|
|
3049
|
+
for (const name of forceInclude) {
|
|
3050
|
+
if (!selected.some((t) => t.name === name)) {
|
|
3051
|
+
const t = this.tools.get(name);
|
|
3052
|
+
if (t) selected = [...selected, t];
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
return selected.map((tool3) => ({
|
|
3057
|
+
name: tool3.name,
|
|
3058
|
+
description: tool3.description,
|
|
3059
|
+
parameters: tool3.parameters
|
|
3060
|
+
}));
|
|
3061
|
+
}
|
|
3062
|
+
/** 获取所有工具列表(用于设置面板) */
|
|
3063
|
+
getAllTools() {
|
|
3064
|
+
return Array.from(this.tools.values()).map((tool3) => ({
|
|
3065
|
+
name: tool3.name,
|
|
3066
|
+
description: tool3.description
|
|
1852
3067
|
}));
|
|
1853
3068
|
}
|
|
1854
3069
|
/**
|
|
@@ -1861,15 +3076,24 @@ var HybridAgent = class {
|
|
|
1861
3076
|
this.abortController = new AbortController();
|
|
1862
3077
|
const signal = this.abortController.signal;
|
|
1863
3078
|
const model = options.model || DEFAULT_MODEL;
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
3079
|
+
if (!this.adapter.supportsModel(model)) {
|
|
3080
|
+
const familyConfig2 = getModelFamily(model);
|
|
3081
|
+
if (!familyConfig2) {
|
|
3082
|
+
yield createApiError(`\u672A\u77E5\u6A21\u578B: ${model}`, { code: "MODEL_NOT_SUPPORTED" });
|
|
3083
|
+
} else {
|
|
3084
|
+
yield createApiError(`\u6A21\u578B ${model} \u7F3A\u5C11 API Key \u914D\u7F6E`, { code: "MISSING_API_KEY" });
|
|
3085
|
+
}
|
|
1868
3086
|
return;
|
|
1869
3087
|
}
|
|
1870
|
-
const
|
|
3088
|
+
const familyConfig = getModelFamily(model);
|
|
3089
|
+
const rawThinkingMode = options.thinkingMode ?? this.getDefaultThinkingMode();
|
|
3090
|
+
const thinkingMode = rawThinkingMode === "enabled" ? "enabled" : "disabled";
|
|
1871
3091
|
const systemPrompt = this.buildSystemPrompt(options);
|
|
1872
|
-
const
|
|
3092
|
+
const isAskMode = options.mode === "ask";
|
|
3093
|
+
const enableSearch = !isAskMode && !!options.enableWebSearch;
|
|
3094
|
+
const forceInclude = enableSearch && familyConfig && !modelSupportsNativeSearch(model) ? ["web_search"] : void 0;
|
|
3095
|
+
const tools2 = isAskMode ? [] : this.getToolDefinitions(options.enabledTools, forceInclude);
|
|
3096
|
+
const enableThinking = !isAskMode && thinkingMode === "enabled" && (familyConfig?.supportsThinking ?? false);
|
|
1873
3097
|
const history = options.history || [];
|
|
1874
3098
|
const context = {
|
|
1875
3099
|
systemPrompt,
|
|
@@ -1879,10 +3103,10 @@ var HybridAgent = class {
|
|
|
1879
3103
|
images
|
|
1880
3104
|
};
|
|
1881
3105
|
try {
|
|
1882
|
-
yield* this.orchestrator.chat(adapter, message, context, {
|
|
3106
|
+
yield* this.orchestrator.chat(this.adapter, message, context, {
|
|
1883
3107
|
model,
|
|
1884
|
-
enableThinking
|
|
1885
|
-
enableSearch
|
|
3108
|
+
enableThinking,
|
|
3109
|
+
enableSearch,
|
|
1886
3110
|
autoRunConfig: options.autoRunConfig
|
|
1887
3111
|
});
|
|
1888
3112
|
} finally {
|
|
@@ -2561,7 +3785,9 @@ var AMAP_KEY = "16924e39cfd78c645f0035c1bc290961";
|
|
|
2561
3785
|
function getWeatherTool() {
|
|
2562
3786
|
return tool({
|
|
2563
3787
|
name: "get_weather",
|
|
2564
|
-
description:
|
|
3788
|
+
description: '\u83B7\u53D6\u6307\u5B9A\u57CE\u5E02\u7684\u5B9E\u65F6\u5929\u6C14\u4FE1\u606F\u3002\u652F\u6301\u5168\u56FD 337 \u4E2A\u5730\u7EA7\u5E02\uFF0C\u5305\u62EC\uFF1A\u5317\u4EAC\u3001\u4E0A\u6D77\u3001\u5E7F\u5DDE\u3001\u6DF1\u5733\u3001\u676D\u5DDE\u3001\u6210\u90FD\u3001\u6B66\u6C49\u3001\u897F\u5B89\u3001\u5357\u4EAC\u3001\u82CF\u5DDE\u3001\u91CD\u5E86\u3001\u5929\u6D25\u7B49\u3002\u8FD4\u56DE\u6E29\u5EA6\u3001\u5929\u6C14\u72B6\u51B5\u3001\u6E7F\u5EA6\u3001\u98CE\u5411\u98CE\u529B\u7B49\u4FE1\u606F\u3002\n\n\u6CE8\u610F\uFF1A\u5DE5\u5177\u7ED3\u679C\u4F1A\u4EE5\u53EF\u89C6\u5316\u5361\u7247\u5F62\u5F0F\u81EA\u52A8\u663E\u793A\u7ED9\u7528\u6237\u3002\n- \u8BF7\u4E0D\u8981\u5728\u56DE\u590D\u4E2D\u7528\u5217\u8868/\u8868\u683C\u91CD\u590D\u5C55\u793A\u5361\u7247\u91CC\u5DF2\u6709\u5B57\u6BB5\uFF08\u57CE\u5E02\u3001\u6E29\u5EA6\u3001\u5929\u6C14\u3001\u6E7F\u5EA6\u3001\u98CE\u529B\u3001\u65F6\u95F4\u7B49\uFF09\u3002\n- \u4F60\u53EA\u9700\u8981\u7ED9\u51FA\u4E00\u53E5\u5230\u4E09\u53E5"\u7ED3\u8BBA/\u5EFA\u8BAE"\uFF08\u5982\u7A7F\u8863\u3001\u9632\u6652\u3001\u51FA\u884C\u63D0\u9192\uFF09\u3002\n- \u53EA\u6709\u7528\u6237\u660E\u786E\u8FFD\u95EE\u5177\u4F53\u6570\u503C\u65F6\uFF0C\u518D\u8865\u5145\u6570\u636E\u3002',
|
|
3789
|
+
// 结果类型:前端会生成 { type: 'weather', ...result } 的 Part
|
|
3790
|
+
resultType: "weather",
|
|
2565
3791
|
parameters: {
|
|
2566
3792
|
type: "object",
|
|
2567
3793
|
properties: {
|
|
@@ -2841,6 +4067,6 @@ async function getDocumentSearchInstance(dataDir = "./.search-data", workspace)
|
|
|
2841
4067
|
return await mod.searchPlugin({ dataDir, workspace });
|
|
2842
4068
|
}
|
|
2843
4069
|
|
|
2844
|
-
export {
|
|
4070
|
+
export { AnthropicProtocol, ArkProtocol, CLAUDE_FAMILY, ChatOrchestrator, DEEPSEEK_FAMILY, DEFAULT_MODEL, DOUBAO_FAMILY, DebugLogger, DeepSeekProtocol, GEMINI_FAMILY, GPT_FAMILY, GeminiProtocol, HybridAgent, MODELS, MODEL_FAMILIES, MODEL_REGISTRY, OpenAIProtocol, QWEN_FAMILY, QwenProtocol, UnifiedAdapter, analyzeImageTool, analyzeVideoTool, createAbort, createAnthropicProtocol, createApiError, createArkProtocol, createDeepSeekProtocol, createDefaultToolExecutor, createDocumentSearchTools, createDone, createError, createGeminiProtocol, createImage, createOpenAIProtocol, createOrchestrator, createParseError, createQwenProtocol, createRateLimitError, createSearchEnd, createSearchResult, createSearchStart, createStepEnd, createStepStart, createStreamStart, createTextDelta, createThinkingDelta, createThinkingEnd, createThinkingStart, createTimeoutError, createToolCallResult, createToolCallStart, createToolError, createUnifiedAdapter, createVideo, executeCommandTool, generateImageTool, getCwdTool, getDefaultProvider, getDocumentSearchInstance, getModelByModelId, getModelEntry, getModelFamily, getModelProtocol, getModelSearchStrategy, getModelsByFamily, getModelsByProtocol, getPlatformTool, getVisibleModels, getWeatherTool, isAbortEvent, isErrorEvent, isMediaEvent, isModelForProvider, isRetryableError, isSearchEvent, isStatusEvent, isStepEvent, isTextEvent, isThinkingEvent, isToolEvent, modelSupportsNativeSearch, modelSupportsThinking, navigateToDirectoryTool, openConfirmDialogTool, openFilePreviewTool, resolveTools, routeModelToProvider, routeModelWithDetails, showToastTool, tool, tools, uiActionTools };
|
|
2845
4071
|
//# sourceMappingURL=index.js.map
|
|
2846
4072
|
//# sourceMappingURL=index.js.map
|