@n0ts123/mcplink-core 0.0.1
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 +424 -0
- package/dist/index.d.ts +456 -0
- package/dist/index.js +1410 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1410 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
4
|
+
import { generateText, streamText } from 'ai';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
// src/MCPManager.ts
|
|
8
|
+
var MCPManager = class {
|
|
9
|
+
servers = /* @__PURE__ */ new Map();
|
|
10
|
+
/**
|
|
11
|
+
* 添加 MCP 服务器配置
|
|
12
|
+
*/
|
|
13
|
+
addServer(id, config) {
|
|
14
|
+
if (this.servers.has(id)) {
|
|
15
|
+
throw new Error(`MCP server "${id}" already exists`);
|
|
16
|
+
}
|
|
17
|
+
const client = new Client({ name: "mcplink", version: "0.0.1" }, { capabilities: {} });
|
|
18
|
+
let transport;
|
|
19
|
+
if (config.type === "sse") {
|
|
20
|
+
const sseConfig = config;
|
|
21
|
+
transport = new SSEClientTransport(new URL(sseConfig.url));
|
|
22
|
+
} else {
|
|
23
|
+
const stdioConfig = config;
|
|
24
|
+
const processEnv = {};
|
|
25
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
26
|
+
if (value !== void 0) {
|
|
27
|
+
processEnv[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const mergedEnv = {
|
|
31
|
+
...processEnv,
|
|
32
|
+
...stdioConfig.env
|
|
33
|
+
};
|
|
34
|
+
const isWindows = process.platform === "win32";
|
|
35
|
+
let command = stdioConfig.command;
|
|
36
|
+
let args = stdioConfig.args || [];
|
|
37
|
+
if (isWindows) {
|
|
38
|
+
const windowsCommands = ["npx", "npm", "node", "pnpm", "yarn", "bunx"];
|
|
39
|
+
if (windowsCommands.includes(command.toLowerCase())) {
|
|
40
|
+
args = ["/c", command, ...args];
|
|
41
|
+
command = "cmd";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
transport = new StdioClientTransport({
|
|
45
|
+
command,
|
|
46
|
+
args,
|
|
47
|
+
env: mergedEnv
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
this.servers.set(id, {
|
|
51
|
+
id,
|
|
52
|
+
config,
|
|
53
|
+
client,
|
|
54
|
+
transport,
|
|
55
|
+
tools: [],
|
|
56
|
+
status: "stopped"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 启动 MCP 服务器
|
|
61
|
+
*/
|
|
62
|
+
async startServer(id) {
|
|
63
|
+
const server = this.servers.get(id);
|
|
64
|
+
if (!server) {
|
|
65
|
+
throw new Error(`MCP server "${id}" not found`);
|
|
66
|
+
}
|
|
67
|
+
if (server.status === "running") {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
server.status = "starting";
|
|
71
|
+
server.error = void 0;
|
|
72
|
+
const config = server.config;
|
|
73
|
+
if (config.type === "stdio") {
|
|
74
|
+
const stdioConfig = config;
|
|
75
|
+
const isWindows = process.platform === "win32";
|
|
76
|
+
const windowsCommands = ["npx", "npm", "node", "pnpm", "yarn", "bunx"];
|
|
77
|
+
let displayCmd = stdioConfig.command;
|
|
78
|
+
let displayArgs = stdioConfig.args || [];
|
|
79
|
+
if (isWindows && windowsCommands.includes(stdioConfig.command.toLowerCase())) {
|
|
80
|
+
displayCmd = "cmd";
|
|
81
|
+
displayArgs = ["/c", stdioConfig.command, ...displayArgs];
|
|
82
|
+
}
|
|
83
|
+
console.log(`
|
|
84
|
+
\u{1F527} [MCP] \u6B63\u5728\u542F\u52A8\u670D\u52A1\u5668 "${id}"...`);
|
|
85
|
+
console.log(` \u547D\u4EE4: ${displayCmd} ${displayArgs.join(" ")}`);
|
|
86
|
+
if (stdioConfig.env && Object.keys(stdioConfig.env).length > 0) {
|
|
87
|
+
console.log(` \u73AF\u5883\u53D8\u91CF: ${Object.keys(stdioConfig.env).join(", ")}`);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
const sseConfig = config;
|
|
91
|
+
console.log(`
|
|
92
|
+
\u{1F527} [MCP] \u6B63\u5728\u8FDE\u63A5 SSE \u670D\u52A1\u5668 "${id}"...`);
|
|
93
|
+
console.log(` URL: ${sseConfig.url}`);
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
await server.client.connect(server.transport);
|
|
97
|
+
const toolsResult = await server.client.listTools();
|
|
98
|
+
server.tools = toolsResult.tools.map((tool) => ({
|
|
99
|
+
name: tool.name,
|
|
100
|
+
description: tool.description || "",
|
|
101
|
+
inputSchema: tool.inputSchema
|
|
102
|
+
}));
|
|
103
|
+
server.status = "running";
|
|
104
|
+
console.log(`\u2705 [MCP] \u670D\u52A1\u5668 "${id}" \u542F\u52A8\u6210\u529F\uFF0C\u53D1\u73B0 ${server.tools.length} \u4E2A\u5DE5\u5177`);
|
|
105
|
+
if (server.tools.length > 0) {
|
|
106
|
+
console.log(` \u5DE5\u5177: ${server.tools.map((t) => t.name).join(", ")}`);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
server.status = "error";
|
|
110
|
+
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
111
|
+
if (errorMessage.includes("Connection closed")) {
|
|
112
|
+
if (config.type === "stdio") {
|
|
113
|
+
const stdioConfig = config;
|
|
114
|
+
errorMessage = `MCP \u670D\u52A1\u5668\u542F\u52A8\u5931\u8D25: \u8FDB\u7A0B\u7ACB\u5373\u9000\u51FA\u3002
|
|
115
|
+
\u547D\u4EE4: ${stdioConfig.command} ${(stdioConfig.args || []).join(" ")}
|
|
116
|
+
\u53EF\u80FD\u539F\u56E0:
|
|
117
|
+
1. \u547D\u4EE4 "${stdioConfig.command}" \u4E0D\u5B58\u5728\u6216\u4E0D\u5728 PATH \u4E2D
|
|
118
|
+
2. \u5982\u679C\u4F7F\u7528 Docker\uFF0C\u8BF7\u786E\u4FDD Docker \u6B63\u5728\u8FD0\u884C
|
|
119
|
+
3. \u68C0\u67E5\u73AF\u5883\u53D8\u91CF\u662F\u5426\u6B63\u786E\u914D\u7F6E
|
|
120
|
+
4. \u5C1D\u8BD5\u5728\u7EC8\u7AEF\u624B\u52A8\u8FD0\u884C\u547D\u4EE4\u67E5\u770B\u5177\u4F53\u9519\u8BEF`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
console.error(`\u274C [MCP] \u670D\u52A1\u5668 "${id}" \u542F\u52A8\u5931\u8D25:`);
|
|
124
|
+
console.error(` ${errorMessage.split("\n").join("\n ")}`);
|
|
125
|
+
server.error = errorMessage;
|
|
126
|
+
throw new Error(errorMessage);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 停止 MCP 服务器
|
|
131
|
+
*/
|
|
132
|
+
async stopServer(id) {
|
|
133
|
+
const server = this.servers.get(id);
|
|
134
|
+
if (!server) {
|
|
135
|
+
throw new Error(`MCP server "${id}" not found`);
|
|
136
|
+
}
|
|
137
|
+
if (server.status === "stopped") {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
console.log(`\u{1F527} [MCP] \u6B63\u5728\u505C\u6B62\u670D\u52A1\u5668 "${id}"...`);
|
|
141
|
+
try {
|
|
142
|
+
await server.client.close();
|
|
143
|
+
console.log(`\u2705 [MCP] \u670D\u52A1\u5668 "${id}" \u5DF2\u505C\u6B62`);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(`\u26A0\uFE0F [MCP] \u505C\u6B62\u670D\u52A1\u5668 "${id}" \u65F6\u51FA\u9519:`, error);
|
|
146
|
+
} finally {
|
|
147
|
+
server.status = "stopped";
|
|
148
|
+
server.tools = [];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 启动所有已配置的服务器
|
|
153
|
+
*/
|
|
154
|
+
async startAll() {
|
|
155
|
+
const startPromises = Array.from(this.servers.keys()).map(
|
|
156
|
+
(id) => this.startServer(id).catch((error) => {
|
|
157
|
+
console.error(`Failed to start MCP server "${id}":`, error);
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
await Promise.all(startPromises);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 停止所有服务器
|
|
164
|
+
*/
|
|
165
|
+
async stopAll() {
|
|
166
|
+
const stopPromises = Array.from(this.servers.keys()).map(
|
|
167
|
+
(id) => this.stopServer(id).catch((error) => {
|
|
168
|
+
console.error(`Failed to stop MCP server "${id}":`, error);
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
await Promise.all(stopPromises);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 获取所有可用的工具
|
|
175
|
+
*/
|
|
176
|
+
getAllTools() {
|
|
177
|
+
const tools = [];
|
|
178
|
+
for (const server of this.servers.values()) {
|
|
179
|
+
if (server.status === "running") {
|
|
180
|
+
tools.push(...server.tools);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return tools;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 调用工具
|
|
187
|
+
*/
|
|
188
|
+
async callTool(toolName, args) {
|
|
189
|
+
for (const server of this.servers.values()) {
|
|
190
|
+
if (server.status !== "running") continue;
|
|
191
|
+
const tool = server.tools.find((t) => t.name === toolName);
|
|
192
|
+
if (tool) {
|
|
193
|
+
const result = await server.client.callTool({
|
|
194
|
+
name: toolName,
|
|
195
|
+
arguments: args
|
|
196
|
+
});
|
|
197
|
+
if (result.content && Array.isArray(result.content)) {
|
|
198
|
+
const textContents = result.content.filter((c) => c.type === "text").map((c) => c.text);
|
|
199
|
+
if (textContents.length > 0) {
|
|
200
|
+
const textResult = textContents.join("\n");
|
|
201
|
+
if (result.isError) {
|
|
202
|
+
throw new Error(textResult || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
|
|
203
|
+
}
|
|
204
|
+
return textResult;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (result.isError) {
|
|
208
|
+
const errorContent = result.content;
|
|
209
|
+
throw new Error(
|
|
210
|
+
typeof errorContent === "string" ? errorContent : JSON.stringify(errorContent) || "\u5DE5\u5177\u6267\u884C\u5931\u8D25"
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return result.content;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
throw new Error(`Tool "${toolName}" not found in any running MCP server`);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* 获取所有服务器状态
|
|
220
|
+
*/
|
|
221
|
+
getServerStatuses() {
|
|
222
|
+
return Array.from(this.servers.values()).map((server) => ({
|
|
223
|
+
id: server.id,
|
|
224
|
+
name: server.id,
|
|
225
|
+
config: server.config,
|
|
226
|
+
status: server.status,
|
|
227
|
+
tools: server.tools,
|
|
228
|
+
error: server.error
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* 移除服务器
|
|
233
|
+
*/
|
|
234
|
+
async removeServer(id) {
|
|
235
|
+
await this.stopServer(id);
|
|
236
|
+
this.servers.delete(id);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// src/types.ts
|
|
241
|
+
var MCPLinkEventType = /* @__PURE__ */ ((MCPLinkEventType2) => {
|
|
242
|
+
MCPLinkEventType2["THINKING_START"] = "thinking_start";
|
|
243
|
+
MCPLinkEventType2["THINKING_DELTA"] = "thinking_delta";
|
|
244
|
+
MCPLinkEventType2["THINKING_END"] = "thinking_end";
|
|
245
|
+
MCPLinkEventType2["THINKING_CONTENT"] = "thinking_content";
|
|
246
|
+
MCPLinkEventType2["TEXT_START"] = "text_start";
|
|
247
|
+
MCPLinkEventType2["TEXT_DELTA"] = "text_delta";
|
|
248
|
+
MCPLinkEventType2["TEXT_END"] = "text_end";
|
|
249
|
+
MCPLinkEventType2["TOOL_CALL_START"] = "tool_call_start";
|
|
250
|
+
MCPLinkEventType2["TOOL_CALL_DELTA"] = "tool_call_delta";
|
|
251
|
+
MCPLinkEventType2["TOOL_CALL_END"] = "tool_call_end";
|
|
252
|
+
MCPLinkEventType2["TOOL_EXECUTING"] = "tool_executing";
|
|
253
|
+
MCPLinkEventType2["TOOL_RESULT"] = "tool_result";
|
|
254
|
+
MCPLinkEventType2["ITERATION_START"] = "iteration_start";
|
|
255
|
+
MCPLinkEventType2["ITERATION_END"] = "iteration_end";
|
|
256
|
+
MCPLinkEventType2["COMPLETE"] = "complete";
|
|
257
|
+
MCPLinkEventType2["ERROR"] = "error";
|
|
258
|
+
MCPLinkEventType2["TODO_START"] = "todo_start";
|
|
259
|
+
MCPLinkEventType2["TODO_ITEM_ADD"] = "todo_item_add";
|
|
260
|
+
MCPLinkEventType2["TODO_ITEM_UPDATE"] = "todo_item_update";
|
|
261
|
+
MCPLinkEventType2["TODO_END"] = "todo_end";
|
|
262
|
+
return MCPLinkEventType2;
|
|
263
|
+
})(MCPLinkEventType || {});
|
|
264
|
+
|
|
265
|
+
// src/Agent.ts
|
|
266
|
+
var DEFAULT_SYSTEM_PROMPT = `\u4F60\u662F\u4E00\u4E2A\u667A\u80FD\u52A9\u624B\uFF0C\u8BF7\u4F7F\u7528\u4E2D\u6587\u56DE\u590D\u3002`;
|
|
267
|
+
var Agent = class {
|
|
268
|
+
model;
|
|
269
|
+
mcpManager;
|
|
270
|
+
systemPrompt;
|
|
271
|
+
maxIterations;
|
|
272
|
+
constructor(model, mcpManager, options = {}) {
|
|
273
|
+
this.model = model;
|
|
274
|
+
this.mcpManager = mcpManager;
|
|
275
|
+
this.systemPrompt = options.systemPrompt || DEFAULT_SYSTEM_PROMPT;
|
|
276
|
+
this.maxIterations = options.maxIterations || 10;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* 将 MCP 工具转换为 Vercel AI SDK 格式
|
|
280
|
+
*/
|
|
281
|
+
convertMCPToolsToAITools(mcpTools) {
|
|
282
|
+
const tools = {};
|
|
283
|
+
for (const mcpTool of mcpTools) {
|
|
284
|
+
const zodSchema = this.jsonSchemaToZod(mcpTool.inputSchema);
|
|
285
|
+
tools[mcpTool.name] = {
|
|
286
|
+
description: mcpTool.description,
|
|
287
|
+
parameters: zodSchema
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
return tools;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* 简单的 JSON Schema 到 Zod 转换
|
|
294
|
+
*/
|
|
295
|
+
jsonSchemaToZod(schema) {
|
|
296
|
+
if (!schema.properties) {
|
|
297
|
+
return z.object({});
|
|
298
|
+
}
|
|
299
|
+
const shape = {};
|
|
300
|
+
const required = schema.required || [];
|
|
301
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
302
|
+
const propSchema = prop;
|
|
303
|
+
let zodType;
|
|
304
|
+
switch (propSchema.type) {
|
|
305
|
+
case "string":
|
|
306
|
+
zodType = z.string();
|
|
307
|
+
break;
|
|
308
|
+
case "number":
|
|
309
|
+
zodType = z.number();
|
|
310
|
+
break;
|
|
311
|
+
case "integer":
|
|
312
|
+
zodType = z.number().int();
|
|
313
|
+
break;
|
|
314
|
+
case "boolean":
|
|
315
|
+
zodType = z.boolean();
|
|
316
|
+
break;
|
|
317
|
+
case "array":
|
|
318
|
+
if (propSchema.items?.type === "string") {
|
|
319
|
+
zodType = z.array(z.string());
|
|
320
|
+
} else if (propSchema.items?.type === "number") {
|
|
321
|
+
zodType = z.array(z.number());
|
|
322
|
+
} else {
|
|
323
|
+
zodType = z.array(z.unknown());
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
326
|
+
default:
|
|
327
|
+
zodType = z.unknown();
|
|
328
|
+
}
|
|
329
|
+
if (propSchema.description) {
|
|
330
|
+
zodType = zodType.describe(propSchema.description);
|
|
331
|
+
}
|
|
332
|
+
if (!required.includes(key)) {
|
|
333
|
+
zodType = zodType.optional();
|
|
334
|
+
}
|
|
335
|
+
shape[key] = zodType;
|
|
336
|
+
}
|
|
337
|
+
return z.object(shape);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* 执行对话
|
|
341
|
+
*/
|
|
342
|
+
async chat(userMessage, callbacks) {
|
|
343
|
+
const startTime = Date.now();
|
|
344
|
+
const toolCallRecords = [];
|
|
345
|
+
let totalPromptTokens = 0;
|
|
346
|
+
let totalCompletionTokens = 0;
|
|
347
|
+
const messages = [
|
|
348
|
+
{ role: "system", content: this.systemPrompt },
|
|
349
|
+
{ role: "user", content: userMessage }
|
|
350
|
+
];
|
|
351
|
+
const mcpTools = this.mcpManager.getAllTools();
|
|
352
|
+
const tools = this.convertMCPToolsToAITools(mcpTools);
|
|
353
|
+
let iteration = 0;
|
|
354
|
+
let finalContent = "";
|
|
355
|
+
while (iteration < this.maxIterations) {
|
|
356
|
+
iteration++;
|
|
357
|
+
callbacks?.onIterationStart?.(iteration);
|
|
358
|
+
const response = await generateText({
|
|
359
|
+
model: this.model,
|
|
360
|
+
messages,
|
|
361
|
+
tools: Object.keys(tools).length > 0 ? tools : void 0,
|
|
362
|
+
maxSteps: 1
|
|
363
|
+
// 每次只执行一步,方便我们控制流程
|
|
364
|
+
});
|
|
365
|
+
if (response.usage) {
|
|
366
|
+
totalPromptTokens += response.usage.promptTokens;
|
|
367
|
+
totalCompletionTokens += response.usage.completionTokens;
|
|
368
|
+
}
|
|
369
|
+
const toolCalls = response.toolCalls || [];
|
|
370
|
+
if (toolCalls.length === 0) {
|
|
371
|
+
finalContent = response.text || "";
|
|
372
|
+
callbacks?.onTextDelta?.(finalContent);
|
|
373
|
+
callbacks?.onIterationEnd?.(iteration);
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
const toolResults = [];
|
|
377
|
+
for (const toolCall of toolCalls) {
|
|
378
|
+
const toolName = toolCall.toolName;
|
|
379
|
+
const toolArgs = toolCall.args;
|
|
380
|
+
const toolCallId = toolCall.toolCallId;
|
|
381
|
+
callbacks?.onToolCallStart?.(toolName, toolArgs);
|
|
382
|
+
const toolStartTime = Date.now();
|
|
383
|
+
let result;
|
|
384
|
+
let isError = false;
|
|
385
|
+
try {
|
|
386
|
+
result = await this.mcpManager.callTool(toolName, toolArgs);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
result = error instanceof Error ? error.message : String(error);
|
|
389
|
+
isError = true;
|
|
390
|
+
}
|
|
391
|
+
const duration2 = Date.now() - toolStartTime;
|
|
392
|
+
callbacks?.onToolResult?.(toolName, result, duration2);
|
|
393
|
+
toolResults.push({
|
|
394
|
+
toolCallId,
|
|
395
|
+
toolName,
|
|
396
|
+
result,
|
|
397
|
+
isError,
|
|
398
|
+
duration: duration2
|
|
399
|
+
});
|
|
400
|
+
toolCallRecords.push({
|
|
401
|
+
name: toolName,
|
|
402
|
+
arguments: toolArgs,
|
|
403
|
+
result,
|
|
404
|
+
duration: duration2
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
messages.push({
|
|
408
|
+
role: "assistant",
|
|
409
|
+
content: [
|
|
410
|
+
{ type: "text", text: response.text || "" },
|
|
411
|
+
...toolCalls.map((tc) => ({
|
|
412
|
+
type: "tool-call",
|
|
413
|
+
toolCallId: tc.toolCallId,
|
|
414
|
+
toolName: tc.toolName,
|
|
415
|
+
args: tc.args
|
|
416
|
+
}))
|
|
417
|
+
]
|
|
418
|
+
});
|
|
419
|
+
for (const tr of toolResults) {
|
|
420
|
+
messages.push({
|
|
421
|
+
role: "tool",
|
|
422
|
+
content: [
|
|
423
|
+
{
|
|
424
|
+
type: "tool-result",
|
|
425
|
+
toolCallId: tr.toolCallId,
|
|
426
|
+
toolName: tr.toolName,
|
|
427
|
+
result: tr.result
|
|
428
|
+
}
|
|
429
|
+
]
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
callbacks?.onIterationEnd?.(iteration);
|
|
433
|
+
}
|
|
434
|
+
const duration = Date.now() - startTime;
|
|
435
|
+
return {
|
|
436
|
+
content: finalContent,
|
|
437
|
+
toolCalls: toolCallRecords,
|
|
438
|
+
messages: messages.map((m) => ({
|
|
439
|
+
role: m.role,
|
|
440
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
441
|
+
})),
|
|
442
|
+
usage: {
|
|
443
|
+
promptTokens: totalPromptTokens,
|
|
444
|
+
completionTokens: totalCompletionTokens,
|
|
445
|
+
totalTokens: totalPromptTokens + totalCompletionTokens
|
|
446
|
+
},
|
|
447
|
+
iterations: iteration,
|
|
448
|
+
duration
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* 流式对话 - 返回事件生成器
|
|
453
|
+
* @param userMessage 用户消息
|
|
454
|
+
* @param options 可选参数
|
|
455
|
+
* @param options.allowedTools 允许使用的工具名称列表,为空或不传则使用所有工具
|
|
456
|
+
* @param options.history 历史消息列表
|
|
457
|
+
*/
|
|
458
|
+
async *chatStream(userMessage, options) {
|
|
459
|
+
const startTime = Date.now();
|
|
460
|
+
const messages = [{ role: "system", content: this.systemPrompt }];
|
|
461
|
+
if (options?.history && options.history.length > 0) {
|
|
462
|
+
for (const msg of options.history) {
|
|
463
|
+
messages.push({
|
|
464
|
+
role: msg.role,
|
|
465
|
+
content: msg.content
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
messages.push({ role: "user", content: userMessage });
|
|
470
|
+
let mcpTools = this.mcpManager.getAllTools();
|
|
471
|
+
if (options?.allowedTools && options.allowedTools.length > 0) {
|
|
472
|
+
mcpTools = mcpTools.filter((tool) => options.allowedTools.includes(tool.name));
|
|
473
|
+
}
|
|
474
|
+
const tools = this.convertMCPToolsToAITools(mcpTools);
|
|
475
|
+
const hasTools = Object.keys(tools).length > 0;
|
|
476
|
+
let iteration = 0;
|
|
477
|
+
while (iteration < this.maxIterations) {
|
|
478
|
+
iteration++;
|
|
479
|
+
yield {
|
|
480
|
+
type: "iteration_start" /* ITERATION_START */,
|
|
481
|
+
timestamp: Date.now(),
|
|
482
|
+
data: { iteration, maxIterations: this.maxIterations }
|
|
483
|
+
};
|
|
484
|
+
const stream = streamText({
|
|
485
|
+
model: this.model,
|
|
486
|
+
messages,
|
|
487
|
+
tools: hasTools ? tools : void 0,
|
|
488
|
+
maxSteps: 1
|
|
489
|
+
});
|
|
490
|
+
let fullText = "";
|
|
491
|
+
let reasoningText = "";
|
|
492
|
+
const toolCalls = [];
|
|
493
|
+
let currentToolCall = null;
|
|
494
|
+
let hasStartedText = false;
|
|
495
|
+
let hasStartedReasoning = false;
|
|
496
|
+
const sentToolCallStarts = /* @__PURE__ */ new Set();
|
|
497
|
+
let isInsideThinkTag = false;
|
|
498
|
+
let textBuffer = "";
|
|
499
|
+
for await (const chunk of stream.fullStream) {
|
|
500
|
+
switch (chunk.type) {
|
|
501
|
+
case "reasoning":
|
|
502
|
+
if (!hasStartedReasoning) {
|
|
503
|
+
hasStartedReasoning = true;
|
|
504
|
+
yield {
|
|
505
|
+
type: "thinking_start" /* THINKING_START */,
|
|
506
|
+
timestamp: Date.now(),
|
|
507
|
+
data: {}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
reasoningText += chunk.textDelta;
|
|
511
|
+
yield {
|
|
512
|
+
type: "thinking_delta" /* THINKING_DELTA */,
|
|
513
|
+
timestamp: Date.now(),
|
|
514
|
+
data: { content: chunk.textDelta }
|
|
515
|
+
};
|
|
516
|
+
break;
|
|
517
|
+
case "text-delta":
|
|
518
|
+
const delta = chunk.textDelta;
|
|
519
|
+
textBuffer += delta;
|
|
520
|
+
if (!isInsideThinkTag) {
|
|
521
|
+
const thinkStartMatch = textBuffer.match(/<think>/i);
|
|
522
|
+
if (thinkStartMatch) {
|
|
523
|
+
const beforeThink = textBuffer.substring(0, thinkStartMatch.index);
|
|
524
|
+
if (beforeThink.trim()) {
|
|
525
|
+
if (!hasStartedText) {
|
|
526
|
+
hasStartedText = true;
|
|
527
|
+
yield {
|
|
528
|
+
type: "text_start" /* TEXT_START */,
|
|
529
|
+
timestamp: Date.now(),
|
|
530
|
+
data: {}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
fullText += beforeThink;
|
|
534
|
+
yield {
|
|
535
|
+
type: "text_delta" /* TEXT_DELTA */,
|
|
536
|
+
timestamp: Date.now(),
|
|
537
|
+
data: { content: beforeThink }
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
isInsideThinkTag = true;
|
|
541
|
+
if (!hasStartedReasoning) {
|
|
542
|
+
hasStartedReasoning = true;
|
|
543
|
+
yield {
|
|
544
|
+
type: "thinking_start" /* THINKING_START */,
|
|
545
|
+
timestamp: Date.now(),
|
|
546
|
+
data: {}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
textBuffer = textBuffer.substring(thinkStartMatch.index + 7);
|
|
550
|
+
} else if (!textBuffer.includes("<")) {
|
|
551
|
+
if (hasStartedReasoning && !hasStartedText) {
|
|
552
|
+
yield {
|
|
553
|
+
type: "thinking_end" /* THINKING_END */,
|
|
554
|
+
timestamp: Date.now(),
|
|
555
|
+
data: {}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
if (!hasStartedText) {
|
|
559
|
+
hasStartedText = true;
|
|
560
|
+
yield {
|
|
561
|
+
type: "text_start" /* TEXT_START */,
|
|
562
|
+
timestamp: Date.now(),
|
|
563
|
+
data: {}
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
fullText += textBuffer;
|
|
567
|
+
yield {
|
|
568
|
+
type: "text_delta" /* TEXT_DELTA */,
|
|
569
|
+
timestamp: Date.now(),
|
|
570
|
+
data: { content: textBuffer }
|
|
571
|
+
};
|
|
572
|
+
textBuffer = "";
|
|
573
|
+
}
|
|
574
|
+
} else {
|
|
575
|
+
const thinkEndMatch = textBuffer.match(/<\/think>/i);
|
|
576
|
+
if (thinkEndMatch) {
|
|
577
|
+
const thinkContent = textBuffer.substring(0, thinkEndMatch.index);
|
|
578
|
+
if (thinkContent) {
|
|
579
|
+
reasoningText += thinkContent;
|
|
580
|
+
yield {
|
|
581
|
+
type: "thinking_delta" /* THINKING_DELTA */,
|
|
582
|
+
timestamp: Date.now(),
|
|
583
|
+
data: { content: thinkContent }
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
yield {
|
|
587
|
+
type: "thinking_end" /* THINKING_END */,
|
|
588
|
+
timestamp: Date.now(),
|
|
589
|
+
data: {}
|
|
590
|
+
};
|
|
591
|
+
isInsideThinkTag = false;
|
|
592
|
+
textBuffer = textBuffer.substring(thinkEndMatch.index + 8);
|
|
593
|
+
} else if (!textBuffer.includes("<")) {
|
|
594
|
+
reasoningText += textBuffer;
|
|
595
|
+
yield {
|
|
596
|
+
type: "thinking_delta" /* THINKING_DELTA */,
|
|
597
|
+
timestamp: Date.now(),
|
|
598
|
+
data: { content: textBuffer }
|
|
599
|
+
};
|
|
600
|
+
textBuffer = "";
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
break;
|
|
604
|
+
case "tool-call":
|
|
605
|
+
if (!sentToolCallStarts.has(chunk.toolCallId)) {
|
|
606
|
+
yield {
|
|
607
|
+
type: "tool_call_start" /* TOOL_CALL_START */,
|
|
608
|
+
timestamp: Date.now(),
|
|
609
|
+
data: {
|
|
610
|
+
toolName: chunk.toolName,
|
|
611
|
+
toolCallId: chunk.toolCallId,
|
|
612
|
+
toolArgs: chunk.args
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
sentToolCallStarts.add(chunk.toolCallId);
|
|
616
|
+
}
|
|
617
|
+
toolCalls.push({
|
|
618
|
+
toolCallId: chunk.toolCallId,
|
|
619
|
+
toolName: chunk.toolName,
|
|
620
|
+
args: chunk.args
|
|
621
|
+
});
|
|
622
|
+
break;
|
|
623
|
+
case "tool-call-streaming-start":
|
|
624
|
+
currentToolCall = {
|
|
625
|
+
toolCallId: chunk.toolCallId,
|
|
626
|
+
toolName: chunk.toolName,
|
|
627
|
+
argsText: ""
|
|
628
|
+
};
|
|
629
|
+
if (!sentToolCallStarts.has(chunk.toolCallId)) {
|
|
630
|
+
yield {
|
|
631
|
+
type: "tool_call_start" /* TOOL_CALL_START */,
|
|
632
|
+
timestamp: Date.now(),
|
|
633
|
+
data: {
|
|
634
|
+
toolName: chunk.toolName,
|
|
635
|
+
toolCallId: chunk.toolCallId
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
sentToolCallStarts.add(chunk.toolCallId);
|
|
639
|
+
}
|
|
640
|
+
break;
|
|
641
|
+
case "tool-call-delta":
|
|
642
|
+
if (currentToolCall) {
|
|
643
|
+
currentToolCall.argsText += chunk.argsTextDelta;
|
|
644
|
+
yield {
|
|
645
|
+
type: "tool_call_delta" /* TOOL_CALL_DELTA */,
|
|
646
|
+
timestamp: Date.now(),
|
|
647
|
+
data: {
|
|
648
|
+
toolCallId: currentToolCall.toolCallId,
|
|
649
|
+
argsTextDelta: chunk.argsTextDelta
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
break;
|
|
654
|
+
case "finish":
|
|
655
|
+
if (textBuffer) {
|
|
656
|
+
if (isInsideThinkTag) {
|
|
657
|
+
reasoningText += textBuffer;
|
|
658
|
+
yield {
|
|
659
|
+
type: "thinking_delta" /* THINKING_DELTA */,
|
|
660
|
+
timestamp: Date.now(),
|
|
661
|
+
data: { content: textBuffer }
|
|
662
|
+
};
|
|
663
|
+
} else {
|
|
664
|
+
if (!hasStartedText) {
|
|
665
|
+
hasStartedText = true;
|
|
666
|
+
yield {
|
|
667
|
+
type: "text_start" /* TEXT_START */,
|
|
668
|
+
timestamp: Date.now(),
|
|
669
|
+
data: {}
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
fullText += textBuffer;
|
|
673
|
+
yield {
|
|
674
|
+
type: "text_delta" /* TEXT_DELTA */,
|
|
675
|
+
timestamp: Date.now(),
|
|
676
|
+
data: { content: textBuffer }
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
textBuffer = "";
|
|
680
|
+
}
|
|
681
|
+
if (isInsideThinkTag || hasStartedReasoning && !hasStartedText) {
|
|
682
|
+
yield {
|
|
683
|
+
type: "thinking_end" /* THINKING_END */,
|
|
684
|
+
timestamp: Date.now(),
|
|
685
|
+
data: {}
|
|
686
|
+
};
|
|
687
|
+
isInsideThinkTag = false;
|
|
688
|
+
}
|
|
689
|
+
if (hasStartedText) {
|
|
690
|
+
yield {
|
|
691
|
+
type: "text_end" /* TEXT_END */,
|
|
692
|
+
timestamp: Date.now(),
|
|
693
|
+
data: {}
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
break;
|
|
697
|
+
case "error":
|
|
698
|
+
yield {
|
|
699
|
+
type: "error" /* ERROR */,
|
|
700
|
+
timestamp: Date.now(),
|
|
701
|
+
data: { error: chunk.error }
|
|
702
|
+
};
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (toolCalls.length === 0) {
|
|
707
|
+
yield {
|
|
708
|
+
type: "iteration_end" /* ITERATION_END */,
|
|
709
|
+
timestamp: Date.now(),
|
|
710
|
+
data: { iteration }
|
|
711
|
+
};
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
if (fullText) {
|
|
715
|
+
yield {
|
|
716
|
+
type: "thinking_content" /* THINKING_CONTENT */,
|
|
717
|
+
timestamp: Date.now(),
|
|
718
|
+
data: { content: fullText }
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
const toolResults = [];
|
|
722
|
+
for (const toolCall of toolCalls) {
|
|
723
|
+
const toolName = toolCall.toolName;
|
|
724
|
+
const toolArgs = toolCall.args;
|
|
725
|
+
const toolCallId = toolCall.toolCallId;
|
|
726
|
+
yield {
|
|
727
|
+
type: "tool_executing" /* TOOL_EXECUTING */,
|
|
728
|
+
timestamp: Date.now(),
|
|
729
|
+
data: { toolName, toolCallId, toolArgs }
|
|
730
|
+
};
|
|
731
|
+
const toolStartTime = Date.now();
|
|
732
|
+
let result;
|
|
733
|
+
let isError = false;
|
|
734
|
+
try {
|
|
735
|
+
result = await this.mcpManager.callTool(toolName, toolArgs);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
result = error instanceof Error ? error.message : String(error);
|
|
738
|
+
isError = true;
|
|
739
|
+
}
|
|
740
|
+
const duration = Date.now() - toolStartTime;
|
|
741
|
+
yield {
|
|
742
|
+
type: "tool_result" /* TOOL_RESULT */,
|
|
743
|
+
timestamp: Date.now(),
|
|
744
|
+
data: {
|
|
745
|
+
toolName,
|
|
746
|
+
toolResult: result,
|
|
747
|
+
toolCallId,
|
|
748
|
+
duration,
|
|
749
|
+
isError
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
toolResults.push({
|
|
753
|
+
toolCallId,
|
|
754
|
+
toolName,
|
|
755
|
+
result,
|
|
756
|
+
isError,
|
|
757
|
+
duration
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
messages.push({
|
|
761
|
+
role: "assistant",
|
|
762
|
+
content: [
|
|
763
|
+
...fullText ? [{ type: "text", text: fullText }] : [],
|
|
764
|
+
...toolCalls.map((tc) => ({
|
|
765
|
+
type: "tool-call",
|
|
766
|
+
toolCallId: tc.toolCallId,
|
|
767
|
+
toolName: tc.toolName,
|
|
768
|
+
args: tc.args
|
|
769
|
+
}))
|
|
770
|
+
]
|
|
771
|
+
});
|
|
772
|
+
for (const tr of toolResults) {
|
|
773
|
+
messages.push({
|
|
774
|
+
role: "tool",
|
|
775
|
+
content: [
|
|
776
|
+
{
|
|
777
|
+
type: "tool-result",
|
|
778
|
+
toolCallId: tr.toolCallId,
|
|
779
|
+
toolName: tr.toolName,
|
|
780
|
+
result: tr.result
|
|
781
|
+
}
|
|
782
|
+
]
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
yield {
|
|
786
|
+
type: "iteration_end" /* ITERATION_END */,
|
|
787
|
+
timestamp: Date.now(),
|
|
788
|
+
data: { iteration }
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
const totalDuration = Date.now() - startTime;
|
|
792
|
+
yield {
|
|
793
|
+
type: "complete" /* COMPLETE */,
|
|
794
|
+
timestamp: Date.now(),
|
|
795
|
+
data: {
|
|
796
|
+
totalIterations: iteration,
|
|
797
|
+
totalDuration
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
var PromptBasedAgent = class {
|
|
803
|
+
model;
|
|
804
|
+
mcpManager;
|
|
805
|
+
systemPrompt;
|
|
806
|
+
maxIterations;
|
|
807
|
+
constructor(model, mcpManager, options = {}) {
|
|
808
|
+
this.model = model;
|
|
809
|
+
this.mcpManager = mcpManager;
|
|
810
|
+
this.systemPrompt = options.systemPrompt || "";
|
|
811
|
+
this.maxIterations = options.maxIterations || 10;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* 生成工具列表描述
|
|
815
|
+
*/
|
|
816
|
+
generateToolsDescription(tools) {
|
|
817
|
+
if (tools.length === 0) {
|
|
818
|
+
return "\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684\u5DE5\u5177\u3002";
|
|
819
|
+
}
|
|
820
|
+
let description = "";
|
|
821
|
+
for (const tool of tools) {
|
|
822
|
+
description += `### ${tool.name}
|
|
823
|
+
`;
|
|
824
|
+
description += `\u63CF\u8FF0: ${tool.description}
|
|
825
|
+
`;
|
|
826
|
+
description += `\u53C2\u6570: ${JSON.stringify(tool.inputSchema, null, 2)}
|
|
827
|
+
|
|
828
|
+
`;
|
|
829
|
+
}
|
|
830
|
+
return description;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* 内置系统提示词 - 强调格式约束
|
|
834
|
+
*/
|
|
835
|
+
BUILT_IN_PROMPT = `
|
|
836
|
+
## \u5DE5\u5177\u8C03\u7528\u683C\u5F0F\uFF08\u5FC5\u987B\u4E25\u683C\u9075\u5B88\uFF09
|
|
837
|
+
|
|
838
|
+
\u5F53\u4F60\u9700\u8981\u83B7\u53D6\u6570\u636E\u6216\u6267\u884C\u64CD\u4F5C\u65F6\uFF0C**\u53EA\u80FD**\u4F7F\u7528\u4EE5\u4E0B\u683C\u5F0F\uFF1A
|
|
839
|
+
|
|
840
|
+
<tool_call>
|
|
841
|
+
{"name": "\u5DE5\u5177\u540D\u79F0", "arguments": {"\u53C2\u6570\u540D": "\u503C"}}
|
|
842
|
+
</tool_call>
|
|
843
|
+
|
|
844
|
+
### \u5DE5\u4F5C\u6D41\u7A0B
|
|
845
|
+
1. \u5206\u6790\u7528\u6237\u9700\u6C42
|
|
846
|
+
2. \u5982\u9700\u6570\u636E\uFF0C\u8F93\u51FA <tool_call>...</tool_call> \u540E**\u7ACB\u5373\u505C\u6B62**
|
|
847
|
+
3. \u7CFB\u7EDF\u4F1A\u6267\u884C\u5DE5\u5177\u5E76\u8FD4\u56DE\u771F\u5B9E\u7ED3\u679C
|
|
848
|
+
4. \u6536\u5230\u7ED3\u679C\u540E\uFF0C\u7528\u4E2D\u6587\u6574\u7406\u56DE\u590D\u7528\u6237
|
|
849
|
+
|
|
850
|
+
### \u4E25\u683C\u7981\u6B62
|
|
851
|
+
- \u274C \u81EA\u5DF1\u7F16\u5199\u5DE5\u5177\u8FD4\u56DE\u7ED3\u679C\uFF08\u5982 \`\u7ED3\u679C:{...}\` \u6216 \`{"code":200...}\`\uFF09
|
|
852
|
+
- \u274C \u6A21\u62DF\u5DE5\u5177\u8C03\u7528\uFF08\u5982 \`RPCCall:\`\u3001\`FunctionCall:\`\uFF09
|
|
853
|
+
- \u274C \u5728\u6CA1\u6709\u771F\u5B9E\u5DE5\u5177\u7ED3\u679C\u7684\u60C5\u51B5\u4E0B\u7F16\u9020\u6570\u636E
|
|
854
|
+
- \u274C \u4E00\u6B21\u8F93\u51FA\u4E2D\u540C\u65F6\u5305\u542B\u5DE5\u5177\u8C03\u7528\u548C\u6700\u7EC8\u56DE\u590D
|
|
855
|
+
|
|
856
|
+
### \u6B63\u786E\u793A\u4F8B
|
|
857
|
+
\u7528\u6237: "\u67E5\u8BE2\u6211\u7684\u8BA2\u5355"
|
|
858
|
+
\u4F60\u7684\u8F93\u51FA:
|
|
859
|
+
<tool_call>
|
|
860
|
+
{"name": "get_orders", "arguments": {"token": "xxx"}}
|
|
861
|
+
</tool_call>
|
|
862
|
+
|
|
863
|
+
\uFF08\u7136\u540E\u505C\u6B62\uFF0C\u7B49\u5F85\u7CFB\u7EDF\u8FD4\u56DE\u771F\u5B9E\u7ED3\u679C\uFF09
|
|
864
|
+
|
|
865
|
+
### \u56DE\u590D\u683C\u5F0F
|
|
866
|
+
- \u4F7F\u7528\u4E2D\u6587
|
|
867
|
+
- \u4F7F\u7528 Markdown \u683C\u5F0F\u7F8E\u5316\u8F93\u51FA
|
|
868
|
+
- \u5217\u8868\u6570\u636E\u6BCF\u9879\u72EC\u5360\u4E00\u884C
|
|
869
|
+
`;
|
|
870
|
+
/**
|
|
871
|
+
* 构建完整的系统提示词
|
|
872
|
+
*/
|
|
873
|
+
buildSystemPrompt(tools) {
|
|
874
|
+
const toolsDescription = this.generateToolsDescription(tools);
|
|
875
|
+
const userPrompt = this.systemPrompt || "\u4F60\u662F\u4E00\u4E2A\u667A\u80FD\u52A9\u624B\u3002";
|
|
876
|
+
return `${userPrompt}
|
|
877
|
+
|
|
878
|
+
## \u53EF\u7528\u5DE5\u5177
|
|
879
|
+
${toolsDescription}
|
|
880
|
+
${this.BUILT_IN_PROMPT}`;
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* 解析工具调用
|
|
884
|
+
*/
|
|
885
|
+
parseToolCall(text) {
|
|
886
|
+
const tagMatch = text.match(/<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/i);
|
|
887
|
+
if (tagMatch) {
|
|
888
|
+
try {
|
|
889
|
+
const json = JSON.parse(tagMatch[1].trim());
|
|
890
|
+
if (json.name) return { name: json.name, arguments: json.arguments || {} };
|
|
891
|
+
} catch {
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
const codeMatch = text.match(/```(?:json)?\s*\n?\s*(\{[\s\S]*?"name"[\s\S]*?\})\s*\n?\s*```/i);
|
|
895
|
+
if (codeMatch) {
|
|
896
|
+
try {
|
|
897
|
+
const json = JSON.parse(codeMatch[1].trim());
|
|
898
|
+
if (json.name) return { name: json.name, arguments: json.arguments || {} };
|
|
899
|
+
} catch {
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const jsonMatch = text.match(/\{\s*"name"\s*:\s*"([^"]+)"[\s\S]*?"arguments"\s*:\s*(\{[\s\S]*?\})\s*\}/i);
|
|
903
|
+
if (jsonMatch) {
|
|
904
|
+
try {
|
|
905
|
+
const fullMatch = jsonMatch[0];
|
|
906
|
+
const json = JSON.parse(fullMatch);
|
|
907
|
+
if (json.name) return { name: json.name, arguments: json.arguments || {} };
|
|
908
|
+
} catch {
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* 智能压缩历史消息
|
|
915
|
+
* - 用户消息完整保留
|
|
916
|
+
* - AI 回复保留关键信息(ID、名称、数量、价格等)
|
|
917
|
+
* - 去除冗长的 JSON 原始数据
|
|
918
|
+
*/
|
|
919
|
+
compressHistory(history) {
|
|
920
|
+
const MAX_USER_MESSAGE_LENGTH = 500;
|
|
921
|
+
const MAX_ASSISTANT_MESSAGE_LENGTH = 1500;
|
|
922
|
+
const recentHistory = history.slice(-20);
|
|
923
|
+
return recentHistory.map((msg) => {
|
|
924
|
+
if (msg.role === "user") {
|
|
925
|
+
if (msg.content.length <= MAX_USER_MESSAGE_LENGTH) {
|
|
926
|
+
return msg;
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
role: msg.role,
|
|
930
|
+
content: msg.content.slice(0, MAX_USER_MESSAGE_LENGTH) + "..."
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
let content = msg.content;
|
|
934
|
+
content = content.replace(/```json\n[\s\S]*?\n```/g, "[\u5DE5\u5177\u8FD4\u56DE\u6570\u636E]");
|
|
935
|
+
content = content.replace(/## .*?\(原始JSON\)[\s\S]*?(?=##|$)/g, "");
|
|
936
|
+
if (content.length > MAX_ASSISTANT_MESSAGE_LENGTH) {
|
|
937
|
+
const tableMatch = content.match(/\|[\s\S]*?\|/g);
|
|
938
|
+
if (tableMatch) {
|
|
939
|
+
const tables = tableMatch.join("\n");
|
|
940
|
+
if (tables.length < MAX_ASSISTANT_MESSAGE_LENGTH) {
|
|
941
|
+
content = content.slice(0, MAX_ASSISTANT_MESSAGE_LENGTH - tables.length) + "\n" + tables;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
content = content.slice(0, MAX_ASSISTANT_MESSAGE_LENGTH) + "...";
|
|
945
|
+
}
|
|
946
|
+
return { role: msg.role, content: content.trim() || msg.content.slice(0, 500) };
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* 流式对话
|
|
951
|
+
*/
|
|
952
|
+
async *chatStream(userMessage, options) {
|
|
953
|
+
const startTime = Date.now();
|
|
954
|
+
let mcpTools = this.mcpManager.getAllTools();
|
|
955
|
+
if (options?.allowedTools?.length) {
|
|
956
|
+
mcpTools = mcpTools.filter((t) => options.allowedTools.includes(t.name));
|
|
957
|
+
}
|
|
958
|
+
const messages = [
|
|
959
|
+
{ role: "system", content: this.buildSystemPrompt(mcpTools) }
|
|
960
|
+
];
|
|
961
|
+
if (options?.history?.length) {
|
|
962
|
+
const compressedHistory = this.compressHistory(options.history);
|
|
963
|
+
console.log(`[PromptBasedAgent] \u{1F4DA} \u5386\u53F2\u6D88\u606F: ${options.history.length} \u6761 -> \u538B\u7F29\u540E: ${compressedHistory.length} \u6761`);
|
|
964
|
+
for (const msg of compressedHistory) {
|
|
965
|
+
messages.push({ role: msg.role, content: msg.content });
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
messages.push({ role: "user", content: userMessage });
|
|
969
|
+
console.log(`[PromptBasedAgent] \u{1F4DD} \u7528\u6237\u6D88\u606F: "${userMessage.slice(0, 50)}${userMessage.length > 50 ? "..." : ""}"`);
|
|
970
|
+
console.log(`[PromptBasedAgent] \u{1F4CA} \u603B\u6D88\u606F\u6570: ${messages.length}`);
|
|
971
|
+
let iteration = 0;
|
|
972
|
+
while (iteration < this.maxIterations) {
|
|
973
|
+
iteration++;
|
|
974
|
+
yield {
|
|
975
|
+
type: "iteration_start" /* ITERATION_START */,
|
|
976
|
+
timestamp: Date.now(),
|
|
977
|
+
data: { iteration, maxIterations: this.maxIterations }
|
|
978
|
+
};
|
|
979
|
+
console.log(`[PromptBasedAgent] \u{1F916} \u8C03\u7528\u6A21\u578B\uFF0C\u8FED\u4EE3 ${iteration}/${this.maxIterations}...`);
|
|
980
|
+
const modelStartTime = Date.now();
|
|
981
|
+
const stream = streamText({
|
|
982
|
+
model: this.model,
|
|
983
|
+
messages,
|
|
984
|
+
// 设置请求超时
|
|
985
|
+
experimental_telemetry: {
|
|
986
|
+
isEnabled: false
|
|
987
|
+
// 禁用遥测以减少开销
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
let fullResponse = "";
|
|
991
|
+
let buffer = "";
|
|
992
|
+
let inThinking = false;
|
|
993
|
+
let inToolCall = false;
|
|
994
|
+
let thinkingStarted = false;
|
|
995
|
+
let thinkingEnded = false;
|
|
996
|
+
let textStarted = false;
|
|
997
|
+
let firstChunkReceived = false;
|
|
998
|
+
const FIRST_CHUNK_TIMEOUT = 12e4;
|
|
999
|
+
let timeoutId = null;
|
|
1000
|
+
new Promise((_, reject) => {
|
|
1001
|
+
timeoutId = setTimeout(() => {
|
|
1002
|
+
reject(new Error(`\u6A21\u578B\u54CD\u5E94\u8D85\u65F6 (${FIRST_CHUNK_TIMEOUT / 1e3}\u79D2\u65E0\u54CD\u5E94)`));
|
|
1003
|
+
}, FIRST_CHUNK_TIMEOUT);
|
|
1004
|
+
});
|
|
1005
|
+
try {
|
|
1006
|
+
for await (const chunk of stream.fullStream) {
|
|
1007
|
+
if (!firstChunkReceived) {
|
|
1008
|
+
firstChunkReceived = true;
|
|
1009
|
+
if (timeoutId) {
|
|
1010
|
+
clearTimeout(timeoutId);
|
|
1011
|
+
timeoutId = null;
|
|
1012
|
+
}
|
|
1013
|
+
console.log(`[PromptBasedAgent] \u26A1 \u9996\u4E2A chunk \u5230\u8FBE\uFF0C\u8017\u65F6: ${Date.now() - modelStartTime}ms`);
|
|
1014
|
+
}
|
|
1015
|
+
if (chunk.type === "reasoning") {
|
|
1016
|
+
if (!thinkingStarted) {
|
|
1017
|
+
thinkingStarted = true;
|
|
1018
|
+
yield { type: "thinking_start" /* THINKING_START */, timestamp: Date.now(), data: {} };
|
|
1019
|
+
}
|
|
1020
|
+
if (chunk.textDelta) {
|
|
1021
|
+
yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content: chunk.textDelta } };
|
|
1022
|
+
}
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
if (chunk.type === "text-delta") {
|
|
1026
|
+
const delta = chunk.textDelta;
|
|
1027
|
+
buffer += delta;
|
|
1028
|
+
fullResponse += delta;
|
|
1029
|
+
while (buffer.length > 0) {
|
|
1030
|
+
if (!inThinking && !inToolCall) {
|
|
1031
|
+
const thinkStart = buffer.indexOf("<think>");
|
|
1032
|
+
if (thinkStart !== -1) {
|
|
1033
|
+
if (thinkStart > 0) {
|
|
1034
|
+
const before = buffer.substring(0, thinkStart);
|
|
1035
|
+
if (before.trim() && thinkingEnded) {
|
|
1036
|
+
if (!textStarted) {
|
|
1037
|
+
textStarted = true;
|
|
1038
|
+
yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
|
|
1039
|
+
}
|
|
1040
|
+
yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: before } };
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
inThinking = true;
|
|
1044
|
+
if (!thinkingStarted) {
|
|
1045
|
+
thinkingStarted = true;
|
|
1046
|
+
yield { type: "thinking_start" /* THINKING_START */, timestamp: Date.now(), data: {} };
|
|
1047
|
+
}
|
|
1048
|
+
buffer = buffer.substring(thinkStart + 7);
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
const toolStart = buffer.indexOf("<tool_call>");
|
|
1052
|
+
if (toolStart !== -1) {
|
|
1053
|
+
if (toolStart > 0) {
|
|
1054
|
+
const before = buffer.substring(0, toolStart);
|
|
1055
|
+
if (before.trim() && thinkingEnded) {
|
|
1056
|
+
if (!textStarted) {
|
|
1057
|
+
textStarted = true;
|
|
1058
|
+
yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
|
|
1059
|
+
}
|
|
1060
|
+
yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: before } };
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
inToolCall = true;
|
|
1064
|
+
buffer = buffer.substring(toolStart + 11);
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
if (!buffer.includes("<")) {
|
|
1068
|
+
if (buffer.trim() && (thinkingEnded || !thinkingStarted)) {
|
|
1069
|
+
if (!thinkingStarted && !thinkingEnded) {
|
|
1070
|
+
thinkingStarted = true;
|
|
1071
|
+
thinkingEnded = true;
|
|
1072
|
+
yield { type: "thinking_start" /* THINKING_START */, timestamp: Date.now(), data: {} };
|
|
1073
|
+
yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content: "\u5206\u6790\u7528\u6237\u8BF7\u6C42..." } };
|
|
1074
|
+
yield { type: "thinking_end" /* THINKING_END */, timestamp: Date.now(), data: {} };
|
|
1075
|
+
}
|
|
1076
|
+
if (!textStarted) {
|
|
1077
|
+
textStarted = true;
|
|
1078
|
+
yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
|
|
1079
|
+
}
|
|
1080
|
+
yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: buffer } };
|
|
1081
|
+
}
|
|
1082
|
+
buffer = "";
|
|
1083
|
+
}
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
if (inThinking) {
|
|
1087
|
+
const thinkEnd = buffer.indexOf("</think>");
|
|
1088
|
+
if (thinkEnd !== -1) {
|
|
1089
|
+
const content = buffer.substring(0, thinkEnd);
|
|
1090
|
+
if (content) {
|
|
1091
|
+
yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content } };
|
|
1092
|
+
}
|
|
1093
|
+
yield { type: "thinking_end" /* THINKING_END */, timestamp: Date.now(), data: {} };
|
|
1094
|
+
thinkingEnded = true;
|
|
1095
|
+
inThinking = false;
|
|
1096
|
+
buffer = buffer.substring(thinkEnd + 8);
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
if (buffer.length > 10 && !buffer.includes("<")) {
|
|
1100
|
+
const safe = buffer.substring(0, buffer.length - 10);
|
|
1101
|
+
yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content: safe } };
|
|
1102
|
+
buffer = buffer.substring(safe.length);
|
|
1103
|
+
}
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
if (inToolCall) {
|
|
1107
|
+
const toolEnd = buffer.indexOf("</tool_call>");
|
|
1108
|
+
if (toolEnd !== -1) {
|
|
1109
|
+
inToolCall = false;
|
|
1110
|
+
buffer = buffer.substring(toolEnd + 12);
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (chunk.type === "finish") {
|
|
1119
|
+
if (buffer.trim()) {
|
|
1120
|
+
if (inThinking) {
|
|
1121
|
+
yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content: buffer } };
|
|
1122
|
+
yield { type: "thinking_end" /* THINKING_END */, timestamp: Date.now(), data: {} };
|
|
1123
|
+
thinkingEnded = true;
|
|
1124
|
+
} else if (!inToolCall) {
|
|
1125
|
+
if (!textStarted) {
|
|
1126
|
+
textStarted = true;
|
|
1127
|
+
yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
|
|
1128
|
+
}
|
|
1129
|
+
yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: buffer } };
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (textStarted) {
|
|
1133
|
+
yield { type: "text_end" /* TEXT_END */, timestamp: Date.now(), data: {} };
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
} finally {
|
|
1138
|
+
if (timeoutId) {
|
|
1139
|
+
clearTimeout(timeoutId);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
const toolCall = this.parseToolCall(fullResponse);
|
|
1143
|
+
if (toolCall) {
|
|
1144
|
+
const toolCallId = `tool-${Date.now()}`;
|
|
1145
|
+
yield {
|
|
1146
|
+
type: "tool_call_start" /* TOOL_CALL_START */,
|
|
1147
|
+
timestamp: Date.now(),
|
|
1148
|
+
data: { toolName: toolCall.name, toolCallId, toolArgs: toolCall.arguments }
|
|
1149
|
+
};
|
|
1150
|
+
yield {
|
|
1151
|
+
type: "tool_executing" /* TOOL_EXECUTING */,
|
|
1152
|
+
timestamp: Date.now(),
|
|
1153
|
+
data: { toolName: toolCall.name, toolCallId, toolArgs: toolCall.arguments }
|
|
1154
|
+
};
|
|
1155
|
+
const toolStartTime = Date.now();
|
|
1156
|
+
let result;
|
|
1157
|
+
let isError = false;
|
|
1158
|
+
try {
|
|
1159
|
+
result = await this.mcpManager.callTool(toolCall.name, toolCall.arguments);
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
result = error instanceof Error ? error.message : String(error);
|
|
1162
|
+
isError = true;
|
|
1163
|
+
}
|
|
1164
|
+
const duration = Date.now() - toolStartTime;
|
|
1165
|
+
yield {
|
|
1166
|
+
type: "tool_result" /* TOOL_RESULT */,
|
|
1167
|
+
timestamp: Date.now(),
|
|
1168
|
+
data: { toolName: toolCall.name, toolResult: result, toolCallId, duration, isError }
|
|
1169
|
+
};
|
|
1170
|
+
messages.push({ role: "assistant", content: fullResponse });
|
|
1171
|
+
const resultStr = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
1172
|
+
messages.push({
|
|
1173
|
+
role: "user",
|
|
1174
|
+
content: `\u5DE5\u5177 ${toolCall.name} \u8FD4\u56DE\u7ED3\u679C\uFF1A
|
|
1175
|
+
${resultStr}
|
|
1176
|
+
|
|
1177
|
+
\u8BF7\u6839\u636E\u7ED3\u679C\u7528\u4E2D\u6587\u56DE\u590D\u7528\u6237\u3002`
|
|
1178
|
+
});
|
|
1179
|
+
yield { type: "iteration_end" /* ITERATION_END */, timestamp: Date.now(), data: { iteration } };
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
console.log(`[PromptBasedAgent] \u2705 \u6A21\u578B\u54CD\u5E94\u5B8C\u6210\uFF0C\u8017\u65F6: ${Date.now() - modelStartTime}ms\uFF0C\u54CD\u5E94\u957F\u5EA6: ${fullResponse.length}`);
|
|
1183
|
+
if (!textStarted && fullResponse.trim()) {
|
|
1184
|
+
let cleanText = fullResponse.replace(/<think>[\s\S]*?<\/think>/gi, "").replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, "").trim();
|
|
1185
|
+
if (cleanText) {
|
|
1186
|
+
yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
|
|
1187
|
+
yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: cleanText } };
|
|
1188
|
+
yield { type: "text_end" /* TEXT_END */, timestamp: Date.now(), data: {} };
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
yield { type: "iteration_end" /* ITERATION_END */, timestamp: Date.now(), data: { iteration } };
|
|
1192
|
+
break;
|
|
1193
|
+
}
|
|
1194
|
+
yield {
|
|
1195
|
+
type: "complete" /* COMPLETE */,
|
|
1196
|
+
timestamp: Date.now(),
|
|
1197
|
+
data: { totalDuration: Date.now() - startTime, totalIterations: iteration }
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
|
|
1202
|
+
// src/MCPLink.ts
|
|
1203
|
+
var NATIVE_FUNCTION_CALLING_PATTERNS = [
|
|
1204
|
+
// OpenAI GPT 系列 - 支持原生 function calling
|
|
1205
|
+
/^gpt/i,
|
|
1206
|
+
// OpenAI o1/o3 需要特殊处理,暂用 PromptBased
|
|
1207
|
+
// /^o1/i,
|
|
1208
|
+
// /^o3/i,
|
|
1209
|
+
// Anthropic Claude - 支持原生 function calling
|
|
1210
|
+
/^claude/i,
|
|
1211
|
+
// Google Gemini 稳定版 - 支持原生 function calling
|
|
1212
|
+
// 注意:gemini-*-preview/thinking 版本需要特殊处理,不在此列表
|
|
1213
|
+
/^gemini-[\d.]+-flash$/i,
|
|
1214
|
+
/^gemini-[\d.]+-pro$/i,
|
|
1215
|
+
/^gemini-pro$/i,
|
|
1216
|
+
/^gemini-flash$/i,
|
|
1217
|
+
// Mistral - 支持原生 function calling
|
|
1218
|
+
/^mistral/i,
|
|
1219
|
+
/^mixtral/i,
|
|
1220
|
+
// Cohere Command-R - 支持原生 function calling
|
|
1221
|
+
/^command-r/i
|
|
1222
|
+
];
|
|
1223
|
+
var PROMPT_BASED_PATTERNS = [
|
|
1224
|
+
// DeepSeek(不支持原生 function calling)
|
|
1225
|
+
/deepseek/i,
|
|
1226
|
+
// OpenAI o1/o3 思考模型
|
|
1227
|
+
/^o1/i,
|
|
1228
|
+
/^o3/i,
|
|
1229
|
+
// Gemini 思考/预览版本 - 需要 thought_signature,暂用 PromptBased
|
|
1230
|
+
/gemini.*preview/i,
|
|
1231
|
+
/gemini.*thinking/i,
|
|
1232
|
+
/gemini.*exp/i,
|
|
1233
|
+
// 开源模型(大多数不支持原生 function calling)
|
|
1234
|
+
/^llama/i,
|
|
1235
|
+
/^phi-/i,
|
|
1236
|
+
/^qwen/i,
|
|
1237
|
+
/^yi-/i,
|
|
1238
|
+
/^glm/i,
|
|
1239
|
+
/^baichuan/i
|
|
1240
|
+
];
|
|
1241
|
+
function detectNativeToolSupport(modelId) {
|
|
1242
|
+
console.log(`[MCPLink] \u{1F50D} \u68C0\u6D4B\u6A21\u578B: "${modelId}"`);
|
|
1243
|
+
for (const pattern of PROMPT_BASED_PATTERNS) {
|
|
1244
|
+
if (pattern.test(modelId)) {
|
|
1245
|
+
console.log(`[MCPLink] \u2705 Model "${modelId}" -> PromptBasedAgent (matched: ${pattern})`);
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
for (const pattern of NATIVE_FUNCTION_CALLING_PATTERNS) {
|
|
1250
|
+
if (pattern.test(modelId)) {
|
|
1251
|
+
console.log(`[MCPLink] \u2705 Model "${modelId}" -> Agent (\u539F\u751F\u6A21\u5F0F, matched: ${pattern})`);
|
|
1252
|
+
return true;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
console.log(`[MCPLink] \u26A0\uFE0F Model "${modelId}" -> PromptBasedAgent (\u672A\u77E5\u6A21\u578B\uFF0C\u9ED8\u8BA4)`);
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1258
|
+
var MCPLink = class {
|
|
1259
|
+
model;
|
|
1260
|
+
mcpManager;
|
|
1261
|
+
agent;
|
|
1262
|
+
promptBasedAgent;
|
|
1263
|
+
config;
|
|
1264
|
+
initialized = false;
|
|
1265
|
+
detectedNativeSupport;
|
|
1266
|
+
constructor(config) {
|
|
1267
|
+
this.config = config;
|
|
1268
|
+
this.model = config.model;
|
|
1269
|
+
this.mcpManager = new MCPManager();
|
|
1270
|
+
if (config.mcpServers) {
|
|
1271
|
+
for (const [id, serverConfig] of Object.entries(config.mcpServers)) {
|
|
1272
|
+
this.mcpManager.addServer(id, serverConfig);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
this.agent = new Agent(this.model, this.mcpManager, {
|
|
1276
|
+
systemPrompt: config.systemPrompt,
|
|
1277
|
+
maxIterations: config.maxIterations
|
|
1278
|
+
});
|
|
1279
|
+
this.promptBasedAgent = new PromptBasedAgent(this.model, this.mcpManager, {
|
|
1280
|
+
systemPrompt: config.systemPrompt,
|
|
1281
|
+
maxIterations: config.maxIterations
|
|
1282
|
+
});
|
|
1283
|
+
if (config.usePromptBasedTools === true) {
|
|
1284
|
+
this.detectedNativeSupport = false;
|
|
1285
|
+
} else if (config.usePromptBasedTools === false) {
|
|
1286
|
+
this.detectedNativeSupport = true;
|
|
1287
|
+
} else {
|
|
1288
|
+
const modelNameToCheck = config.modelName || config.model.modelId;
|
|
1289
|
+
this.detectedNativeSupport = detectNativeToolSupport(modelNameToCheck);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* 初始化 - 连接所有 MCP 服务器
|
|
1294
|
+
*/
|
|
1295
|
+
async initialize() {
|
|
1296
|
+
if (this.initialized) {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
await this.mcpManager.startAll();
|
|
1300
|
+
this.initialized = true;
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* 关闭 - 断开所有 MCP 服务器连接
|
|
1304
|
+
*/
|
|
1305
|
+
async close() {
|
|
1306
|
+
await this.mcpManager.stopAll();
|
|
1307
|
+
this.initialized = false;
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* 发起对话
|
|
1311
|
+
*/
|
|
1312
|
+
async chat(message, callbacks) {
|
|
1313
|
+
if (!this.initialized) {
|
|
1314
|
+
await this.initialize();
|
|
1315
|
+
}
|
|
1316
|
+
return this.agent.chat(message, callbacks);
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* 流式对话
|
|
1320
|
+
* @param message 用户消息
|
|
1321
|
+
* @param options 可选参数
|
|
1322
|
+
* @param options.allowedTools 允许使用的工具名称列表
|
|
1323
|
+
* @param options.history 历史消息列表
|
|
1324
|
+
*/
|
|
1325
|
+
async *chatStream(message, options) {
|
|
1326
|
+
if (!this.initialized) {
|
|
1327
|
+
await this.initialize();
|
|
1328
|
+
}
|
|
1329
|
+
if (this.detectedNativeSupport) {
|
|
1330
|
+
yield* this.agent.chatStream(message, options);
|
|
1331
|
+
} else {
|
|
1332
|
+
yield* this.promptBasedAgent.chatStream(message, options);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* 获取当前使用的模式
|
|
1337
|
+
*/
|
|
1338
|
+
getToolCallingMode() {
|
|
1339
|
+
return this.detectedNativeSupport ? "native" : "prompt-based";
|
|
1340
|
+
}
|
|
1341
|
+
// ============ MCP 服务器管理 ============
|
|
1342
|
+
/**
|
|
1343
|
+
* 添加 MCP 服务器
|
|
1344
|
+
*/
|
|
1345
|
+
addMCPServer(id, config) {
|
|
1346
|
+
this.mcpManager.addServer(id, config);
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* 移除 MCP 服务器
|
|
1350
|
+
*/
|
|
1351
|
+
async removeMCPServer(id) {
|
|
1352
|
+
await this.mcpManager.removeServer(id);
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* 启动指定 MCP 服务器
|
|
1356
|
+
*/
|
|
1357
|
+
async startMCPServer(id) {
|
|
1358
|
+
await this.mcpManager.startServer(id);
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* 停止指定 MCP 服务器
|
|
1362
|
+
*/
|
|
1363
|
+
async stopMCPServer(id) {
|
|
1364
|
+
await this.mcpManager.stopServer(id);
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* 获取所有 MCP 服务器状态
|
|
1368
|
+
*/
|
|
1369
|
+
getMCPServerStatuses() {
|
|
1370
|
+
return this.mcpManager.getServerStatuses();
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* 获取所有可用工具
|
|
1374
|
+
*/
|
|
1375
|
+
getTools() {
|
|
1376
|
+
return this.mcpManager.getAllTools();
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* 手动调用工具
|
|
1380
|
+
*/
|
|
1381
|
+
async callTool(toolName, args) {
|
|
1382
|
+
return this.mcpManager.callTool(toolName, args);
|
|
1383
|
+
}
|
|
1384
|
+
// ============ 配置管理 ============
|
|
1385
|
+
/**
|
|
1386
|
+
* 更新系统提示词
|
|
1387
|
+
*/
|
|
1388
|
+
setSystemPrompt(prompt) {
|
|
1389
|
+
this.config.systemPrompt = prompt;
|
|
1390
|
+
this.agent = new Agent(this.model, this.mcpManager, {
|
|
1391
|
+
systemPrompt: prompt,
|
|
1392
|
+
maxIterations: this.config.maxIterations
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* 更新 AI 模型
|
|
1397
|
+
*/
|
|
1398
|
+
setModel(model) {
|
|
1399
|
+
this.model = model;
|
|
1400
|
+
this.config.model = model;
|
|
1401
|
+
this.agent = new Agent(this.model, this.mcpManager, {
|
|
1402
|
+
systemPrompt: this.config.systemPrompt,
|
|
1403
|
+
maxIterations: this.config.maxIterations
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
|
|
1408
|
+
export { Agent, DEFAULT_SYSTEM_PROMPT, MCPLink, MCPLinkEventType, MCPManager, PromptBasedAgent };
|
|
1409
|
+
//# sourceMappingURL=index.js.map
|
|
1410
|
+
//# sourceMappingURL=index.js.map
|